pi-subagents 0.42.0 → 0.43.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +3 -5
  3. package/package.json +1 -1
  4. package/skills/pi-subagents/SKILL.md +2 -2
  5. package/skills/pi-subagents/references/constraints-and-recipes.md +35 -32
  6. package/skills/pi-subagents/references/execution-controls.md +27 -43
  7. package/skills/pi-subagents/references/management-authoring-rpc.md +20 -3
  8. package/skills/pi-subagents/references/prompting-and-roles.md +16 -49
  9. package/src/agents/agent-refinements.ts +624 -0
  10. package/src/agents/agents.ts +0 -2
  11. package/src/agents/proactive-skills.ts +1 -1
  12. package/src/api/delegation.ts +1 -2
  13. package/src/extension/control-notices.ts +2 -2
  14. package/src/extension/fanout-child.ts +46 -22
  15. package/src/extension/index.ts +29 -12
  16. package/src/extension/public-execution.ts +71 -0
  17. package/src/extension/rpc.ts +7 -8
  18. package/src/extension/schemas.ts +17 -17
  19. package/src/extension/tool-description.ts +14 -14
  20. package/src/missions/actions.ts +47 -11
  21. package/src/missions/goal-driver.ts +162 -0
  22. package/src/missions/lifecycle.ts +44 -12
  23. package/src/missions/store.ts +68 -3
  24. package/src/missions/types.ts +25 -3
  25. package/src/missions/workflow-state.ts +77 -0
  26. package/src/profiles/profiles.ts +1 -3
  27. package/src/runs/background/async-execution.ts +3 -0
  28. package/src/runs/background/async-job-tracker.ts +2 -17
  29. package/src/runs/background/control-channel.ts +50 -6
  30. package/src/runs/background/retained-children.ts +68 -0
  31. package/src/runs/background/scheduled-runs.ts +17 -13
  32. package/src/runs/background/steering.ts +7 -5
  33. package/src/runs/background/subagent-runner.ts +10 -4
  34. package/src/runs/foreground/async-steering-action.ts +36 -10
  35. package/src/runs/foreground/chain-clarify.ts +13 -16
  36. package/src/runs/foreground/execution.ts +4 -0
  37. package/src/runs/foreground/subagent-executor.ts +200 -59
  38. package/src/runs/shared/acceptance.ts +154 -9
  39. package/src/runs/shared/subagent-prompt-runtime.ts +94 -25
  40. package/src/shared/types.ts +17 -3
  41. package/src/slash/delegation-adapters.ts +0 -13
  42. package/src/slash/prompt-template-bridge.ts +14 -12
  43. package/src/slash/prompt-workflows.ts +10 -4
  44. package/src/slash/slash-bridge.ts +8 -6
  45. package/src/slash/slash-commands.ts +27 -12
  46. package/src/slash/slash-live-state.ts +4 -2
  47. package/src/tui/fleet-status.ts +8 -4
  48. package/src/tui/fleet.ts +15 -5
  49. package/src/tui/render.ts +13 -31
  50. package/src/workflows/chat-progress.ts +2 -2
  51. package/src/workflows/scripted-workflow.ts +97 -10
  52. package/agents/context-builder.md +0 -46
  53. package/agents/planner.md +0 -56
  54. package/prompts/parallel-context-build.md +0 -55
  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,6 +91,7 @@ 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 { createMissionWorkflowState } from "../../missions/workflow-state.ts";
91
95
  import { resolveAuthorityDecision } from "../../policy/authority.ts";
92
96
  import { handleHerdrInspectorAction, HERDR_INSPECTOR_ACTIONS } from "../../inspectors/herdr/actions.ts";
93
97
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.ts";
@@ -144,7 +148,7 @@ import {
144
148
  wrapForkTask,
145
149
  } from "../../shared/types.ts";
146
150
 
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"]);
151
+ 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
152
 
149
153
  type UndefinedOmitted<T extends object> = {
150
154
  [K in keyof T as undefined extends T[K] ? never : K]: T[K];
@@ -209,10 +213,13 @@ export interface SubagentParamsLike {
209
213
  type?: string;
210
214
  agent?: string;
211
215
  task?: string;
216
+ /** Retained async child run id. Valid only on workflow runs.run items. */
217
+ resume?: string;
212
218
  message?: string;
213
219
  steeringRecovery?: boolean;
220
+ mode?: SteerDeliveryMode;
214
221
  workflowScript?: string;
215
- chatProgress?: "auto" | "off" | "terminal" | "milestones" | "live-card";
222
+ chatProgress?: "auto" | "off" | "live-card";
216
223
  step?: ChainStep;
217
224
  /** Internal workflow ownership metadata; not part of the public schema. */
218
225
  workflowParentRunId?: string;
@@ -253,6 +260,7 @@ export interface SubagentParamsLike {
253
260
  agentScope?: unknown;
254
261
  chainDir?: string;
255
262
  acceptance?: AcceptanceInput;
263
+ gate?: string;
256
264
  agentContract?: AgentContract;
257
265
  schedule?: string;
258
266
  scheduleName?: string;
@@ -1160,7 +1168,7 @@ function directNestedAsyncInterrupt(target: ResolvedSubagentRunId & { kind: "nes
1160
1168
  }
1161
1169
  }
1162
1170
 
1163
- async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
1171
+ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; mode?: SteerDeliveryMode; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details> | undefined> {
1164
1172
  const run = input.target.match.run;
1165
1173
  const asyncDir = resolveNestedAsyncDir(input.target.match.rootRunId, run);
1166
1174
  if (!asyncDir) return undefined;
@@ -1182,6 +1190,7 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
1182
1190
  try {
1183
1191
  requestAsyncSteer(asyncDir, {
1184
1192
  message: input.message,
1193
+ mode: input.mode,
1185
1194
  ...(effectiveTargetIndex !== undefined ? { targetIndex: effectiveTargetIndex } : { targetIndexes }),
1186
1195
  source: "nested-steer",
1187
1196
  id: requestId,
@@ -1191,12 +1200,12 @@ async function directNestedAsyncSteer(input: { target: ResolvedSubagentRunId & {
1191
1200
  }
1192
1201
  const targets = targetIndexes.map((index) => ({ index, state: steps[index]?.status === "pending" ? "scheduled" as const : "pending" as const }));
1193
1202
  if (targets.every((target) => target.state === "scheduled")) {
1194
- const scheduled = { requestId, state: "scheduled" as const, sourceRunId: run.id, targets };
1203
+ const scheduled = { requestId, state: "scheduled" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
1195
1204
  return { content: [{ type: "text", text: `Steering scheduled for nested async run ${run.id} (request ${requestId}).` }], details: { mode: "management", results: [], steering: scheduled } };
1196
1205
  }
1197
1206
  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 === "delivered" ? "delivered" : result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : "pending";
1207
+ const result = waited ?? { requestId, state: "pending" as const, deliveryStatus: "queued" as const, sourceRunId: run.id, targets };
1208
+ const stateText = result.state === "failed" ? "failed" : result.state === "partial" ? "partial" : result.deliveryStatus === "queued" ? "queued" : result.state === "delivered" ? "delivered" : "pending";
1200
1209
  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
1210
  }
1202
1211
 
@@ -1219,7 +1228,7 @@ async function resumeLiveNestedRun(input: { target: ResolvedSubagentRunId & { ki
1219
1228
  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
1229
  }
1221
1230
 
1222
- async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
1231
+ async function steerNestedRun(input: { target: ResolvedSubagentRunId & { kind: "nested" }; message: string; mode?: SteerDeliveryMode; index?: number; signal?: AbortSignal }): Promise<AgentToolResult<Details>> {
1223
1232
  const run = input.target.match.run;
1224
1233
  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
1234
  const direct = await directNestedAsyncSteer(input);
@@ -1449,6 +1458,9 @@ async function resumeAsyncRun(input: {
1449
1458
  return { content: [{ type: "text", text: formatAsyncStartedMessage(lines.join("\n"), input.ctx.hasUI) }], details: result.details };
1450
1459
  }
1451
1460
 
1461
+ const sourceAsyncDir = target.source === "async" ? target.asyncDir : undefined;
1462
+ const queuedBriefs = sourceAsyncDir ? readRevivalBriefs(sourceAsyncDir) : [];
1463
+ const effectiveFollowUp = [...queuedBriefs.map(({ request }) => request.message), followUp].filter(Boolean).join("\n\n");
1452
1464
  const revivalSessionFile = target.sessionFile;
1453
1465
  if (!revivalSessionFile) {
1454
1466
  return { content: [{ type: "text", text: `Async child '${target.runId}' has no persisted session file to resume.` }], isError: true, details: { mode: "management", results: [] } };
@@ -1461,8 +1473,8 @@ async function resumeAsyncRun(input: {
1461
1473
  const parentModel = input.parentModel;
1462
1474
  const result = executeAsyncSingle(runId, compactOptional<Parameters<typeof executeAsyncSingle>[1]>({
1463
1475
  agent: target.agent,
1464
- task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], followUp),
1465
- goal: followUp,
1476
+ task: buildRevivedAsyncTask(target as Parameters<typeof buildRevivedAsyncTask>[0], effectiveFollowUp),
1477
+ goal: effectiveFollowUp,
1466
1478
  agentConfig: recoveryAgentConfig,
1467
1479
  ctx: compactOptional<Parameters<typeof executeAsyncSingle>[1]["ctx"]>({
1468
1480
  pi: input.deps.pi,
@@ -1512,8 +1524,18 @@ async function resumeAsyncRun(input: {
1512
1524
  ...(input.params.turnBudget !== undefined ? { turnBudget: input.params.turnBudget } : {}),
1513
1525
  ...(input.params.toolBudget !== undefined ? { toolBudget: input.params.toolBudget } : {}),
1514
1526
  capabilityCeiling: intersectSubagentCapabilityCeilings("capabilityCeiling" in target ? target.capabilityCeiling : undefined, recoveryDescriptor?.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(input.deps.state.currentSessionId)),
1527
+ parentWorkflowRunId: input.params.workflowParentRunId,
1528
+ workflowKey: input.params.workflowKey,
1515
1529
  }));
1516
1530
  if (result.isError) return result;
1531
+ for (const brief of queuedBriefs) fs.rmSync(brief.path, { force: true });
1532
+ if (queuedBriefs.length > 0 && sourceAsyncDir) {
1533
+ const sourceStatus = readStatus(sourceAsyncDir);
1534
+ if (sourceStatus?.steering) {
1535
+ for (const brief of queuedBriefs) updateSteeringTarget(sourceStatus.steering, brief.request.id, target.index, "delivered", Date.now());
1536
+ writeAtomicJson(path.join(sourceAsyncDir, "status.json"), sourceStatus);
1537
+ }
1538
+ }
1517
1539
 
1518
1540
  const revivedId = result.details.asyncId ?? runId;
1519
1541
  const revivedTarget = intercomBridge.active ? resolveSubagentIntercomTarget(revivedId, target.agent, 0) : undefined;
@@ -3894,6 +3916,60 @@ function workflowChildResult(key: string, result: AgentToolResult<Details>): Wor
3894
3916
  };
3895
3917
  }
3896
3918
 
3919
+ export function prepareWorkflowLaunchParams(
3920
+ workflowDefaults: SubagentParamsLike,
3921
+ childParams: Record<string, unknown>,
3922
+ parentWorkflowRunId: string,
3923
+ workflowKey: string,
3924
+ options: { missionDetached?: boolean; suppressRoutineResultIntercom?: boolean } = {},
3925
+ ): SubagentParamsLike {
3926
+ if (typeof childParams.resume === "string") {
3927
+ if (childParams.gate !== undefined || workflowDefaults.gate !== undefined) {
3928
+ throw new Error("gate is not supported with retained resume; resume uses the retained child contract.");
3929
+ }
3930
+ const timeoutMs = childParams.timeoutMs ?? childParams.maxRuntimeMs ?? workflowDefaults.timeoutMs ?? workflowDefaults.maxRuntimeMs;
3931
+ const turnBudget = childParams.turnBudget ?? workflowDefaults.turnBudget;
3932
+ const toolBudget = childParams.toolBudget ?? workflowDefaults.toolBudget;
3933
+ return {
3934
+ action: "resume",
3935
+ id: childParams.resume.trim(),
3936
+ message: typeof childParams.task === "string" ? childParams.task.trim() : "",
3937
+ workflowParentRunId: parentWorkflowRunId,
3938
+ workflowKey,
3939
+ ...(options.missionDetached ? { mission: false } : {}),
3940
+ ...(timeoutMs !== undefined ? { timeoutMs: timeoutMs as number } : {}),
3941
+ ...(turnBudget !== undefined ? { turnBudget: turnBudget as TurnBudgetConfig } : {}),
3942
+ ...(toolBudget !== undefined ? { toolBudget: toolBudget as ToolBudgetConfig } : {}),
3943
+ };
3944
+ }
3945
+ const launchParams = {
3946
+ ...workflowDefaults,
3947
+ ...childParams,
3948
+ ...(options.missionDetached ? { mission: false } : {}),
3949
+ workflowParentRunId: parentWorkflowRunId,
3950
+ workflowKey,
3951
+ ...(options.suppressRoutineResultIntercom ? { suppressRoutineResultIntercom: true } : {}),
3952
+ } as SubagentParamsLike;
3953
+ const normalizedGate = normalizeGateParams(launchParams);
3954
+ if (!normalizedGate.ok) throw new Error(normalizedGate.error);
3955
+ return prepareWorkflowChildParams(normalizedGate.params);
3956
+ }
3957
+
3958
+ type GateParamsNormalizationResult =
3959
+ | { ok: true; params: SubagentParamsLike }
3960
+ | { ok: false; error: string };
3961
+
3962
+ function normalizeGateParams(params: SubagentParamsLike): GateParamsNormalizationResult {
3963
+ if (params.gate !== undefined && params.action === "resume") {
3964
+ return { ok: false, error: "gate is not supported with action='resume'; resume uses the retained child contract." };
3965
+ }
3966
+ const normalized = normalizeGateAcceptance(params.gate, params.acceptance);
3967
+ if (!normalized.ok) return { ok: false, error: normalized.error };
3968
+ if (params.gate === undefined) return { ok: true, params };
3969
+ const { gate: _gate, ...rest } = params;
3970
+ return { ok: true, params: { ...rest, ...(normalized.acceptance !== undefined ? { acceptance: normalized.acceptance } : {}) } };
3971
+ }
3972
+
3897
3973
  function prepareWorkflowChildParams(params: SubagentParamsLike): SubagentParamsLike {
3898
3974
  if (params.worktree !== true || !params.agent) return params;
3899
3975
  const {
@@ -3949,21 +4025,6 @@ function workflowChatProgressUpdate(
3949
4025
  };
3950
4026
  }
3951
4027
 
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
4028
  function createScheduledOwnerState(source: SubagentState, ownerSessionId: string, ctx: ExtensionContext): SubagentState {
3968
4029
  const ownerSpawns = source.subagentSpawns?.sessionId === ownerSessionId
3969
4030
  ? {
@@ -4002,6 +4063,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4002
4063
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
4003
4064
  ctx: ExtensionContext,
4004
4065
  ) => Promise<AgentToolResult<Details>>;
4066
+ /** Public/model-facing execution boundary. Internal direct launch primitives use execute or executeDelegated. */
4067
+ executePublic: (
4068
+ id: string,
4069
+ params: SubagentParamsLike,
4070
+ signal: AbortSignal,
4071
+ onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
4072
+ ctx: ExtensionContext,
4073
+ ) => Promise<AgentToolResult<Details>>;
4005
4074
  /**
4006
4075
  * Correlated extension-to-extension delegation owns its request IDs and
4007
4076
  * cancellation controllers, so independent requests may execute concurrently.
@@ -4040,22 +4109,45 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4040
4109
  deps.state.foregroundRuns ??= new Map();
4041
4110
  deps.state.foregroundControls ??= new Map();
4042
4111
  deps.state.lastForegroundControlId ??= null;
4043
- const requestParams = omitExecutionModeActionAlias(params);
4044
- if (requestParams.workflowScript !== undefined) {
4045
- const invalidMode = requestParams.action !== undefined || requestParams.agent !== undefined || requestParams.step !== undefined || requestParams.tasks !== undefined || requestParams.chain !== undefined;
4046
- if (invalidMode) {
4047
- return { content: [{ type: "text", text: "workflowScript is its own execution mode; do not combine it with action, agent, step, tasks, or chain." }], isError: true, details: { mode: "workflow", results: [] } };
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
- }
4052
- const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? DEFAULT_FOREGROUND_TIMEOUT_MS;
4112
+ const normalizedGate = normalizeGateParams(params);
4113
+ if (!normalizedGate.ok) return buildRequestedModeError(params, normalizedGate.error);
4114
+ const requestParams = normalizedGate.params;
4115
+ const normalizedAction = typeof requestParams.action === "string" ? requestParams.action.trim() : requestParams.action;
4116
+ if (requestParams.workflowScript !== undefined && normalizedAction === undefined) {
4117
+ const parentCwd = ctx.cwd;
4118
+ const timeout = requestParams.timeoutMs ?? requestParams.maxRuntimeMs ?? (requestParams.async === false ? DEFAULT_FOREGROUND_TIMEOUT_MS : undefined);
4053
4119
  const workflowUsageBudget = validateUsageBudgetConfig(requestParams.usageBudget ?? deps.config.usageBudget, requestParams.usageBudget ? "usageBudget" : "config.usageBudget");
4054
4120
  if (workflowUsageBudget.error) return buildRequestedModeError(requestParams, workflowUsageBudget.error);
4055
- const workflowCwd = resolveRequestedCwd(ctx.cwd, requestParams.cwd);
4056
- const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd: ctx.cwd, workflowCwd, background: requestParams.async !== false });
4121
+ const workflowCwd = resolveRequestedCwd(parentCwd, requestParams.cwd);
4122
+ const chatProgressResult = resolveWorkflowChatProgress({ requested: requestParams.chatProgress, parentCwd, workflowCwd, background: requestParams.async !== false });
4057
4123
  if (chatProgressResult.error) return { content: [{ type: "text", text: chatProgressResult.error }], isError: true, details: { mode: "workflow", results: [] } };
4058
4124
  const chatProgress = chatProgressResult.projection!;
4125
+ const explicitMission = requestParams.missionId !== undefined || requestParams.mission !== undefined;
4126
+ let missionBinding: MissionLaunchBinding | undefined;
4127
+ let missionWarning: string | undefined;
4128
+ try {
4129
+ missionBinding = prepareMissionLaunch({
4130
+ params: requestParams,
4131
+ projectRoot: workflowCwd,
4132
+ ...(deps.config.missions ? { config: deps.config.missions } : {}),
4133
+ ownerSessionId: resolveCurrentSessionId(ctx.sessionManager),
4134
+ });
4135
+ } catch (error) {
4136
+ if (explicitMission) return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
4137
+ missionWarning = `Mission tracking unavailable: ${error instanceof Error ? error.message : String(error)}`;
4138
+ }
4139
+ const workflowState = missionBinding ? createMissionWorkflowState(missionBinding.location, missionBinding.missionId) : undefined;
4140
+ const attachWorkflowMission = (result: AgentToolResult<Details>): AgentToolResult<Details> => {
4141
+ if (!missionBinding) return missionWarning ? { ...result, details: { ...result.details, missionWarning } } : result;
4142
+ try {
4143
+ return attachMissionToLaunchResult({ binding: missionBinding, result });
4144
+ } catch (error) {
4145
+ const warning = `Mission tracking unavailable after launch: ${error instanceof Error ? error.message : String(error)}`;
4146
+ return explicitMission
4147
+ ? { ...result, isError: true, content: [...result.content, { type: "text", text: warning }], details: { ...result.details, missionWarning: warning } }
4148
+ : { ...result, details: { ...result.details, missionWarning: warning } };
4149
+ }
4150
+ };
4059
4151
  if (requestParams.async !== false) {
4060
4152
  const workflowRunId = _id;
4061
4153
  const asyncDir = path.join(DIRS.async, workflowRunId);
@@ -4076,9 +4168,8 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4076
4168
  state: "running",
4077
4169
  startedAt,
4078
4170
  lastUpdate: startedAt,
4079
- deadlineAt: startedAt + timeout,
4080
- timeoutMs: timeout,
4081
- cwd: ctx.cwd,
4171
+ ...(timeout !== undefined ? { deadlineAt: startedAt + timeout, timeoutMs: timeout } : {}),
4172
+ cwd: parentCwd,
4082
4173
  pid: process.pid,
4083
4174
  steps: [],
4084
4175
  workflow: { trace: [], emits: [], console: [] },
@@ -4101,7 +4192,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4101
4192
  job.workflow = status.workflow;
4102
4193
  }
4103
4194
  };
4104
- const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: ctx.cwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, timeoutMs: timeout, deadlineAt: startedAt + timeout, workflow: status.workflow };
4195
+ const workflowJob: AsyncJobState = { asyncId: workflowRunId, asyncDir, cwd: parentCwd, status: "running", sessionId: currentSessionId ?? undefined, mode: "workflow", agents: [], steps: [], startedAt, updatedAt: startedAt, ...(timeout !== undefined ? { timeoutMs: timeout, deadlineAt: startedAt + timeout } : {}), workflow: status.workflow };
4105
4196
  deps.state.asyncJobs.set(workflowRunId, workflowJob);
4106
4197
  deps.state.fleetJobs ??= new Map();
4107
4198
  deps.state.fleetJobs.set(workflowRunId, workflowJob);
@@ -4110,7 +4201,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4110
4201
  const { workflowScript, async: _workflowAsync, chatProgress: _chatProgress, ...workflowRequest } = requestParams;
4111
4202
  void Promise.resolve().then(async () => {
4112
4203
  const workflowResults: SingleResult[] = [];
4113
- 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;
4204
+ 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;
4114
4205
  const updateTrace = (trace: NonNullable<Details["workflow"]>["trace"]) => {
4115
4206
  status.workflow = { ...(status.workflow ?? { emits: [], console: [] }), trace };
4116
4207
  for (const entry of trace.filter((candidate) => candidate.operation === "run")) {
@@ -4135,6 +4226,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4135
4226
  script: workflowScript,
4136
4227
  timeoutMs: timeout,
4137
4228
  signal: controller.signal,
4229
+ ...(workflowState ? { state: workflowState } : {}),
4138
4230
  onTrace: updateTrace,
4139
4231
  onEmit: (emits) => {
4140
4232
  // Each emit is validated at the host boundary in runWorkflowScript before onEmit fires.
@@ -4146,7 +4238,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4146
4238
  if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
4147
4239
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4148
4240
  if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
4149
- const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: workflowRunId, workflowKey: key } as SubagentParamsLike);
4241
+ const childRequest = prepareWorkflowLaunchParams(workflowChildDefaults, childParams, workflowRunId, key, { missionDetached: missionBinding !== undefined || requestParams.mission === false });
4150
4242
  const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
4151
4243
  workflowResults.push(...result.details.results);
4152
4244
  const child = workflowChildResult(key, result);
@@ -4165,24 +4257,24 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4165
4257
  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) };
4166
4258
  persist();
4167
4259
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: "complete" });
4168
- 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: ctx.cwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4260
+ 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: parentCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4169
4261
  } catch (error) {
4170
4262
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
4171
4263
  const stopped = controller.signal.aborted;
4172
4264
  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 } });
4173
4265
  persist();
4174
4266
  appendWorkflowEvent({ type: "subagent.workflow.completed", state: status.state, error: status.error });
4175
- 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: ctx.cwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4267
+ 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: parentCwd, sessionId: currentSessionId, timestamp: Date.now(), durationMs: Date.now() - startedAt });
4176
4268
  } finally {
4177
4269
  deps.state.workflowControllers?.delete(workflowRunId);
4178
4270
  }
4179
4271
  });
4180
- return {
4272
+ return attachWorkflowMission({
4181
4273
  content: [{ type: "text", text: formatAsyncStartedMessage(`Async workflow [${workflowRunId}]`, ctx.hasUI === true) }],
4182
4274
  details: { mode: "workflow", runId: workflowRunId, asyncId: workflowRunId, asyncDir, results: [], chatProgress },
4183
- };
4275
+ });
4184
4276
  }
4185
- 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;
4277
+ 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;
4186
4278
  const workflowResults: SingleResult[] = [];
4187
4279
  let liveWorkflow: NonNullable<Details["workflow"]> = { trace: [], emits: [], console: [] };
4188
4280
  const sendWorkflowProgress = () => {
@@ -4194,6 +4286,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4194
4286
  script: requestParams.workflowScript,
4195
4287
  timeoutMs: timeout,
4196
4288
  signal,
4289
+ ...(workflowState ? { state: workflowState } : {}),
4197
4290
  onTrace: (trace) => {
4198
4291
  liveWorkflow = { ...liveWorkflow, trace };
4199
4292
  sendWorkflowProgress();
@@ -4206,7 +4299,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4206
4299
  if (workflowUsageBudget.budget && childParams.async === true) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, "workflow usageBudget does not support async runs.run launches."));
4207
4300
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
4208
4301
  if (budgetState?.exhausted) return workflowChildResult(key, buildRequestedModeError(childParams as SubagentParamsLike, usageBudgetExceededMessage(budgetState)));
4209
- const childRequest = prepareWorkflowChildParams({ ...workflowChildDefaults, ...childParams, workflowParentRunId: _id, workflowKey: key, suppressRoutineResultIntercom: chatProgress.mode === "live-card" } as SubagentParamsLike);
4302
+ const childRequest = prepareWorkflowLaunchParams(workflowChildDefaults, childParams, _id, key, { missionDetached: missionBinding !== undefined || requestParams.mission === false, suppressRoutineResultIntercom: chatProgress.mode === "live-card" });
4210
4303
  const result = await execute(randomUUID(), childRequest, workflowSignal, undefined, ctx, preserveActiveSession);
4211
4304
  workflowResults.push(...result.details.results);
4212
4305
  return workflowChildResult(key, result);
@@ -4218,10 +4311,10 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4218
4311
  if (workflow.emits.length > 0) sections.push(`Emitted:\n${workflow.emits.map(formatWorkflowValue).join("\n")}`);
4219
4312
  if (workflow.console.length > 0) sections.push(`Console:\n${workflow.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4220
4313
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
4221
- return {
4314
+ return attachWorkflowMission({
4222
4315
  content: [{ type: "text", text: sections.join("\n\n") }],
4223
4316
  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 }),
4224
- };
4317
+ });
4225
4318
  } catch (error) {
4226
4319
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
4227
4320
  const text = error instanceof Error ? error.message : String(error);
@@ -4230,11 +4323,11 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4230
4323
  if (partial.emits.length > 0) sections.push(`Emitted:\n${partial.emits.map(formatWorkflowValue).join("\n")}`);
4231
4324
  if (partial.console.length > 0) sections.push(`Console:\n${partial.console.map((entry) => `[${entry.level}] ${entry.text}`).join("\n")}`);
4232
4325
  if (traceLines.length > 0) sections.push(`Call trace:\n${traceLines.join("\n")}`);
4233
- return {
4326
+ return attachWorkflowMission({
4234
4327
  content: [{ type: "text", text: sections.join("\n\n") }],
4235
4328
  isError: true,
4236
4329
  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 }),
4237
- };
4330
+ });
4238
4331
  }
4239
4332
  }
4240
4333
  const directParams = prepareWorkflowChildParams(requestParams);
@@ -4338,6 +4431,29 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4338
4431
  }
4339
4432
  return handleWatchdogToolAction(action, paramsWithResolvedCwd, ctx, deps.watchdog);
4340
4433
  }
4434
+ if (action === "refine" || action === "refine.show" || action === "refine.rollback") {
4435
+ if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
4436
+ return {
4437
+ content: [{ type: "text", text: `Action '${action}' is not available from child-safe subagent fanout mode.` }],
4438
+ isError: true,
4439
+ details: { mode: "management", results: [] },
4440
+ };
4441
+ }
4442
+ return handleRefinementAction(action, paramsWithResolvedCwd, {
4443
+ cwd: requestCwd,
4444
+ state: deps.state,
4445
+ signal,
4446
+ launchProposalChild: (task, outputSchema, proposalSignal) => execute(randomUUID(), {
4447
+ agent: "reviewer",
4448
+ task,
4449
+ context: "fresh",
4450
+ async: false,
4451
+ artifacts: false,
4452
+ outputSchema,
4453
+ toolBudget: { hard: 1, block: ["write", "edit", "bash"] },
4454
+ }, proposalSignal, undefined, ctx, true),
4455
+ });
4456
+ }
4341
4457
  if (action === "grant-spawn-budget") {
4342
4458
  if (deps.allowMutatingManagementActions === false || !ctx.hasUI) {
4343
4459
  return {
@@ -4410,6 +4526,14 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4410
4526
  details: { mode: "management", results: [], spawnBudget: granted.snapshot },
4411
4527
  };
4412
4528
  }
4529
+ if (action === "children.list") {
4530
+ deps.state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
4531
+ const children = listRetainedChildren(DIRS.async, deps.state.currentSessionId);
4532
+ return {
4533
+ content: [{ type: "text", text: formatRetainedChildren(children) }],
4534
+ details: { mode: "management", results: [] },
4535
+ };
4536
+ }
4413
4537
  if (action === "doctor") {
4414
4538
  let currentSessionFile: string | null = null;
4415
4539
  let currentSessionId = deps.state.currentSessionId;
@@ -4566,6 +4690,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4566
4690
  state: deps.state,
4567
4691
  runId,
4568
4692
  message,
4693
+ mode: paramsWithResolvedCwd.mode,
4569
4694
  index: paramsWithResolvedCwd.index,
4570
4695
  kill: deps.kill,
4571
4696
  location,
@@ -4591,7 +4716,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4591
4716
  const text = error instanceof Error ? error.message : String(error);
4592
4717
  return { content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } };
4593
4718
  }
4594
- if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, index: paramsWithResolvedCwd.index, signal }));
4719
+ if (resolved?.kind === "nested") return steerNestedRun(omitUndefinedProperties({ target: resolved, message, mode: paramsWithResolvedCwd.mode, index: paramsWithResolvedCwd.index, signal }));
4595
4720
  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: [] } };
4596
4721
  if (resolved?.kind !== "async") return { content: [{ type: "text", text: `No async run found for '${targetRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
4597
4722
  if (resolved.location.asyncDir) {
@@ -4602,6 +4727,7 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
4602
4727
  state: deps.state,
4603
4728
  runId: resolved.id,
4604
4729
  message,
4730
+ mode: paramsWithResolvedCwd.mode,
4605
4731
  index: paramsWithResolvedCwd.index,
4606
4732
  kill: deps.kill,
4607
4733
  location: resolved.location,
@@ -5205,8 +5331,9 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5205
5331
  onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
5206
5332
  ctx: ExtensionContext,
5207
5333
  ): Promise<AgentToolResult<Details>> => {
5208
- const requestParams = omitExecutionModeActionAlias(params);
5209
- if (requestParams.action) return execute(id, requestParams, signal, onUpdate, ctx);
5334
+ const normalizedAction = typeof params.action === "string" ? params.action.trim() : params.action;
5335
+ const requestParams = normalizedAction ? { ...params, action: normalizedAction } : params;
5336
+ if (normalizedAction) return execute(id, requestParams, signal, onUpdate, ctx);
5210
5337
  const { depth } = checkSubagentDepth(deps.config.maxSubagentDepth);
5211
5338
  const dispatchParams = applyForceTopLevelAsyncOverride(requestParams, depth, deps.config.forceTopLevelAsync === true);
5212
5339
  const runsForeground = dispatchParams.clarify === true || (dispatchParams.async ?? deps.asyncByDefault) !== true;
@@ -5220,6 +5347,20 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5220
5347
  }
5221
5348
  };
5222
5349
 
5350
+ const executePublic = (
5351
+ id: string,
5352
+ params: SubagentParamsLike,
5353
+ signal: AbortSignal,
5354
+ onUpdate: ((r: AgentToolResult<Details>) => void) | undefined,
5355
+ ctx: ExtensionContext,
5356
+ ): Promise<AgentToolResult<Details>> => {
5357
+ const normalized = normalizePublicSubagentExecution(params);
5358
+ if (!normalized.ok) {
5359
+ return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
5360
+ }
5361
+ return executeWithSingleDispatchGuard(id, normalized.params, signal, onUpdate, ctx);
5362
+ };
5363
+
5223
5364
  const executeDelegated = async (
5224
5365
  id: string,
5225
5366
  params: SubagentParamsLike,
@@ -5259,5 +5400,5 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
5259
5400
  return ownerExecutor.execute(id, params, signal, undefined, ctx);
5260
5401
  };
5261
5402
 
5262
- return { execute: executeWithSingleDispatchGuard, executeDelegated, executeScheduled };
5403
+ return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled };
5263
5404
  }