pi-subagents 0.42.1 → 0.44.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 +50 -0
- package/README.md +3 -5
- package/package.json +1 -1
- package/skills/pi-subagents/SKILL.md +2 -2
- package/skills/pi-subagents/references/constraints-and-recipes.md +35 -32
- package/skills/pi-subagents/references/execution-controls.md +30 -44
- package/skills/pi-subagents/references/management-authoring-rpc.md +20 -3
- package/skills/pi-subagents/references/prompting-and-roles.md +16 -49
- package/src/agents/agent-refinements.ts +624 -0
- package/src/agents/agents.ts +0 -2
- package/src/agents/proactive-skills.ts +1 -1
- package/src/api/delegation.ts +1 -2
- package/src/extension/config.ts +28 -7
- package/src/extension/control-notices.ts +2 -2
- package/src/extension/fanout-child.ts +46 -22
- package/src/extension/index.ts +32 -13
- package/src/extension/public-execution.ts +71 -0
- package/src/extension/rpc.ts +7 -8
- package/src/extension/schemas.ts +14 -14
- package/src/extension/tool-description.ts +14 -14
- package/src/missions/actions.ts +47 -11
- package/src/missions/goal-driver.ts +162 -0
- package/src/missions/lifecycle.ts +44 -12
- package/src/missions/store.ts +68 -3
- package/src/missions/types.ts +25 -3
- package/src/missions/workflow-state.ts +250 -0
- package/src/profiles/profiles.ts +1 -3
- package/src/runs/background/async-execution.ts +5 -1
- package/src/runs/background/async-job-tracker.ts +2 -17
- package/src/runs/background/async-status.ts +2 -0
- package/src/runs/background/control-channel.ts +50 -6
- package/src/runs/background/retained-children.ts +68 -0
- package/src/runs/background/run-id-resolver.ts +71 -0
- package/src/runs/background/run-status.ts +3 -2
- package/src/runs/background/scheduled-runs.ts +17 -13
- package/src/runs/background/steering.ts +7 -5
- package/src/runs/background/subagent-runner.ts +50 -23
- package/src/runs/foreground/async-steering-action.ts +36 -10
- package/src/runs/foreground/chain-clarify.ts +7 -3
- package/src/runs/foreground/execution.ts +29 -19
- package/src/runs/foreground/subagent-executor.ts +282 -77
- package/src/runs/shared/acceptance.ts +154 -9
- package/src/runs/shared/subagent-prompt-runtime.ts +97 -25
- package/src/shared/settings.ts +10 -6
- package/src/shared/types.ts +22 -2
- package/src/slash/delegation-adapters.ts +0 -13
- package/src/slash/prompt-template-bridge.ts +14 -12
- package/src/slash/prompt-workflows.ts +10 -4
- package/src/slash/slash-bridge.ts +8 -6
- package/src/slash/slash-commands.ts +24 -9
- package/src/slash/slash-live-state.ts +4 -2
- package/src/tui/fleet-status.ts +8 -4
- package/src/tui/fleet.ts +15 -5
- package/src/tui/render.ts +10 -29
- package/src/workflows/scripted-workflow.ts +91 -6
- package/agents/context-builder.md +0 -46
- package/agents/planner.md +0 -56
- package/prompts/parallel-context-build.md +0 -55
- package/prompts/parallel-handoff-plan.md +0 -61
|
@@ -19,11 +19,14 @@ import {
|
|
|
19
19
|
} from "./foreground-control.ts";
|
|
20
20
|
import { resolveExecutionAgentScope } from "../../agents/agent-scope.ts";
|
|
21
21
|
import { handleManagementAction } from "../../agents/agent-management.ts";
|
|
22
|
+
import { handleRefinementAction } from "../../agents/agent-refinements.ts";
|
|
22
23
|
import { buildDoctorReport } from "../../extension/doctor.ts";
|
|
24
|
+
import { normalizePublicSubagentExecution } from "../../extension/public-execution.ts";
|
|
23
25
|
import { runSync } from "./execution.ts";
|
|
24
26
|
import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.ts";
|
|
25
27
|
import type { MainWatchdogRuntime } from "../../watchdog/runtime.ts";
|
|
26
28
|
import { buildModelCandidates, normalizeParentModel, resolveEffectiveSubagentModel, resolveModelCandidate, type ParentModel } from "../shared/model-fallback.ts";
|
|
29
|
+
import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.ts";
|
|
27
30
|
import type { ModelScopeConfig } from "../shared/model-scope.ts";
|
|
28
31
|
import { aggregateParallelOutputs } from "../shared/parallel-utils.ts";
|
|
29
32
|
import { recordRun } from "../shared/run-history.ts";
|
|
@@ -49,7 +52,7 @@ import { buildAsyncRunnerSteps, executeAsyncChain, executeAsyncSingle, formatAsy
|
|
|
49
52
|
import type { ScheduledRunAction } from "../background/scheduled-runs.ts";
|
|
50
53
|
import { enqueueChainAppendRequest, readPendingChainAppendRequests, runnerStepOutputNames } from "../background/chain-append.ts";
|
|
51
54
|
import { ChainOutputValidationError, validateChainOutputBindingsWithContext } from "../shared/chain-outputs.ts";
|
|
52
|
-
import { validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
55
|
+
import { normalizeGateAcceptance, validateExecutionAcceptance } from "../shared/acceptance.ts";
|
|
53
56
|
import { createForkContextResolver, forkedChildRequiresThinkingOff } from "../../shared/fork-context.ts";
|
|
54
57
|
import { resolveCurrentSessionId } from "../../shared/session-identity.ts";
|
|
55
58
|
import { applyIntercomBridgeToAgent, INTERCOM_BRIDGE_MARKER, resolveIntercomBridge, resolveIntercomSessionTarget, resolveSubagentIntercomTarget, type IntercomBridgeState } from "../../intercom/intercom-bridge.ts";
|
|
@@ -75,8 +78,8 @@ import {
|
|
|
75
78
|
stripDetailsOutputsForIntercomReceipt,
|
|
76
79
|
} from "../../intercom/result-intercom.ts";
|
|
77
80
|
import { applySteeringRecoveryAgentConfig, buildRevivedAsyncTask, resolveAsyncResumeTarget, resolveAsyncRunLocation } from "../background/async-resume.ts";
|
|
78
|
-
import { deliverCheckpointDecisionRequest, deliverInterruptRequest, requestAsyncSteer } from "../background/control-channel.ts";
|
|
79
|
-
import { waitForSteeringAction } from "../background/steering.ts";
|
|
81
|
+
import { deliverCheckpointDecisionRequest, deliverInterruptRequest, readRevivalBriefs, requestAsyncSteer, type SteerDeliveryMode } from "../background/control-channel.ts";
|
|
82
|
+
import { updateSteeringTarget, waitForSteeringAction } from "../background/steering.ts";
|
|
80
83
|
import { steerAsyncRun } from "./async-steering-action.ts";
|
|
81
84
|
import { stopAsyncRun } from "./async-stop-action.ts";
|
|
82
85
|
import { reconcileAsyncRun } from "../background/stale-run-reconciler.ts";
|
|
@@ -88,10 +91,12 @@ import { inspectSubagentStatus } from "../background/run-status.ts";
|
|
|
88
91
|
import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.ts";
|
|
89
92
|
import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.ts";
|
|
90
93
|
import { attachMissionToLaunchResult, prepareMissionLaunch, type MissionLaunchBinding } from "../../missions/lifecycle.ts";
|
|
94
|
+
import { updateMission } from "../../missions/store.ts";
|
|
95
|
+
import { createMissionWorkflowState } from "../../missions/workflow-state.ts";
|
|
91
96
|
import { resolveAuthorityDecision } from "../../policy/authority.ts";
|
|
92
97
|
import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
|
|
93
98
|
import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
|
|
94
|
-
import { runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
99
|
+
import { previewSimpleWorkflowRun, runWorkflowScript, WorkflowScriptError, type WorkflowScriptChildResult } from "../../workflows/scripted-workflow.ts";
|
|
95
100
|
import { resolveWorkflowChatProgress, type WorkflowChatProgressProjection } from "../../workflows/chat-progress.ts";
|
|
96
101
|
import {
|
|
97
102
|
cleanupWorktrees,
|
|
@@ -144,7 +149,7 @@ import {
|
|
|
144
149
|
wrapForkTask,
|
|
145
150
|
} from "../../shared/types.ts";
|
|
146
151
|
|
|
147
|
-
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
152
|
+
const MUTATING_MANAGEMENT_ACTIONS = new Set(["create", "update", "delete", "eject", "disable", "enable", "reset", "grant-spawn-budget", "watchdog.configure", "mission.create", "mission.update", "mission.attach-run", "mission.close", "inspector.open", "inspector.close", "project.open", "project.close", "worktree.discard", "refine", "refine.rollback", "schedule.create", "schedule.pause", "schedule.resume", "schedule.run", "schedule.run-due", "schedule.delete"]);
|
|
148
153
|
|
|
149
154
|
type UndefinedOmitted<T extends object> = {
|
|
150
155
|
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
|
@@ -209,8 +214,11 @@ export interface SubagentParamsLike {
|
|
|
209
214
|
type?: string;
|
|
210
215
|
agent?: string;
|
|
211
216
|
task?: string;
|
|
217
|
+
/** Retained async child run id. Valid only on workflow runs.run items. */
|
|
218
|
+
resume?: string;
|
|
212
219
|
message?: string;
|
|
213
220
|
steeringRecovery?: boolean;
|
|
221
|
+
mode?: SteerDeliveryMode;
|
|
214
222
|
workflowScript?: string;
|
|
215
223
|
chatProgress?: "auto" | "off" | "live-card";
|
|
216
224
|
step?: ChainStep;
|
|
@@ -253,6 +261,7 @@ export interface SubagentParamsLike {
|
|
|
253
261
|
agentScope?: unknown;
|
|
254
262
|
chainDir?: string;
|
|
255
263
|
acceptance?: AcceptanceInput;
|
|
264
|
+
gate?: string;
|
|
256
265
|
agentContract?: AgentContract;
|
|
257
266
|
schedule?: string;
|
|
258
267
|
scheduleName?: string;
|
|
@@ -790,6 +799,20 @@ function getAsyncInterruptTarget(
|
|
|
790
799
|
return newest ? { asyncId: newest.asyncId, asyncDir: newest.asyncDir } : undefined;
|
|
791
800
|
}
|
|
792
801
|
|
|
802
|
+
function isStaleExtensionContextError(error: unknown): boolean {
|
|
803
|
+
if (!(error instanceof Error)) return false;
|
|
804
|
+
return /extension ctx is stale|stale after session replacement or reload/i.test(error.message);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function emitAdvisoryControlEvent(pi: ExtensionAPI, channel: string, payload: unknown): void {
|
|
808
|
+
try {
|
|
809
|
+
pi.events.emit(channel, payload);
|
|
810
|
+
} catch (error) {
|
|
811
|
+
if (isStaleExtensionContextError(error)) return;
|
|
812
|
+
throw error;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
793
816
|
function emitControlNotification(input: {
|
|
794
817
|
pi: ExtensionAPI;
|
|
795
818
|
controlConfig: ResolvedControlConfig;
|
|
@@ -807,10 +830,10 @@ function emitControlNotification(input: {
|
|
|
807
830
|
noticeText: formatControlNoticeMessage(input.event, childIntercomTarget),
|
|
808
831
|
};
|
|
809
832
|
if (input.controlConfig.notifyChannels.includes("event")) {
|
|
810
|
-
input.pi
|
|
833
|
+
emitAdvisoryControlEvent(input.pi, SUBAGENT_CONTROL_EVENT, payload);
|
|
811
834
|
}
|
|
812
835
|
if (input.event.type !== "active_long_running" && input.controlConfig.notifyChannels.includes("intercom") && input.intercomBridge.active && input.intercomBridge.orchestratorTarget) {
|
|
813
|
-
input.pi
|
|
836
|
+
emitAdvisoryControlEvent(input.pi, SUBAGENT_CONTROL_INTERCOM_EVENT, {
|
|
814
837
|
...payload,
|
|
815
838
|
to: input.intercomBridge.orchestratorTarget,
|
|
816
839
|
message: formatControlIntercomMessage(input.event, childIntercomTarget),
|
|
@@ -1160,7 +1183,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
|
|
|
1160
1183
|
}
|
|
1161
1184
|
}
|
|
1162
1185
|
|
|
1163
|
-
async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
|
|
1186
|
+
async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; mode?: SteerDeliveryMode; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
|
|
1164
1187
|
const run = input.target.match.run;
|
|
1165
1188
|
const asyncDir = resolveNestedAsyncDir(input.target.match.rootRunId, run);
|
|
1166
1189
|
if (!asyncDir) return undefined;
|
|
@@ -1182,6 +1205,7 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
|
|
|
1182
1205
|
try {
|
|
1183
1206
|
requestAsyncSteer(asyncDir, {
|
|
1184
1207
|
message: input.message,
|
|
1208
|
+
mode: input.mode,
|
|
1185
1209
|
...(effectiveTargetIndex !== undefined ? { targetIndex: effectiveTargetIndex } : { targetIndexes }),
|
|
1186
1210
|
source: "nested-steer",
|
|
1187
1211
|
id: requestId,
|
|
@@ -1191,12 +1215,12 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
|
|
|
1191
1215
|
}
|
|
1192
1216
|
const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
|
|
1193
1217
|
if (targets.every((target) => target.state === "scheduled")) {
|
|
1194
|
-
const scheduled = { requestId, state: "scheduled" as const, sourceRunId: run.id, targets };
|
|
1218
|
+
const scheduled = { requestId, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
|
|
1195
1219
|
return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
|
|
1196
1220
|
}
|
|
1197
1221
|
const waited = await waitForSteeringAction(omitUndefinedProperties({ asyncDir, sourceRunId: run.id, requestId, timeoutMs: 3_000, signal: input.signal }));
|
|
1198
|
-
const result = waited ?? { requestId, state: "pending" as const, sourceRunId: run.id, targets };
|
|
1199
|
-
const stateText = result.state === "
|
|
1222
|
+
const result = waited ?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
|
|
1223
|
+
const stateText = result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : result.deliveryStatus === "queued" ? "queued" : result.state === "delivered" ? "delivered" : "pending";
|
|
1200
1224
|
return { content: [{ type: "text", text: `Steering ${stateText} for nested async run ${run.id} (request ${requestId}).` }], ...(result.state === "failed" || result.state === "partial" ? { isError: true } : {}), details: { mode: "management", results: [], steering: result } };
|
|
1201
1225
|
}
|
|
1202
1226
|
|
|
@@ -1219,7 +1243,7 @@ async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { ki
|
|
|
1219
1243
|
return { content: [{ type: "text", text: `Nested run ${run.id} appears live but its owner route is not reachable. Wait for completion, then retry action='resume'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1220
1244
|
}
|
|
1221
1245
|
|
|
1222
|
-
async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
|
|
1246
|
+
async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; mode?: SteerDeliveryMode; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
|
|
1223
1247
|
const run = input.target.match.run;
|
|
1224
1248
|
if (run.state !== "running" && run.state !== "queued") return { content: [{ type: "text", text: `Nested run ${run.id} is ${run.state} and cannot be steered.` }], isError: true, details: { mode: "management", results: [] } };
|
|
1225
1249
|
const direct = await directNestedAsyncSteer(input);
|
|
@@ -1449,6 +1473,9 @@ async function resumeAsyncRun(input: {
|
|
|
1449
1473
|
return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
|
|
1450
1474
|
}
|
|
1451
1475
|
|
|
1476
|
+
const sourceAsyncDir = target.source === "async" ? target.asyncDir : undefined;
|
|
1477
|
+
const queuedBriefs = sourceAsyncDir ? readRevivalBriefs(sourceAsyncDir) : [];
|
|
1478
|
+
const effectiveFollowUp = [...queuedBriefs.map(({ request }) => request.message), followUp].filter(Boolean).join("\n\n");
|
|
1452
1479
|
const revivalSessionFile = target.sessionFile;
|
|
1453
1480
|
if (!revivalSessionFile) {
|
|
1454
1481
|
return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
|
|
@@ -1461,8 +1488,8 @@ async function resumeAsyncRun(input: {
|
|
|
1461
1488
|
const parentModel = input.parentModel;
|
|
1462
1489
|
const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
|
|
1463
1490
|
agent: target.agent,
|
|
1464
|
-
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0],
|
|
1465
|
-
goal:
|
|
1491
|
+
task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
|
|
1492
|
+
goal: effectiveFollowUp,
|
|
1466
1493
|
agentConfig: recoveryAgentConfig,
|
|
1467
1494
|
ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
|
|
1468
1495
|
pi: input.deps.pi,
|
|
@@ -1512,8 +1539,18 @@ async function resumeAsyncRun(input: {
|
|
|
1512
1539
|
...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
|
|
1513
1540
|
...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
|
|
1514
1541
|
capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
|
|
1542
|
+
parentWorkflowRunId: input.params.workflowParentRunId,
|
|
1543
|
+
workflowKey: input.params.workflowKey,
|
|
1515
1544
|
}));
|
|
1516
1545
|
if (result.isError) return result;
|
|
1546
|
+
for (const brief of queuedBriefs) fs.rmSync(brief.path, { force: true });
|
|
1547
|
+
if (queuedBriefs.length > 0 && sourceAsyncDir) {
|
|
1548
|
+
const sourceStatus = readStatus(sourceAsyncDir);
|
|
1549
|
+
if (sourceStatus?.steering) {
|
|
1550
|
+
for (const brief of queuedBriefs) updateSteeringTarget(sourceStatus.steering, brief.request.id, target.index, "delivered", Date.now());
|
|
1551
|
+
writeAtomicJson(path.join(sourceAsyncDir, "status.json"), sourceStatus);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1517
1554
|
|
|
1518
1555
|
const revivedId = result.details.asyncId ?? runId;
|
|
1519
1556
|
const revivedTarget = intercomBridge.active ? resolveSubagentIntercomTarget(revivedId, target.agent, 0) : undefined;
|
|
@@ -1843,6 +1880,19 @@ function getRequestedModeLabel(params: SubagentParamsLike): Details["mode"] {
|
|
|
1843
1880
|
return "single";
|
|
1844
1881
|
}
|
|
1845
1882
|
|
|
1883
|
+
function formatStatusTargetLabel(params: Pick<SubagentParamsLike, "dir" | "index" | "view">, targetRunId: string | undefined): string {
|
|
1884
|
+
let target: string;
|
|
1885
|
+
if (targetRunId) {
|
|
1886
|
+
target = `run ${targetRunId}`;
|
|
1887
|
+
} else if (params.dir) {
|
|
1888
|
+
target = `dir ${params.dir}`;
|
|
1889
|
+
} else {
|
|
1890
|
+
target = params.view === "transcript" ? "active run" : "active runs";
|
|
1891
|
+
}
|
|
1892
|
+
if (params.view !== "transcript") return `Status target: ${target}`;
|
|
1893
|
+
return `Transcript target: ${target}${params.index !== undefined ? ` · child ${params.index}` : ""}`;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1846
1896
|
interface AgentDefaultContextPolicy {
|
|
1847
1897
|
params: SubagentParamsLike;
|
|
1848
1898
|
contextForAgent(agentName: string): ContextMode;
|
|
@@ -3301,10 +3351,11 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3301
3351
|
);
|
|
3302
3352
|
if (errorResult) return errorResult;
|
|
3303
3353
|
|
|
3304
|
-
let
|
|
3354
|
+
let worktreeCleanupHandled = false;
|
|
3355
|
+
let pendingHandoff: Details["parallelHandoff"];
|
|
3305
3356
|
try {
|
|
3306
3357
|
if (worktreeSetup) {
|
|
3307
|
-
writePendingParallelHandoff({
|
|
3358
|
+
pendingHandoff = writePendingParallelHandoff({
|
|
3308
3359
|
manifestPath: parallelHandoffPath(artifactsDir, runId),
|
|
3309
3360
|
runId,
|
|
3310
3361
|
mode: "parallel",
|
|
@@ -3407,10 +3458,13 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3407
3458
|
updateForegroundNestedProjection(foregroundControl);
|
|
3408
3459
|
attachRootChildrenToSteps(runId, results, foregroundControl.nestedChildren);
|
|
3409
3460
|
}
|
|
3461
|
+
const detached = results.find((result) => result.detached);
|
|
3410
3462
|
let handoff: ReturnType<typeof finalizeParallelWorktreeHandoff> | undefined;
|
|
3411
3463
|
if (worktreeSetup) {
|
|
3412
|
-
|
|
3413
|
-
handoff =
|
|
3464
|
+
worktreeCleanupHandled = true;
|
|
3465
|
+
handoff = detached
|
|
3466
|
+
? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
|
|
3467
|
+
: finalizeParallelWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: effectiveCwd, tasks, results });
|
|
3414
3468
|
}
|
|
3415
3469
|
const interrupted = results.find((result) => result.interrupted);
|
|
3416
3470
|
const totalCost = sumResultsCost(results);
|
|
@@ -3433,11 +3487,10 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3433
3487
|
details,
|
|
3434
3488
|
};
|
|
3435
3489
|
}
|
|
3436
|
-
const detachedIndex = results.findIndex((result) => result.detached);
|
|
3437
|
-
const detached = detachedIndex >= 0 ? results[detachedIndex] : undefined;
|
|
3438
3490
|
if (detached) {
|
|
3491
|
+
const handoffSuffix = handoff?.suffix ? `\n\n${handoff.suffix}` : "";
|
|
3439
3492
|
return {
|
|
3440
|
-
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached
|
|
3493
|
+
content: [{ type: "text", text: `Parallel run detached for intercom coordination (${detached.agent}). Reply to the supervisor request first, then wait with subagent_wait({ id: "${runId}" }). Use subagent({ action: "status", id: "${runId}" }) to recover the result; do not resume or launch a replacement while it remains detached.${handoffSuffix}` }],
|
|
3441
3494
|
details,
|
|
3442
3495
|
};
|
|
3443
3496
|
}
|
|
@@ -3486,7 +3539,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
3486
3539
|
details,
|
|
3487
3540
|
};
|
|
3488
3541
|
} finally {
|
|
3489
|
-
if (worktreeSetup && !
|
|
3542
|
+
if (worktreeSetup && !worktreeCleanupHandled) cleanupWorktrees(worktreeSetup);
|
|
3490
3543
|
}
|
|
3491
3544
|
}
|
|
3492
3545
|
|
|
@@ -3873,6 +3926,8 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
3873
3926
|
const output = result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
|
|
3874
3927
|
? result.details.results[0].finalOutput
|
|
3875
3928
|
: receiptOutput;
|
|
3929
|
+
const detached = result.details.results.some((child) => child.detached);
|
|
3930
|
+
const ok = result.isError !== true && !detached;
|
|
3876
3931
|
const artifactPaths = new Set<string>();
|
|
3877
3932
|
if (result.details.asyncDir) artifactPaths.add(result.details.asyncDir);
|
|
3878
3933
|
if (result.details.parallelHandoff?.path) artifactPaths.add(result.details.parallelHandoff.path);
|
|
@@ -3884,16 +3939,70 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
|
|
|
3884
3939
|
const structured = result.details.results.map((child) => child.structuredOutput).filter((value) => value !== undefined);
|
|
3885
3940
|
return {
|
|
3886
3941
|
key,
|
|
3887
|
-
ok
|
|
3942
|
+
ok,
|
|
3888
3943
|
...(result.details.runId || result.details.asyncId ? { runId: result.details.runId ?? result.details.asyncId } : {}),
|
|
3889
3944
|
output,
|
|
3890
|
-
...(
|
|
3945
|
+
...(!ok ? { error: receiptOutput || output || "Child run failed." } : {}),
|
|
3891
3946
|
...(structured.length === 1 ? { structuredOutput: structured[0] } : structured.length > 1 ? { structuredOutput: structured } : {}),
|
|
3892
3947
|
artifactPaths: [...artifactPaths],
|
|
3893
3948
|
results: result.details.results,
|
|
3894
3949
|
};
|
|
3895
3950
|
}
|
|
3896
3951
|
|
|
3952
|
+
export function prepareWorkflowLaunchParams(
|
|
3953
|
+
workflowDefaults: SubagentParamsLike,
|
|
3954
|
+
childParams: Record<string, unknown>,
|
|
3955
|
+
parentWorkflowRunId: string,
|
|
3956
|
+
workflowKey: string,
|
|
3957
|
+
options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean } = {},
|
|
3958
|
+
): SubagentParamsLike {
|
|
3959
|
+
if (typeof childParams.resume === "string") {
|
|
3960
|
+
if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
|
|
3961
|
+
throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
|
|
3962
|
+
}
|
|
3963
|
+
const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
|
|
3964
|
+
const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
|
|
3965
|
+
const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
|
|
3966
|
+
return {
|
|
3967
|
+
action: "resume",
|
|
3968
|
+
id: childParams.resume.trim(),
|
|
3969
|
+
message: typeof childParams.task === "string" ? childParams.task.trim() : "",
|
|
3970
|
+
workflowParentRunId: parentWorkflowRunId,
|
|
3971
|
+
workflowKey,
|
|
3972
|
+
...(options.missionDetached ? { mission: false } : {}),
|
|
3973
|
+
...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
|
|
3974
|
+
...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
|
|
3975
|
+
...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
const launchParams = {
|
|
3979
|
+
...workflowDefaults,
|
|
3980
|
+
...childParams,
|
|
3981
|
+
...(options.missionDetached ? { mission: false } : {}),
|
|
3982
|
+
workflowParentRunId: parentWorkflowRunId,
|
|
3983
|
+
workflowKey,
|
|
3984
|
+
...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
|
|
3985
|
+
} as SubagentParamsLike;
|
|
3986
|
+
const normalizedGate = normalizeGateParams(launchParams);
|
|
3987
|
+
if (!normalizedGate.ok) throw new Error(normalizedGate.error);
|
|
3988
|
+
return prepareWorkflowChildParams(normalizedGate.params);
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3991
|
+
type GateParamsNormalizationResult =
|
|
3992
|
+
| { ok: true; params: SubagentParamsLike }
|
|
3993
|
+
| { ok: false; error: string };
|
|
3994
|
+
|
|
3995
|
+
function normalizeGateParams(params: SubagentParamsLike): GateParamsNormalizationResult {
|
|
3996
|
+
if (params.gate !== undefined && params.action === "resume") {
|
|
3997
|
+
return { ok: false, error: "gate is not supported with action='resume'; resume uses the retained child contract." };
|
|
3998
|
+
}
|
|
3999
|
+
const normalized = normalizeGateAcceptance(params.gate, params.acceptance);
|
|
4000
|
+
if (!normalized.ok) return { ok: false, error: normalized.error };
|
|
4001
|
+
if (params.gate === undefined) return { ok: true, params };
|
|
4002
|
+
const { gate: _gate, ...rest } = params;
|
|
4003
|
+
return { ok: true, params: { ...rest, ...(normalized.acceptance !== undefined ? { acceptance: normalized.acceptance } : {}) } };
|
|
4004
|
+
}
|
|
4005
|
+
|
|
3897
4006
|
function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
|
|
3898
4007
|
if (params.worktree !== true || !params.agent) return params;
|
|
3899
4008
|
const {
|
|
@@ -3949,21 +4058,6 @@ function workflowChatProgressUpdate(
|
|
|
3949
4058
|
};
|
|
3950
4059
|
}
|
|
3951
4060
|
|
|
3952
|
-
function omitExecutionModeActionAlias(params: SubagentParamsLike): SubagentParamsLike {
|
|
3953
|
-
const action = params.action?.toLowerCase();
|
|
3954
|
-
if (action === "single" && (params.agent !== undefined || params.task !== undefined)) {
|
|
3955
|
-
const rest = { ...params };
|
|
3956
|
-
delete rest.action;
|
|
3957
|
-
return rest;
|
|
3958
|
-
}
|
|
3959
|
-
if ((action === "parallel" || action === "tasks") && (params.tasks?.length ?? 0) > 0) {
|
|
3960
|
-
const rest = { ...params };
|
|
3961
|
-
delete rest.action;
|
|
3962
|
-
return rest;
|
|
3963
|
-
}
|
|
3964
|
-
return params;
|
|
3965
|
-
}
|
|
3966
|
-
|
|
3967
4061
|
function createScheduledOwnerState(source: SubagentState, ownerSessionId: string, ctx: ExtensionContext): SubagentState {
|
|
3968
4062
|
const ownerSpawns = source.subagentSpawns?.sessionId === ownerSessionId
|
|
3969
4063
|
? {
|
|
@@ -4002,6 +4096,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4002
4096
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
4003
4097
|
ctx: ExtensionContext,
|
|
4004
4098
|
) => Promise<AgentToolResult<Details>>;
|
|
4099
|
+
/** Public/model-facing execution boundary. Internal direct launch primitives use execute or executeDelegated. */
|
|
4100
|
+
executePublic: (
|
|
4101
|
+
id: string,
|
|
4102
|
+
params: SubagentParamsLike,
|
|
4103
|
+
signal: AbortSignal,
|
|
4104
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
4105
|
+
ctx: ExtensionContext,
|
|
4106
|
+
) => Promise<AgentToolResult<Details>>;
|
|
4005
4107
|
/**
|
|
4006
4108
|
* Correlated extension-to-extension delegation owns its request IDs and
|
|
4007
4109
|
* cancellation controllers, so independent requests may execute concurrently.
|
|
@@ -4040,24 +4142,69 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4040
4142
|
deps.state.foregroundRuns ??= new Map();
|
|
4041
4143
|
deps.state.foregroundControls ??= new Map();
|
|
4042
4144
|
deps.state.lastForegroundControlId ??= null;
|
|
4043
|
-
const
|
|
4044
|
-
if (
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
if (requestParams.clarify === true) {
|
|
4050
|
-
return { content: [{ type: "text", text: "workflowScript does not support clarify UI." }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4051
|
-
}
|
|
4145
|
+
const normalizedGate = normalizeGateParams(params);
|
|
4146
|
+
if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
|
|
4147
|
+
const requestParams = normalizedGate.params;
|
|
4148
|
+
const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
|
|
4149
|
+
if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
|
|
4150
|
+
const parentCwd = ctx.cwd;
|
|
4052
4151
|
const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
|
|
4053
4152
|
const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
|
|
4054
4153
|
if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
|
|
4055
|
-
const workflowCwd = resolveRequestedCwd(
|
|
4056
|
-
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd
|
|
4154
|
+
const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
|
|
4155
|
+
const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
|
|
4057
4156
|
if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4058
4157
|
const chatProgress = chatProgressResult.projection!;
|
|
4158
|
+
const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
|
|
4159
|
+
const autoMission = !explicitMission;
|
|
4160
|
+
const workflowPreview = autoMission ? previewSimpleWorkflowRun(requestParams.workflowScript) : undefined;
|
|
4161
|
+
const previewTask = workflowPreview?.task?.trim() || undefined;
|
|
4162
|
+
const previewAgent = workflowPreview?.agent?.trim() || undefined;
|
|
4163
|
+
const scriptFirstLine = requestParams.workflowScript.split(/\r?\n/).map((line) => line.trim()).find(Boolean) || "Workflow";
|
|
4164
|
+
const boundedScriptPreview = scriptFirstLine.length > 100 ? `${scriptFirstLine.slice(0, 97)}...` : scriptFirstLine;
|
|
4165
|
+
const derivedObjective = previewTask || (previewAgent ? `Workflow: ${previewAgent}` : boundedScriptPreview);
|
|
4166
|
+
let missionBinding: MissionLaunchBinding | undefined;
|
|
4167
|
+
let missionWarning: string | undefined;
|
|
4168
|
+
try {
|
|
4169
|
+
missionBinding = prepareMissionLaunch({
|
|
4170
|
+
params: autoMission ? { ...requestParams, task: derivedObjective } : requestParams,
|
|
4171
|
+
projectRoot: workflowCwd,
|
|
4172
|
+
...(deps.config.missions ? { config: deps.config.missions } : {}),
|
|
4173
|
+
ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
|
|
4174
|
+
});
|
|
4175
|
+
} catch (error) {
|
|
4176
|
+
if (explicitMission) return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
|
|
4177
|
+
missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
|
|
4178
|
+
}
|
|
4179
|
+
let shouldPatchMissionObjective = autoMission && previewTask === undefined && missionBinding !== undefined;
|
|
4180
|
+
const patchMissionObjective = (task: unknown): void => {
|
|
4181
|
+
if (!shouldPatchMissionObjective || !missionBinding || typeof task !== "string" || !task.trim()) return;
|
|
4182
|
+
shouldPatchMissionObjective = false;
|
|
4183
|
+
const objective = task.trim();
|
|
4184
|
+
const firstLine = objective.split(/\r?\n/, 1)[0]?.trim() || objective;
|
|
4185
|
+
const title = firstLine.length > 100 ? `${firstLine.slice(0, 97)}...` : firstLine;
|
|
4186
|
+
try {
|
|
4187
|
+
updateMission(missionBinding.location, missionBinding.missionId, { title, objective });
|
|
4188
|
+
} catch (error) {
|
|
4189
|
+
console.warn(`[pi-subagents] Failed to update automatic mission objective: ${error instanceof Error ? error.message : String(error)}`);
|
|
4190
|
+
}
|
|
4191
|
+
};
|
|
4192
|
+
const detachWorkflowChildMissions = autoMission || missionBinding !== undefined || requestParams.mission === false;
|
|
4193
|
+
const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
|
|
4194
|
+
const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
|
|
4195
|
+
if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
|
|
4196
|
+
try {
|
|
4197
|
+
return attachMissionToLaunchResult({ binding: missionBinding, result });
|
|
4198
|
+
} catch (error) {
|
|
4199
|
+
const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
|
|
4200
|
+
return explicitMission
|
|
4201
|
+
? { ...result, isError: true, content: [...result.content, { type: "text", text: warning }], details: { ...result.details, missionWarning: warning } }
|
|
4202
|
+
: { ...result, details: { ...result.details, missionWarning: warning } };
|
|
4203
|
+
}
|
|
4204
|
+
};
|
|
4059
4205
|
if (requestParams.async !== false) {
|
|
4060
|
-
const
|
|
4206
|
+
const toolCallId = _id;
|
|
4207
|
+
const workflowRunId = randomUUID();
|
|
4061
4208
|
const asyncDir = path.join(DIRS.async, workflowRunId);
|
|
4062
4209
|
const resultPath = path.join(DIRS.results, `${workflowRunId}.json`);
|
|
4063
4210
|
const statusPath = path.join(asyncDir, "status.json");
|
|
@@ -4071,13 +4218,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4071
4218
|
deps.state.workflowControllers.set(workflowRunId, controller);
|
|
4072
4219
|
let status: AsyncStatus = {
|
|
4073
4220
|
runId: workflowRunId,
|
|
4221
|
+
toolCallId,
|
|
4074
4222
|
sessionId: currentSessionId ?? undefined,
|
|
4075
4223
|
mode: "workflow",
|
|
4076
4224
|
state: "running",
|
|
4077
4225
|
startedAt,
|
|
4078
4226
|
lastUpdate: startedAt,
|
|
4079
4227
|
...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
|
|
4080
|
-
cwd:
|
|
4228
|
+
cwd: workflowCwd,
|
|
4081
4229
|
pid: process.pid,
|
|
4082
4230
|
steps: [],
|
|
4083
4231
|
workflow: { trace: [], emits: [], console: [] },
|
|
@@ -4100,7 +4248,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4100
4248
|
job.workflow = status.workflow;
|
|
4101
4249
|
}
|
|
4102
4250
|
};
|
|
4103
|
-
const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd:
|
|
4251
|
+
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 };
|
|
4104
4252
|
deps.state.asyncJobs.set(workflowRunId, workflowJob);
|
|
4105
4253
|
deps.state.fleetJobs ??= new Map();
|
|
4106
4254
|
deps.state.fleetJobs.set(workflowRunId, workflowJob);
|
|
@@ -4109,7 +4257,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4109
4257
|
const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
|
|
4110
4258
|
void Promise.resolve().then(async () => {
|
|
4111
4259
|
const workflowResults: SingleResult[] = [];
|
|
4112
|
-
const { action: _action, agent: _agent, task: _task, tasks: _tasks, chain: _chain, concurrency: _concurrency, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, ...workflowChildDefaults } = workflowRequest;
|
|
4260
|
+
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;
|
|
4113
4261
|
const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
|
|
4114
4262
|
status.workflow = { ...(status.workflow ?? { emits: [], console: [] }), trace };
|
|
4115
4263
|
for (const entry of trace.filter((candidate) => candidate.operation === "run")) {
|
|
@@ -4134,6 +4282,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4134
4282
|
script: workflowScript,
|
|
4135
4283
|
timeoutMs: timeout,
|
|
4136
4284
|
signal: controller.signal,
|
|
4285
|
+
...(workflowState ? { state: workflowState } : {}),
|
|
4137
4286
|
onTrace: updateTrace,
|
|
4138
4287
|
onEmit: (emits) => {
|
|
4139
4288
|
// Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
|
|
@@ -4145,7 +4294,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4145
4294
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4146
4295
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4147
4296
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4148
|
-
|
|
4297
|
+
patchMissionObjective(childParams.task);
|
|
4298
|
+
const childRequest = prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: detachWorkflowChildMissions });
|
|
4149
4299
|
const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
|
|
4150
4300
|
workflowResults.push(...result.details.results);
|
|
4151
4301
|
const child = workflowChildResult(key, result);
|
|
@@ -4164,24 +4314,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4164
4314
|
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) };
|
|
4165
4315
|
persist();
|
|
4166
4316
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
|
|
4167
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ agent: child.key, 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:
|
|
4317
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: true, state: "complete", summary, output: summary, results: workflow.children.map((child) => ({ agent: child.key, 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 });
|
|
4168
4318
|
} catch (error) {
|
|
4169
4319
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4170
4320
|
const stopped = controller.signal.aborted;
|
|
4171
4321
|
status = compactOptional<AsyncStatus>({ ...status, state: stopped ? "stopped" : "failed", stopped: stopped || undefined, error: error instanceof Error ? error.message : String(error), endedAt: Date.now(), workflow: { trace: partial.trace, emits: partial.emits, console: partial.console } });
|
|
4172
4322
|
persist();
|
|
4173
4323
|
appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
|
|
4174
|
-
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ agent: child.key, 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:
|
|
4324
|
+
writeAtomicJson(resultPath, { id: workflowRunId, runId: workflowRunId, toolCallId, agent: "workflow", mode: "workflow", success: false, state: status.state, summary: status.error, error: status.error, stopped: status.stopped, results: partial.children.map((child) => ({ agent: child.key, 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 });
|
|
4175
4325
|
} finally {
|
|
4176
4326
|
deps.state.workflowControllers?.delete(workflowRunId);
|
|
4177
4327
|
}
|
|
4178
4328
|
});
|
|
4179
|
-
return {
|
|
4329
|
+
return attachWorkflowMission({
|
|
4180
4330
|
content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
|
|
4181
|
-
details: { mode: "workflow", runId: workflowRunId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4182
|
-
};
|
|
4331
|
+
details: { mode: "workflow", runId: workflowRunId, toolCallId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
|
|
4332
|
+
});
|
|
4183
4333
|
}
|
|
4184
|
-
const { workflowScript: _workflowScript, action: _action, agent: _agent, task: _task, tasks: _tasks, chain: _chain, concurrency: _concurrency, async: _async, foregroundOnly: _foregroundOnly, clarify: _clarify, timeoutMs: _timeoutMs, maxRuntimeMs: _maxRuntimeMs, usageBudget: _usageBudget, chatProgress: _chatProgress, ...workflowChildDefaults } = requestParams;
|
|
4334
|
+
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;
|
|
4185
4335
|
const workflowResults: SingleResult[] = [];
|
|
4186
4336
|
let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
|
|
4187
4337
|
const sendWorkflowProgress = () => {
|
|
@@ -4193,6 +4343,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4193
4343
|
script: requestParams.workflowScript,
|
|
4194
4344
|
timeoutMs: timeout,
|
|
4195
4345
|
signal,
|
|
4346
|
+
...(workflowState ? { state: workflowState } : {}),
|
|
4196
4347
|
onTrace: (trace) => {
|
|
4197
4348
|
liveWorkflow = { ...liveWorkflow, trace };
|
|
4198
4349
|
sendWorkflowProgress();
|
|
@@ -4205,7 +4356,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4205
4356
|
if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
|
|
4206
4357
|
const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
|
|
4207
4358
|
if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
|
|
4208
|
-
|
|
4359
|
+
patchMissionObjective(childParams.task);
|
|
4360
|
+
const childRequest = prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: detachWorkflowChildMissions, suppressRoutineResultIntercom: chatProgress.mode === "live-card" });
|
|
4209
4361
|
const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
|
|
4210
4362
|
workflowResults.push(...result.details.results);
|
|
4211
4363
|
return workflowChildResult(key, result);
|
|
@@ -4217,10 +4369,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4217
4369
|
if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4218
4370
|
if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4219
4371
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4220
|
-
return {
|
|
4372
|
+
return attachWorkflowMission({
|
|
4221
4373
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4222
4374
|
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 }),
|
|
4223
|
-
};
|
|
4375
|
+
});
|
|
4224
4376
|
} catch (error) {
|
|
4225
4377
|
const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
|
|
4226
4378
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -4229,11 +4381,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4229
4381
|
if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
|
|
4230
4382
|
if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
|
|
4231
4383
|
if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
|
|
4232
|
-
return {
|
|
4384
|
+
return attachWorkflowMission({
|
|
4233
4385
|
content: [{ type: "text", text: sections.join("\n\n") }],
|
|
4234
4386
|
isError: true,
|
|
4235
4387
|
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 }),
|
|
4236
|
-
};
|
|
4388
|
+
});
|
|
4237
4389
|
}
|
|
4238
4390
|
}
|
|
4239
4391
|
const directParams = prepareWorkflowChildParams(requestParams);
|
|
@@ -4337,6 +4489,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4337
4489
|
}
|
|
4338
4490
|
return handleWatchdogToolAction(action, paramsWithResolvedCwd, ctx, deps.watchdog);
|
|
4339
4491
|
}
|
|
4492
|
+
if (action === "refine" || action === "refine.show" || action === "refine.rollback") {
|
|
4493
|
+
if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
|
|
4494
|
+
return {
|
|
4495
|
+
content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
|
|
4496
|
+
isError: true,
|
|
4497
|
+
details: { mode: "management", results: [] },
|
|
4498
|
+
};
|
|
4499
|
+
}
|
|
4500
|
+
return handleRefinementAction(action, paramsWithResolvedCwd, {
|
|
4501
|
+
cwd: requestCwd,
|
|
4502
|
+
state: deps.state,
|
|
4503
|
+
signal,
|
|
4504
|
+
launchProposalChild: (task, outputSchema, proposalSignal) => execute(randomUUID(), {
|
|
4505
|
+
agent: "reviewer",
|
|
4506
|
+
task,
|
|
4507
|
+
context: "fresh",
|
|
4508
|
+
async: false,
|
|
4509
|
+
artifacts: false,
|
|
4510
|
+
outputSchema,
|
|
4511
|
+
toolBudget: { hard: 1, block: ["write", "edit", "bash"] },
|
|
4512
|
+
}, proposalSignal, undefined, ctx, true),
|
|
4513
|
+
});
|
|
4514
|
+
}
|
|
4340
4515
|
if (action === "grant-spawn-budget") {
|
|
4341
4516
|
if (deps.allowMutatingManagementActions === false || !ctx.hasUI) {
|
|
4342
4517
|
return {
|
|
@@ -4409,6 +4584,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4409
4584
|
details: { mode: "management", results: [], spawnBudget: granted.snapshot },
|
|
4410
4585
|
};
|
|
4411
4586
|
}
|
|
4587
|
+
if (action === "children.list") {
|
|
4588
|
+
deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4589
|
+
const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
|
|
4590
|
+
return {
|
|
4591
|
+
content: [{ type: "text", text: formatRetainedChildren(children) }],
|
|
4592
|
+
details: { mode: "management", results: [] },
|
|
4593
|
+
};
|
|
4594
|
+
}
|
|
4412
4595
|
if (action === "doctor") {
|
|
4413
4596
|
let currentSessionFile: string | null = null;
|
|
4414
4597
|
let currentSessionId = deps.state.currentSessionId;
|
|
@@ -4447,13 +4630,18 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4447
4630
|
}
|
|
4448
4631
|
if (action === "status") {
|
|
4449
4632
|
if (!preserveActiveSession) deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
4450
|
-
const withBudget = (result: AgentToolResult<Details>) => withSpawnBudgetStatus(
|
|
4451
|
-
result,
|
|
4452
|
-
deps.state,
|
|
4453
|
-
deps.config,
|
|
4454
|
-
deps.state.currentSessionId,
|
|
4455
|
-
);
|
|
4456
4633
|
const targetRunId = paramsWithResolvedCwd.id ?? paramsWithResolvedCwd.runId;
|
|
4634
|
+
const hasDirectoryTarget = Boolean(paramsWithResolvedCwd.dir);
|
|
4635
|
+
const targetLabel = formatStatusTargetLabel(paramsWithResolvedCwd, targetRunId);
|
|
4636
|
+
const withBudget = (result: AgentToolResult<Details>) => {
|
|
4637
|
+
const budgeted = withSpawnBudgetStatus(result, deps.state, deps.config, deps.state.currentSessionId);
|
|
4638
|
+
return {
|
|
4639
|
+
...budgeted,
|
|
4640
|
+
content: budgeted.content.map((item, index) => index === 0 && item.type === "text"
|
|
4641
|
+
? { ...item, text: `${targetLabel}\n${item.text}` }
|
|
4642
|
+
: item),
|
|
4643
|
+
};
|
|
4644
|
+
};
|
|
4457
4645
|
const nestedScope = nestedResolutionScopeForExecutor(deps);
|
|
4458
4646
|
const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
|
|
4459
4647
|
if (paramsWithResolvedCwd.view === "fleet") {
|
|
@@ -4478,7 +4666,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4478
4666
|
const message = error instanceof Error ? error.message : String(error);
|
|
4479
4667
|
return withBudget({ content: [{ type: "text", text: message }], isError: true, details: { mode: "management", results: [] } });
|
|
4480
4668
|
}
|
|
4481
|
-
} else {
|
|
4669
|
+
} else if (!hasDirectoryTarget) {
|
|
4482
4670
|
const foreground = getForegroundControl(deps.state, undefined);
|
|
4483
4671
|
if (foreground && paramsWithResolvedCwd.view !== "transcript") return withBudget(foregroundStatusResult(foreground));
|
|
4484
4672
|
if (foreground && paramsWithResolvedCwd.view === "transcript") {
|
|
@@ -4565,6 +4753,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4565
4753
|
state: deps.state,
|
|
4566
4754
|
runId,
|
|
4567
4755
|
message,
|
|
4756
|
+
mode: paramsWithResolvedCwd.mode,
|
|
4568
4757
|
index: paramsWithResolvedCwd.index,
|
|
4569
4758
|
kill: deps.kill,
|
|
4570
4759
|
location,
|
|
@@ -4590,7 +4779,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4590
4779
|
const text = error instanceof Error ? error.message : String(error);
|
|
4591
4780
|
return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
|
|
4592
4781
|
}
|
|
4593
|
-
if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, index: paramsWithResolvedCwd.index, signal }));
|
|
4782
|
+
if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal }));
|
|
4594
4783
|
if (resolved?.kind === "foreground") return { content: [{ type: "text", text: "action='steer' currently supports live async Pi child sessions only; use action='interrupt' or action='resume' for foreground runs." }], isError: true, details: { mode: "management", results: [] } };
|
|
4595
4784
|
if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
|
|
4596
4785
|
if (resolved.location.asyncDir) {
|
|
@@ -4601,6 +4790,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
4601
4790
|
state: deps.state,
|
|
4602
4791
|
runId: resolved.id,
|
|
4603
4792
|
message,
|
|
4793
|
+
mode: paramsWithResolvedCwd.mode,
|
|
4604
4794
|
index: paramsWithResolvedCwd.index,
|
|
4605
4795
|
kill: deps.kill,
|
|
4606
4796
|
location: resolved.location,
|
|
@@ -5204,8 +5394,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5204
5394
|
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
5205
5395
|
ctx: ExtensionContext,
|
|
5206
5396
|
): Promise<AgentToolResult<Details>> => {
|
|
5207
|
-
const
|
|
5208
|
-
|
|
5397
|
+
const normalizedAction = typeof params.action === "string" ? params.action.trim() : params.action;
|
|
5398
|
+
const requestParams = normalizedAction ? { ...params, action: normalizedAction } : params;
|
|
5399
|
+
if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx);
|
|
5209
5400
|
const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
|
|
5210
5401
|
const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
|
|
5211
5402
|
const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
|
|
@@ -5219,6 +5410,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5219
5410
|
}
|
|
5220
5411
|
};
|
|
5221
5412
|
|
|
5413
|
+
const executePublic = (
|
|
5414
|
+
id: string,
|
|
5415
|
+
params: SubagentParamsLike,
|
|
5416
|
+
signal: AbortSignal,
|
|
5417
|
+
onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
|
|
5418
|
+
ctx: ExtensionContext,
|
|
5419
|
+
): Promise<AgentToolResult<Details>> => {
|
|
5420
|
+
const normalized = normalizePublicSubagentExecution(params);
|
|
5421
|
+
if (!normalized.ok) {
|
|
5422
|
+
return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
|
|
5423
|
+
}
|
|
5424
|
+
return executeWithSingleDispatchGuard(id, normalized.params, signal, onUpdate, ctx);
|
|
5425
|
+
};
|
|
5426
|
+
|
|
5222
5427
|
const executeDelegated = async (
|
|
5223
5428
|
id: string,
|
|
5224
5429
|
params: SubagentParamsLike,
|
|
@@ -5258,5 +5463,5 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
|
|
|
5258
5463
|
return ownerExecutor.execute(id, params, signal, undefined, ctx);
|
|
5259
5464
|
};
|
|
5260
5465
|
|
|
5261
|
-
return { execute: executeWithSingleDispatchGuard, executeDelegated, executeScheduled };
|
|
5466
|
+
return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };
|
|
5262
5467
|
}
|