pi-subagents 0.73.0 → 0.74.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 (245) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +1 -1
  3. package/docs/agents.md +18 -12
  4. package/docs/configuration.md +66 -9
  5. package/docs/extension-api.md +5 -4
  6. package/docs/missions.md +13 -13
  7. package/docs/models.md +2 -2
  8. package/docs/observability.md +2 -2
  9. package/docs/tool-reference.md +63 -86
  10. package/docs/workflows.md +165 -171
  11. package/package.json +2 -2
  12. package/prompts/review-loop.md +1 -1
  13. package/skills/council-mode/SKILL.md +1 -1
  14. package/skills/pi-subagents/SKILL.md +12 -5
  15. package/skills/pi-subagents/references/constraints-and-recipes.md +3 -3
  16. package/skills/pi-subagents/references/execution-controls.md +68 -67
  17. package/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  18. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  19. package/skills/pi-subagents/references/prompting-and-roles.md +45 -48
  20. package/src/agents/advertised-agent-prompt.d.ts +6 -4
  21. package/src/agents/advertised-agent-prompt.d.ts.map +1 -1
  22. package/src/agents/advertised-agent-prompt.js +16 -30
  23. package/src/agents/advertised-agent-prompt.js.map +1 -1
  24. package/src/agents/agent-management.js +2 -2
  25. package/src/agents/agent-management.js.map +1 -1
  26. package/src/agents/agents.d.ts +1 -0
  27. package/src/agents/agents.d.ts.map +1 -1
  28. package/src/agents/agents.js +8 -0
  29. package/src/agents/agents.js.map +1 -1
  30. package/src/api/preflight.d.ts +5 -1
  31. package/src/api/preflight.d.ts.map +1 -1
  32. package/src/api/preflight.js +2 -1
  33. package/src/api/preflight.js.map +1 -1
  34. package/src/extension/config.d.ts.map +1 -1
  35. package/src/extension/config.js +12 -4
  36. package/src/extension/config.js.map +1 -1
  37. package/src/extension/control-notices.d.ts +2 -0
  38. package/src/extension/control-notices.d.ts.map +1 -1
  39. package/src/extension/control-notices.js +2 -0
  40. package/src/extension/control-notices.js.map +1 -1
  41. package/src/extension/doctor.d.ts.map +1 -1
  42. package/src/extension/doctor.js +1 -0
  43. package/src/extension/doctor.js.map +1 -1
  44. package/src/extension/fanout-child.d.ts.map +1 -1
  45. package/src/extension/fanout-child.js +13 -3
  46. package/src/extension/fanout-child.js.map +1 -1
  47. package/src/extension/herdr-pi-bridge.d.ts.map +1 -1
  48. package/src/extension/herdr-pi-bridge.js +2 -0
  49. package/src/extension/herdr-pi-bridge.js.map +1 -1
  50. package/src/extension/index.d.ts +1 -1
  51. package/src/extension/index.d.ts.map +1 -1
  52. package/src/extension/index.js +71 -169
  53. package/src/extension/index.js.map +1 -1
  54. package/src/extension/public-execution.d.ts +10 -1
  55. package/src/extension/public-execution.d.ts.map +1 -1
  56. package/src/extension/public-execution.js +69 -35
  57. package/src/extension/public-execution.js.map +1 -1
  58. package/src/extension/reply-workflow-script.d.ts +12 -0
  59. package/src/extension/reply-workflow-script.d.ts.map +1 -0
  60. package/src/extension/reply-workflow-script.js +61 -0
  61. package/src/extension/reply-workflow-script.js.map +1 -0
  62. package/src/extension/rpc.d.ts +2 -0
  63. package/src/extension/rpc.d.ts.map +1 -1
  64. package/src/extension/rpc.js +33 -10
  65. package/src/extension/rpc.js.map +1 -1
  66. package/src/extension/schemas.d.ts +3 -117
  67. package/src/extension/schemas.d.ts.map +1 -1
  68. package/src/extension/schemas.js +38 -125
  69. package/src/extension/schemas.js.map +1 -1
  70. package/src/extension/tool-activation.d.ts +2 -4
  71. package/src/extension/tool-activation.d.ts.map +1 -1
  72. package/src/extension/tool-activation.js +54 -104
  73. package/src/extension/tool-activation.js.map +1 -1
  74. package/src/extension/tool-description.d.ts +8 -5
  75. package/src/extension/tool-description.d.ts.map +1 -1
  76. package/src/extension/tool-description.js +51 -27
  77. package/src/extension/tool-description.js.map +1 -1
  78. package/src/extension/upgrade-notice.d.ts +7 -0
  79. package/src/extension/upgrade-notice.d.ts.map +1 -0
  80. package/src/extension/upgrade-notice.js +37 -0
  81. package/src/extension/upgrade-notice.js.map +1 -0
  82. package/src/integrations/herdr-status.d.ts +3 -0
  83. package/src/integrations/herdr-status.d.ts.map +1 -1
  84. package/src/integrations/herdr-status.js +17 -3
  85. package/src/integrations/herdr-status.js.map +1 -1
  86. package/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  87. package/src/intercom/native-supervisor-channel.js +4 -1
  88. package/src/intercom/native-supervisor-channel.js.map +1 -1
  89. package/src/runs/background/async-execution.d.ts +3 -0
  90. package/src/runs/background/async-execution.d.ts.map +1 -1
  91. package/src/runs/background/async-execution.js +6 -2
  92. package/src/runs/background/async-execution.js.map +1 -1
  93. package/src/runs/background/async-job-tracker.d.ts.map +1 -1
  94. package/src/runs/background/async-job-tracker.js +65 -22
  95. package/src/runs/background/async-job-tracker.js.map +1 -1
  96. package/src/runs/background/async-retention.d.ts +1 -1
  97. package/src/runs/background/async-retention.d.ts.map +1 -1
  98. package/src/runs/background/async-retention.js +47 -15
  99. package/src/runs/background/async-retention.js.map +1 -1
  100. package/src/runs/background/await-async-run.d.ts +23 -0
  101. package/src/runs/background/await-async-run.d.ts.map +1 -0
  102. package/src/runs/background/await-async-run.js +80 -0
  103. package/src/runs/background/await-async-run.js.map +1 -0
  104. package/src/runs/background/chain-root-attachment.d.ts +1 -0
  105. package/src/runs/background/chain-root-attachment.d.ts.map +1 -1
  106. package/src/runs/background/chain-root-attachment.js +3 -3
  107. package/src/runs/background/chain-root-attachment.js.map +1 -1
  108. package/src/runs/background/control-channel.d.ts +4 -0
  109. package/src/runs/background/control-channel.d.ts.map +1 -1
  110. package/src/runs/background/control-channel.js +16 -1
  111. package/src/runs/background/control-channel.js.map +1 -1
  112. package/src/runs/background/notify.d.ts +3 -0
  113. package/src/runs/background/notify.d.ts.map +1 -1
  114. package/src/runs/background/notify.js +3 -0
  115. package/src/runs/background/notify.js.map +1 -1
  116. package/src/runs/background/run-child-session.d.ts.map +1 -1
  117. package/src/runs/background/run-child-session.js +4 -1
  118. package/src/runs/background/run-child-session.js.map +1 -1
  119. package/src/runs/background/run-status.d.ts.map +1 -1
  120. package/src/runs/background/run-status.js +6 -0
  121. package/src/runs/background/run-status.js.map +1 -1
  122. package/src/runs/background/runner-child-launch.d.ts +1 -0
  123. package/src/runs/background/runner-child-launch.d.ts.map +1 -1
  124. package/src/runs/background/runner-child-launch.js +2 -0
  125. package/src/runs/background/runner-child-launch.js.map +1 -1
  126. package/src/runs/background/scheduled-runs.js +5 -5
  127. package/src/runs/background/scheduled-runs.js.map +1 -1
  128. package/src/runs/background/subagent-runner.d.ts +3 -0
  129. package/src/runs/background/subagent-runner.d.ts.map +1 -1
  130. package/src/runs/background/subagent-runner.js +13 -1
  131. package/src/runs/background/subagent-runner.js.map +1 -1
  132. package/src/runs/foreground/async-stop-action.d.ts.map +1 -1
  133. package/src/runs/foreground/async-stop-action.js +19 -3
  134. package/src/runs/foreground/async-stop-action.js.map +1 -1
  135. package/src/runs/foreground/execution.d.ts.map +1 -1
  136. package/src/runs/foreground/execution.js +8 -1
  137. package/src/runs/foreground/execution.js.map +1 -1
  138. package/src/runs/foreground/subagent-executor.d.ts +6 -5
  139. package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  140. package/src/runs/foreground/subagent-executor.js +293 -182
  141. package/src/runs/foreground/subagent-executor.js.map +1 -1
  142. package/src/runs/shared/child-launch.d.ts +5 -1
  143. package/src/runs/shared/child-launch.d.ts.map +1 -1
  144. package/src/runs/shared/child-launch.js +15 -1
  145. package/src/runs/shared/child-launch.js.map +1 -1
  146. package/src/runs/shared/child-session.d.ts +9 -0
  147. package/src/runs/shared/child-session.d.ts.map +1 -1
  148. package/src/runs/shared/child-session.js +90 -4
  149. package/src/runs/shared/child-session.js.map +1 -1
  150. package/src/runs/shared/child-tool-plan.d.ts +4 -0
  151. package/src/runs/shared/child-tool-plan.d.ts.map +1 -1
  152. package/src/runs/shared/child-tool-plan.js +11 -4
  153. package/src/runs/shared/child-tool-plan.js.map +1 -1
  154. package/src/runs/shared/dynamic-fanout.js +1 -1
  155. package/src/runs/shared/dynamic-fanout.js.map +1 -1
  156. package/src/runs/shared/mcp-config-sources.d.ts +1 -0
  157. package/src/runs/shared/mcp-config-sources.d.ts.map +1 -1
  158. package/src/runs/shared/mcp-config-sources.js +1 -1
  159. package/src/runs/shared/mcp-config-sources.js.map +1 -1
  160. package/src/runs/shared/mcp-direct-tool-allowlist.d.ts +28 -0
  161. package/src/runs/shared/mcp-direct-tool-allowlist.d.ts.map +1 -1
  162. package/src/runs/shared/mcp-direct-tool-allowlist.js +78 -2
  163. package/src/runs/shared/mcp-direct-tool-allowlist.js.map +1 -1
  164. package/src/runs/shared/model-resolution.d.ts +10 -0
  165. package/src/runs/shared/model-resolution.d.ts.map +1 -1
  166. package/src/runs/shared/model-resolution.js +30 -9
  167. package/src/runs/shared/model-resolution.js.map +1 -1
  168. package/src/runs/shared/model-scope.d.ts +2 -1
  169. package/src/runs/shared/model-scope.d.ts.map +1 -1
  170. package/src/runs/shared/model-scope.js +26 -10
  171. package/src/runs/shared/model-scope.js.map +1 -1
  172. package/src/runs/shared/parallel-utils.d.ts +2 -0
  173. package/src/runs/shared/parallel-utils.d.ts.map +1 -1
  174. package/src/runs/shared/parallel-utils.js.map +1 -1
  175. package/src/runs/shared/pi-spawn.d.ts +1 -10
  176. package/src/runs/shared/pi-spawn.d.ts.map +1 -1
  177. package/src/runs/shared/pi-spawn.js +7 -86
  178. package/src/runs/shared/pi-spawn.js.map +1 -1
  179. package/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  180. package/src/runs/shared/subagent-prompt-runtime.js +2 -0
  181. package/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  182. package/src/runs/shared/worktree.d.ts +1 -1
  183. package/src/runs/shared/worktree.d.ts.map +1 -1
  184. package/src/runs/shared/worktree.js +1 -1
  185. package/src/runs/shared/worktree.js.map +1 -1
  186. package/src/shared/disabled-features.d.ts +92 -0
  187. package/src/shared/disabled-features.d.ts.map +1 -0
  188. package/src/shared/disabled-features.js +113 -0
  189. package/src/shared/disabled-features.js.map +1 -0
  190. package/src/shared/extension-context.d.ts +9 -0
  191. package/src/shared/extension-context.d.ts.map +1 -1
  192. package/src/shared/extension-context.js +7 -0
  193. package/src/shared/extension-context.js.map +1 -1
  194. package/src/shared/settings.d.ts +1 -1
  195. package/src/shared/types.d.ts +20 -0
  196. package/src/shared/types.d.ts.map +1 -1
  197. package/src/shared/types.js.map +1 -1
  198. package/src/slash/prompt-template-bridge.js +2 -2
  199. package/src/slash/prompt-template-bridge.js.map +1 -1
  200. package/src/slash/slash-commands.d.ts +2 -0
  201. package/src/slash/slash-commands.d.ts.map +1 -1
  202. package/src/slash/slash-commands.js +2 -2
  203. package/src/slash/slash-commands.js.map +1 -1
  204. package/src/tui/render.d.ts.map +1 -1
  205. package/src/tui/render.js +55 -17
  206. package/src/tui/render.js.map +1 -1
  207. package/src/watchdog/diff-tool.d.ts +3 -1
  208. package/src/watchdog/diff-tool.d.ts.map +1 -1
  209. package/src/watchdog/diff-tool.js +20 -11
  210. package/src/watchdog/diff-tool.js.map +1 -1
  211. package/src/watchdog/register-child.js +2 -2
  212. package/src/watchdog/register-child.js.map +1 -1
  213. package/src/watchdog/register-main.js +2 -2
  214. package/src/watchdog/register-main.js.map +1 -1
  215. package/src/watchdog/review.d.ts +1 -1
  216. package/src/watchdog/review.d.ts.map +1 -1
  217. package/src/watchdog/review.js +1 -1
  218. package/src/watchdog/review.js.map +1 -1
  219. package/src/watchdog/turn-delta.js +1 -1
  220. package/src/watchdog/turn-delta.js.map +1 -1
  221. package/src/workflows/chat-progress.js +2 -2
  222. package/src/workflows/chat-progress.js.map +1 -1
  223. package/src/workflows/scripted-workflow.d.ts +8 -0
  224. package/src/workflows/scripted-workflow.d.ts.map +1 -1
  225. package/src/workflows/scripted-workflow.js +10 -2
  226. package/src/workflows/scripted-workflow.js.map +1 -1
  227. package/src/workflows/structured-workflow-scripts.d.ts +10 -0
  228. package/src/workflows/structured-workflow-scripts.d.ts.map +1 -0
  229. package/src/workflows/structured-workflow-scripts.js +166 -0
  230. package/src/workflows/structured-workflow-scripts.js.map +1 -0
  231. package/src/workflows/workflow-receipt.d.ts.map +1 -1
  232. package/src/workflows/workflow-receipt.js +8 -2
  233. package/src/workflows/workflow-receipt.js.map +1 -1
  234. package/src/workflows/workflow-resources.d.ts +9 -0
  235. package/src/workflows/workflow-resources.d.ts.map +1 -1
  236. package/src/workflows/workflow-resources.js +39 -12
  237. package/src/workflows/workflow-resources.js.map +1 -1
  238. package/src/workflows/workflow-reuse.d.ts +51 -0
  239. package/src/workflows/workflow-reuse.d.ts.map +1 -0
  240. package/src/workflows/workflow-reuse.js +103 -0
  241. package/src/workflows/workflow-reuse.js.map +1 -0
  242. package/src/workflows/workflow-revival.d.ts +32 -0
  243. package/src/workflows/workflow-revival.d.ts.map +1 -0
  244. package/src/workflows/workflow-revival.js +111 -0
  245. package/src/workflows/workflow-revival.js.map +1 -0
@@ -15,12 +15,14 @@ import { handleManagementAction } from "../../agents/agent-management.js";
15
15
  import { handleRefinementAction } from "../../agents/agent-refinements.js";
16
16
  import { buildDoctorReport } from "../../extension/doctor.js";
17
17
  import { readSubagentGuide } from "../../extension/subagent-guide.js";
18
- import { normalizePublicSubagentExecution, validateWorkflowCapacityOverrides } from "../../extension/public-execution.js";
18
+ import { isWorkflowScriptPath, normalizePublicSubagentExecution, validateWorkflowCapacityOverrides } from "../../extension/public-execution.js";
19
+ import { readReplyWorkflowScript } from "../../extension/reply-workflow-script.js";
20
+ import { disabledFeatureNotice, disabledFeatureUseError, resolveDisabledFeatureSurface } from "../../shared/disabled-features.js";
19
21
  import { runSync } from "./execution.js";
20
22
  import { handleWatchdogToolAction, WATCHDOG_TOOL_ACTIONS } from "../../watchdog/tool-actions.js";
21
23
  import { applyWatchdogLaunchRules } from "../../watchdog/rules.js";
22
24
  import { childWatchdogProgressForModel } from "../../watchdog/child-status.js";
23
- import { normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin } from "../shared/model-resolution.js";
25
+ import { normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin, scopedModelIdsFromContext } from "../shared/model-resolution.js";
24
26
  import { projectChainOutputSchemas, resolveEffectiveOutputSchema } from "../shared/child-launch-plan.js";
25
27
  import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.js";
26
28
  import { resolveModelScopesForAgent } from "../shared/model-scope.js";
@@ -46,6 +48,7 @@ import { formatControlIntercomMessage, formatControlNoticeMessage, resolveContro
46
48
  import { formatSpawnBudget, getSpawnBudgetSnapshot, grantSpawnBudget, preflightSpawnBudget, preflightSpawnBudgetGrant, reserveSpawnBudget } from "../shared/spawn-budget.js";
47
49
  import { claimRunFanoutBatch, claimRunFanoutBatchWithCommit, createRunFanoutBudget, formatRunFanoutBudget, getRunFanoutBudgetSnapshot, readRunFanoutBudgetDescriptor, RunFanoutLimitError, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.js";
48
50
  import { retainLiveForegroundNestedRoute } from "../../integrations/pi-web-session-liveness.js";
51
+ import { HERDR_FOREGROUND_CONTROL_CHANGED_EVENT } from "../../integrations/herdr-status.js";
49
52
  import { validateToolBudgetConfig } from "../shared/tool-budget.js";
50
53
  import { usageBudgetExceededMessage, usageBudgetState, validateUsageBudgetConfig } from "../shared/usage-budget.js";
51
54
  import { assertAgentAllowedByCapabilityCeiling, intersectSubagentCapabilityCeilings, resolveCurrentSubagentCapabilityCeiling } from "../shared/capability-ceiling.js";
@@ -68,11 +71,12 @@ import { dismissRecoveredWorkflow } from "./async-dismiss-action.js";
68
71
  import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "./workflow-detach-reconcile.js";
69
72
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.js";
70
73
  import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.js";
74
+ import { awaitExistingAsyncRun, claimWorkflowAwaitedResult } from "../background/await-async-run.js";
71
75
  import { fallbackResultPayloadPathForSessionRun, removeResultIndex, resultFilePath, writeAsyncResultFile } from "../background/result-files.js";
72
76
  import { attachRootChildrenToSteps, createNestedRoute, findNestedControlResult, inheritedNestedParentAddressOf, inheritedNestedRouteOf, nestedRunScope, resolveNestedAsyncDir, retainNestedLookupRoute, snapshotNestedEventFiles, updateForegroundNestedProjection, writeNestedControlRequest, writeNestedEvent } from "../shared/nested-events.js";
73
77
  import { resolveSubagentRunId } from "../background/run-id-resolver.js";
74
78
  import { formatNestedRunStatusLines } from "../shared/nested-render.js";
75
- import { isStoppableAsyncStatusStep, resolveAsyncStatusChild, stopStoppableAsyncStatusChildren } from "../shared/child-identity.js";
79
+ import { isStoppableAsyncStatusStep, resolveAsyncStatusChild } from "../shared/child-identity.js";
76
80
  import { inspectSubagentStatus } from "../background/run-status.js";
77
81
  import { getExternalJobProvider } from "../../api/external-job-provider.js";
78
82
  import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.js";
@@ -89,15 +93,17 @@ import { getInspectorPlugins } from "../../inspectors/plugins.js";
89
93
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.js";
90
94
  import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError } from "../../workflows/scripted-workflow.js";
91
95
  import { formatIncrementalChildCompletion, incrementalChildCompletionTriggersTurn } from "../background/notify.js";
96
+ import { appendWorkflowChildJournal, findWorkflowReuseSource, matchWorkflowReuse, workflowChildFingerprint, workflowScriptDigest, workflowStopCause, WORKFLOW_RUNTIME_REPLACED_RELAUNCH_NOTICE } from "../../workflows/workflow-reuse.js";
92
97
  import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath } from "../../workflows/host-command.js";
93
98
  import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt } from "../../workflows/workflow-receipt.js";
99
+ import { formatWorkflowKeyRevival, recordWorkflowRevival, withWorkflowRevivals } from "../../workflows/workflow-revival.js";
94
100
  import { upsertHostStep, validHostStepNodes } from "../shared/host-step-status.js";
95
101
  import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.js";
96
102
  import { parseWorkflowChildSummary, workflowChildProgress, workflowChildSummary } from "../../workflows/workflow-child-summary.js";
97
103
  import { resolveWorkflowChatProgress } from "../../workflows/chat-progress.js";
98
104
  import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.js";
99
105
  import { authorizeWorkflowResourceHost, claimWorkflowChildPermit, consumeWorkflowResourcePermit, validateWorkflowChildPermitRoot, } from "../../shared/workflow-child-permit.js";
100
- import { deepFreezeWorkflowArgs, normalizeWorkflowArgs, resolveWorkflowResource } from "../../workflows/workflow-resources.js";
106
+ import { deepFreezeWorkflowArgs, normalizeWorkflowArgs, resolveStructuredWorkflowResource, resolveWorkflowResource } from "../../workflows/workflow-resources.js";
101
107
  import { stableJsonDigest } from "../../shared/launch-contract.js";
102
108
  import { cleanupWorktrees, createWorktrees, preflightWorktreeSource, withWorktreeTransaction, diffWorktrees, formatWorktreeDiffSummary, } from "../shared/worktree.js";
103
109
  import { createWorktreeCleanupPlan, formatWorktreeCleanupPlan } from "../shared/worktree-cleanup-plan.js";
@@ -132,9 +138,10 @@ function hasSingleAdjacentTransposition(left, right) {
132
138
  && left[mismatch + 1] === right[mismatch]
133
139
  && left.slice(mismatch + 2) === right.slice(mismatch + 2);
134
140
  }
135
- export function unknownSubagentActionMessage(action) {
141
+ export function unknownSubagentActionMessage(action, disabled) {
136
142
  const requested = action.toLowerCase();
137
- const suggestion = SUBAGENT_ACTIONS.find((candidate) => {
143
+ const enabledActions = SUBAGENT_ACTIONS.filter((candidate) => !disabled?.actions.has(candidate));
144
+ const suggestion = enabledActions.find((candidate) => {
138
145
  const distance = editDistance(requested, candidate);
139
146
  const closeMatch = distance <= Math.max(1, Math.floor(candidate.length / 4)) || hasSingleAdjacentTransposition(requested, candidate);
140
147
  if (DESTRUCTIVE_MANAGEMENT_ACTIONS.has(candidate))
@@ -142,7 +149,7 @@ export function unknownSubagentActionMessage(action) {
142
149
  return closeMatch;
143
150
  });
144
151
  const nextStep = 'Use subagent({ action: "status" }) to inspect runs or subagent({ action: "list" }) to inspect agents.';
145
- const validActions = `Valid: ${SUBAGENT_ACTIONS.join(", ")}.`;
152
+ const validActions = `Valid: ${enabledActions.join(", ")}.`;
146
153
  return suggestion
147
154
  ? `Unknown action: ${action}. Did you mean ${suggestion}? ${nextStep} ${validActions}`
148
155
  : `Unknown action: ${action}. ${nextStep} ${validActions}`;
@@ -185,21 +192,18 @@ function inheritedRunFanoutBudget(deps) {
185
192
  function resolveRequestedCwd(runtimeCwd, requestedCwd) {
186
193
  return requestedCwd ? path.resolve(runtimeCwd, requestedCwd) : runtimeCwd;
187
194
  }
188
- function loadWorkflowScriptPath(params, runtimeCwd) {
189
- if (params.workflowScriptPath === undefined)
190
- return { params };
191
- const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, params.cwd), params.workflowScriptPath);
192
- let workflowScript;
195
+ function readWorkflowScriptFile(requestedPath, requestedCwd, runtimeCwd) {
196
+ const scriptPath = path.resolve(resolveRequestedCwd(runtimeCwd, requestedCwd), requestedPath);
197
+ let script;
193
198
  try {
194
- workflowScript = fs.readFileSync(scriptPath, "utf8");
199
+ script = fs.readFileSync(scriptPath, "utf8");
195
200
  }
196
201
  catch (error) {
197
- return { error: `Failed to read workflowScriptPath '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
202
+ return { error: `Failed to read workflow script '${scriptPath}': ${error instanceof Error ? error.message : String(error)}` };
198
203
  }
199
- if (!workflowScript.trim())
200
- return { error: `workflowScriptPath file '${scriptPath}' is empty.` };
201
- const { workflowScriptPath: _workflowScriptPath, ...rest } = params;
202
- return { params: { ...rest, workflowScript } };
204
+ if (!script.trim())
205
+ return { error: `Workflow script file '${scriptPath}' is empty.` };
206
+ return { script };
203
207
  }
204
208
  export function removeForegroundControlIfIdle(state, runId, trackRetainedNestedRoute) {
205
209
  const control = state.foregroundControls.get(runId);
@@ -294,6 +298,10 @@ function trustedSessionRootsForStatus(ctx, deps) {
294
298
  roots.push(deps.getSubagentSessionRoot(parentSessionFile));
295
299
  return [...new Set(roots)];
296
300
  }
301
+ /** Children follow the launching session's trust; hosts older than Pi's trust concept keep Pi's default. */
302
+ function sessionProjectTrust(ctx) {
303
+ return typeof ctx.isProjectTrusted === "function" ? ctx.isProjectTrusted() : undefined;
304
+ }
297
305
  function spawnBudgetErrorResult(message, mode) {
298
306
  return {
299
307
  content: [{ type: "text", text: message }],
@@ -995,6 +1003,7 @@ function appendStepToAsyncChain(input) {
995
1003
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
996
1004
  currentModelProvider: parentModel?.provider,
997
1005
  currentModel: parentModel,
1006
+ scopedModelIds: scopedModelIdsFromContext(input.ctx),
998
1007
  modelScope: discoveredForAppend.modelScope,
999
1008
  modelResponseAliases: input.deps.config.modelResponseAliases,
1000
1009
  interactive: input.ctx.hasUI,
@@ -1444,11 +1453,13 @@ async function resumeExternalJobFollowUp(input) {
1444
1453
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1445
1454
  currentModelProvider: parentModel?.provider,
1446
1455
  currentModel: parentModel,
1456
+ scopedModelIds: scopedModelIdsFromContext(input.ctx),
1447
1457
  modelScope: input.modelScope,
1448
1458
  modelResponseAliases: input.deps.config.modelResponseAliases,
1449
1459
  interactive: input.ctx.hasUI,
1450
1460
  permissions: input.deps.config.permissions,
1451
1461
  childRuntime: input.deps.childRuntime,
1462
+ projectTrusted: sessionProjectTrust(input.ctx),
1452
1463
  }),
1453
1464
  cwd: input.effectiveCwd,
1454
1465
  artifactsDir,
@@ -1708,11 +1719,13 @@ async function resumeAsyncRun(input) {
1708
1719
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1709
1720
  currentModelProvider: parentModel?.provider,
1710
1721
  currentModel: parentModel,
1722
+ scopedModelIds: scopedModelIdsFromContext(input.ctx),
1711
1723
  modelScope,
1712
1724
  modelResponseAliases: input.deps.config.modelResponseAliases,
1713
1725
  interactive: input.ctx.hasUI,
1714
1726
  permissions: input.deps.config.permissions,
1715
1727
  childRuntime: input.deps.childRuntime,
1728
+ projectTrusted: sessionProjectTrust(input.ctx),
1716
1729
  }),
1717
1730
  availableModels,
1718
1731
  cwd: effectiveCwd,
@@ -1836,12 +1849,14 @@ async function resumeAsyncRun(input) {
1836
1849
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
1837
1850
  currentModelProvider: parentModel?.provider,
1838
1851
  currentModel: parentModel,
1852
+ scopedModelIds: scopedModelIdsFromContext(input.ctx),
1839
1853
  modelScope,
1840
1854
  // Absence in the retained contract is meaningful; never acquire current aliases.
1841
1855
  modelResponseAliases: recoveryDescriptor ? recoveryDescriptor.modelResponseAliases : foregroundContract?.modelResponseAliases,
1842
1856
  interactive: input.ctx.hasUI,
1843
1857
  permissions: input.deps.config.permissions,
1844
1858
  childRuntime: input.deps.childRuntime,
1859
+ projectTrusted: sessionProjectTrust(input.ctx),
1845
1860
  }),
1846
1861
  cwd: effectiveCwd,
1847
1862
  maxOutput: input.params.maxOutput ?? recoveryDescriptor?.maxOutput,
@@ -1937,67 +1952,34 @@ async function resumeAsyncRun(input) {
1937
1952
  if (input.params.workflowParentRunId !== undefined && result.details.asyncDir) {
1938
1953
  const asyncDir = result.details.asyncDir;
1939
1954
  const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
1940
- const stopOnAbort = () => { stopAsyncRun(input.deps.state, revivedId, input.deps.kill, { asyncDir, resolvedId: revivedId }); };
1941
- if (input.signal?.aborted)
1942
- stopOnAbort();
1943
- else
1944
- input.signal?.addEventListener("abort", stopOnAbort, { once: true });
1945
- let completed;
1946
- try {
1947
- completed = await waitForImportedAsyncRoot({ runId: revivedId, asyncDir, resultPath, index: 0 });
1948
- }
1949
- finally {
1950
- input.signal?.removeEventListener("abort", stopOnAbort);
1951
- }
1952
- if (completed.importedPublication)
1953
- removeWorkflowAwaitedResult(asyncDir, resultPath, revivedId, completed.importedPublication);
1954
- emitWorkflowAwaitedChildComplete(input.deps.pi, input.deps.state, revivedId, asyncDir, input.params.workflowParentRunId, completed);
1955
- const usage = importedAsyncRootUsage(completed);
1956
- const childResult = {
1957
- index: 0,
1958
- agent: completed.agent,
1959
- ...(completed.sessionName ? { sessionName: completed.sessionName } : {}),
1960
- task: effectiveFollowUp,
1961
- exitCode: completed.exitCode,
1962
- usage,
1963
- finalOutput: completed.output,
1964
- outputState: completed.output.trim() ? "present" : "absent",
1965
- ...(completed.error ? { error: completed.error } : {}),
1966
- ...(completed.timedOut ? { timedOut: true } : {}),
1967
- ...(completed.stopped ? { stopped: true } : {}),
1968
- ...(completed.sessionFile ? { sessionFile: completed.sessionFile } : {}),
1969
- ...(completed.model ? { model: completed.model } : {}),
1970
- ...(completed.requestedModel ? { requestedModel: completed.requestedModel } : {}),
1971
- ...(completed.contextOverflow ? { contextOverflow: true } : {}),
1972
- ...(completed.structuredOutput !== undefined ? { structuredOutput: completed.structuredOutput } : {}),
1973
- ...(completed.structuredOutputPath ? { structuredOutputPath: completed.structuredOutputPath } : {}),
1974
- ...(completed.structuredOutputSchemaPath ? { structuredOutputSchemaPath: completed.structuredOutputSchemaPath } : {}),
1975
- ...(completed.acceptance ? { acceptance: completed.acceptance } : {}),
1976
- ...(completed.artifactPaths ? { artifactPaths: completed.artifactPaths } : {}),
1977
- ...(completed.savedOutputPath ? { savedOutputPath: completed.savedOutputPath } : {}),
1978
- ...(completed.outputSaveError ? { outputSaveError: completed.outputSaveError } : {}),
1979
- ...(completed.transcriptPath ? { transcriptPath: completed.transcriptPath } : {}),
1980
- ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
1981
- };
1982
- return {
1983
- content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Revived ${target.source} subagent ${revivedId} completed without output.` : completed.error || completed.output || `Revived ${target.source} subagent ${revivedId} completed without output.` }],
1984
- ...(completed.success ? {} : { isError: true }),
1985
- details: {
1986
- ...result.details,
1987
- runId: revivedId,
1988
- results: [childResult],
1989
- ...(target.launchContractDigest ? { sourceLaunchContractDigest: target.launchContractDigest } : {}),
1990
- },
1991
- };
1955
+ const stopListener = stopAwaitedAsyncChildOnAbort(input.signal, input.deps.state, revivedId, asyncDir, input.deps.kill);
1956
+ const completed = await waitForImportedAsyncRoot({ runId: revivedId, asyncDir, resultPath, index: 0 }, {
1957
+ shouldAbort: () => input.signal?.aborted === true,
1958
+ timeoutMessage: "Workflow stopped before async child completed.",
1959
+ abortedAsStopped: true,
1960
+ }).finally(stopListener.remove);
1961
+ const details = { ...result.details };
1962
+ if (target.launchContractDigest)
1963
+ details.sourceLaunchContractDigest = target.launchContractDigest;
1964
+ return importWorkflowAwaitedChildResult(completed, { runId: revivedId, asyncDir, resultPath, task: effectiveFollowUp, parentWorkflowRunId: input.params.workflowParentRunId, details, emptyOutputText: `Revived ${target.source} subagent ${revivedId} completed without output.`, state: input.deps.state, pi: input.deps.pi });
1992
1965
  }
1993
1966
  const revivedTarget = intercomBridge.active ? resolveSubagentIntercomTarget(revivedId, target.agent, 0) : undefined;
1994
1967
  const sourceLabel = target.source;
1968
+ let workflowRevivalWarning;
1969
+ try {
1970
+ if (target.source === "async")
1971
+ recordWorkflowRevival(DIRS.async, target.runId, revivedId);
1972
+ }
1973
+ catch (error) {
1974
+ workflowRevivalWarning = `Warning: workflow key link not recorded: ${error instanceof Error ? error.message : String(error)}`;
1975
+ }
1995
1976
  const lines = [
1996
1977
  `Revived ${sourceLabel} subagent from ${target.runId}.`,
1997
1978
  `Revived run: ${revivedId}`,
1998
1979
  `Agent: ${target.agent}`,
1999
1980
  `Session: ${target.sessionFile}`,
2000
1981
  result.details.asyncDir ? `Async dir: ${result.details.asyncDir}` : undefined,
1982
+ workflowRevivalWarning,
2001
1983
  revivedTarget ? `Intercom target: ${revivedTarget} (if registered)` : undefined,
2002
1984
  `Status if needed: subagent({ action: "status", id: "${revivedId}" })`,
2003
1985
  ].filter((line) => Boolean(line));
@@ -2733,7 +2715,7 @@ function toExecutionErrorResult(params, error, contextSummary) {
2733
2715
  function resolveStaticLaunchSummary(input) {
2734
2716
  const agentConfig = input.agents.find((agent) => agent.name === input.agent);
2735
2717
  const externalRunner = agentConfig?.runner?.type === "external-cli" || agentConfig?.runner?.type === "external-job";
2736
- const modelScopes = resolveModelScopesForAgent(input.modelScope, input.agent, input.parentModel);
2718
+ const modelScopes = resolveModelScopesForAgent(input.modelScope, input.agent, input.parentModel, input.scopedModelIds);
2737
2719
  const model = externalRunner
2738
2720
  ? undefined
2739
2721
  : resolveEffectiveSubagentModel(input.explicitModel, agentConfig?.model, input.parentModel, input.availableModels, agentConfig?.modelProvider ?? input.currentProvider, modelScopes.length === 0 ? {} : { scope: modelScopes });
@@ -2752,6 +2734,7 @@ function collectStaticLaunchSummaries(input) {
2752
2734
  explicitModel,
2753
2735
  agents: input.agents,
2754
2736
  parentModel: input.parentModel,
2737
+ scopedModelIds: input.scopedModelIds,
2755
2738
  availableModels: input.availableModels,
2756
2739
  currentProvider: input.currentProvider,
2757
2740
  modelScope: input.modelScope,
@@ -2925,29 +2908,11 @@ function emitWorkflowAwaitedChildComplete(pi, state, runId, asyncDir, parentWork
2925
2908
  parentWorkflowRunId,
2926
2909
  });
2927
2910
  }
2928
- async function waitForWorkflowAsyncSingleResult(params, launchResult, options) {
2929
- if (params.workflowAwaitAsync !== true || !launchResult.details.asyncDir)
2930
- return launchResult;
2931
- const asyncDir = launchResult.details.asyncDir;
2932
- const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
2933
- const stopOnAbort = () => { stopAsyncRun(options.state, options.runId, options.kill, { asyncDir, resolvedId: options.runId }); };
2934
- if (options.signal?.aborted)
2935
- stopOnAbort();
2936
- else
2937
- options.signal?.addEventListener("abort", stopOnAbort, { once: true });
2938
- let completed;
2939
- try {
2940
- completed = await waitForImportedAsyncRoot({ runId: options.runId, asyncDir, resultPath, index: 0 }, omitUndefinedProperties({
2941
- shouldAbort: () => options.signal?.aborted === true,
2942
- timeoutMessage: "Workflow stopped before async child completed.",
2943
- }));
2944
- }
2945
- finally {
2946
- options.signal?.removeEventListener("abort", stopOnAbort);
2947
- }
2911
+ /** Consumes an awaited workflow child's published result: clears the payload, announces completion, and builds the tool result. */
2912
+ function importWorkflowAwaitedChildResult(completed, options) {
2948
2913
  if (completed.importedPublication)
2949
- removeWorkflowAwaitedResult(asyncDir, resultPath, options.runId, completed.importedPublication);
2950
- emitWorkflowAwaitedChildComplete(options.pi, options.state, options.runId, asyncDir, params.workflowParentRunId, completed);
2914
+ removeWorkflowAwaitedResult(options.asyncDir, options.resultPath, options.runId, completed.importedPublication);
2915
+ emitWorkflowAwaitedChildComplete(options.pi, options.state, options.runId, options.asyncDir, options.parentWorkflowRunId, completed);
2951
2916
  const usage = importedAsyncRootUsage(completed);
2952
2917
  const childResult = omitUndefinedProperties({
2953
2918
  index: 0,
@@ -2976,15 +2941,54 @@ async function waitForWorkflowAsyncSingleResult(params, launchResult, options) {
2976
2941
  ...(completed.transcriptError ? { transcriptError: completed.transcriptError } : {}),
2977
2942
  });
2978
2943
  return {
2979
- content: [{ type: "text", text: completed.success ? completed.output || completed.error || `Async workflow child ${options.runId} completed without output.` : completed.error || completed.output || `Async workflow child ${options.runId} completed without output.` }],
2944
+ content: [{ type: "text", text: (completed.success ? completed.output || completed.error : completed.error || completed.output) || (options.emptyOutputText ?? `Async workflow child ${options.runId} completed without output.`) }],
2980
2945
  ...(completed.success ? {} : { isError: true }),
2981
2946
  details: {
2982
- ...launchResult.details,
2947
+ ...options.details,
2983
2948
  runId: options.runId,
2984
2949
  results: [childResult],
2985
2950
  },
2986
2951
  };
2987
2952
  }
2953
+ // A replaced runtime leaves the child running so a relaunch of the workflow can re-attach to it.
2954
+ function stopAwaitedAsyncChildOnAbort(signal, state, runId, asyncDir, kill) {
2955
+ const stopOnAbort = () => { if (!workflowStopCause(signal?.reason))
2956
+ stopAsyncRun(state, runId, kill, { asyncDir, resolvedId: runId }); };
2957
+ if (signal?.aborted)
2958
+ stopOnAbort();
2959
+ else
2960
+ signal?.addEventListener("abort", stopOnAbort, { once: true });
2961
+ return { remove: () => signal?.removeEventListener("abort", stopOnAbort) };
2962
+ }
2963
+ // Live workflow controls are keyed by the full run id; a stop id may be a prefix or the launch tool-call id.
2964
+ function resolveLiveWorkflowRunId(deps, id) {
2965
+ const controllers = deps.state.workflowControllers;
2966
+ if (!controllers?.size)
2967
+ return undefined;
2968
+ if (controllers.has(id))
2969
+ return id;
2970
+ try {
2971
+ const resolved = resolveSubagentRunId(id, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) }));
2972
+ return resolved?.kind === "async" && controllers.has(resolved.id) ? resolved.id : undefined;
2973
+ }
2974
+ catch {
2975
+ // The generic stop path resolves the id again and reports the error.
2976
+ return undefined;
2977
+ }
2978
+ }
2979
+ async function waitForWorkflowAsyncSingleResult(params, launchResult, options) {
2980
+ if (params.workflowAwaitAsync !== true || !launchResult.details.asyncDir)
2981
+ return launchResult;
2982
+ const asyncDir = launchResult.details.asyncDir;
2983
+ const resultPath = workflowAwaitedAsyncResultPath(asyncDir);
2984
+ const stopListener = stopAwaitedAsyncChildOnAbort(options.signal, options.state, options.runId, asyncDir, options.kill);
2985
+ const completed = await waitForImportedAsyncRoot({ runId: options.runId, asyncDir, resultPath, index: 0 }, omitUndefinedProperties({
2986
+ shouldAbort: () => options.signal?.aborted === true,
2987
+ timeoutMessage: "Workflow stopped before async child completed.",
2988
+ abortedAsStopped: true,
2989
+ })).finally(stopListener.remove);
2990
+ return importWorkflowAwaitedChildResult(completed, { runId: options.runId, asyncDir, resultPath, task: options.task, parentWorkflowRunId: params.workflowParentRunId, details: launchResult.details, state: options.state, pi: options.pi });
2991
+ }
2988
2992
  async function runAsyncPath(data, deps) {
2989
2993
  const { params, effectiveCwd, agents, ctx, shareEnabled, sessionRoot, sessionFileForTask, thinkingOverrideForTask, artifactConfig, artifactsDir, effectiveAsync, controlConfig, intercomBridge, nestedRoute, contextPolicy, unknownAgentDiagnosticContext, } = data;
2990
2994
  const hasChain = (params.chain?.length ?? 0) > 0;
@@ -3008,11 +3012,13 @@ async function runAsyncPath(data, deps) {
3008
3012
  parentSessionId: data.parentPiSessionId,
3009
3013
  currentModelProvider: parentModel?.provider,
3010
3014
  currentModel: parentModel,
3015
+ scopedModelIds: data.scopedModelIds,
3011
3016
  modelScope: data.modelScope,
3012
3017
  modelResponseAliases: deps.config.modelResponseAliases,
3013
3018
  interactive: ctx.hasUI,
3014
3019
  permissions: deps.config.permissions,
3015
3020
  childRuntime: deps.childRuntime,
3021
+ projectTrusted: sessionProjectTrust(ctx),
3016
3022
  });
3017
3023
  const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
3018
3024
  const currentMaxSubagentDepth = resolveCurrentMaxSubagentDepth(deps.config.maxSubagentDepth, deps.childRuntime);
@@ -3040,7 +3046,7 @@ async function runAsyncPath(data, deps) {
3040
3046
  if ((a.runner?.type === "external-cli" || a.runner?.type === "external-job") && (params.fast ?? a.fast) === true) {
3041
3047
  return buildRequestedModeError(params, `Agent '${a.name}' uses runner.type='${a.runner.type}' and does not support fast mode.`);
3042
3048
  }
3043
- const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel);
3049
+ const modelScopes = resolveModelScopesForAgent(data.modelScope, a.name, parentModel, data.scopedModelIds);
3044
3050
  const modelOrigin = resolveModelOrigin({
3045
3051
  storedOrigin: params.modelOrigin,
3046
3052
  explicitModel: params.model,
@@ -3463,7 +3469,7 @@ async function runSinglePath(data, deps) {
3463
3469
  const parentModel = data.parentModel;
3464
3470
  const currentProvider = parentModel?.provider;
3465
3471
  const availableModels = ctx.modelRegistry.getAvailable().map(toModelInfo);
3466
- const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel);
3472
+ const modelScopes = resolveModelScopesForAgent(data.modelScope, agentConfig.name, parentModel, data.scopedModelIds);
3467
3473
  let task = typeof params.task === "string" ? params.task : "";
3468
3474
  const modelOrigin = resolveModelOrigin({
3469
3475
  storedOrigin: params.modelOrigin,
@@ -3569,6 +3575,24 @@ async function runSinglePath(data, deps) {
3569
3575
  let detachForeground;
3570
3576
  let childSessionControls;
3571
3577
  const foregroundControl = deps.state.foregroundControls.get(runId);
3578
+ const syncHerdrForegroundChild = () => {
3579
+ if (!foregroundControl?.parentWorkflowRunId)
3580
+ return;
3581
+ try {
3582
+ deps.pi.events.emit(HERDR_FOREGROUND_CONTROL_CHANGED_EVENT, { runId: foregroundControl.parentWorkflowRunId });
3583
+ }
3584
+ catch (error) {
3585
+ console.error("Failed to sync Herdr foreground child:", error);
3586
+ }
3587
+ };
3588
+ const finishTrackedForegroundChild = () => {
3589
+ if (!foregroundControl)
3590
+ return;
3591
+ const wasActive = foregroundControl.activeChildren?.has(0) === true;
3592
+ finishForegroundChild(foregroundControl, 0);
3593
+ if (wasActive)
3594
+ syncHerdrForegroundChild();
3595
+ };
3572
3596
  if (foregroundControl) {
3573
3597
  const thinking = resolveEffectiveThinking(modelOverride, thinkingOverrideForTask());
3574
3598
  beginForegroundChild(foregroundControl, omitUndefinedProperties({
@@ -3608,6 +3632,7 @@ async function runSinglePath(data, deps) {
3608
3632
  // Capture the owned mailbox before a child can start and finish between polling ticks.
3609
3633
  if (deps.childRuntime?.fanoutChild)
3610
3634
  deps.activateSupervisorTransport?.();
3635
+ syncHerdrForegroundChild();
3611
3636
  }
3612
3637
  const modelResponseAliases = deps.config.modelResponseAliases === undefined ? undefined : structuredClone(deps.config.modelResponseAliases);
3613
3638
  const forwardSingleUpdate = onUpdate
@@ -3628,6 +3653,7 @@ async function runSinglePath(data, deps) {
3628
3653
  const launched = await runSync(ctx.cwd, agents, params.agent, task, compactOptional({
3629
3654
  machine: foregroundMachine,
3630
3655
  parentProviderRegistry: ctx.modelRegistry,
3656
+ projectTrusted: sessionProjectTrust(ctx),
3631
3657
  remoteReads: foregroundMachine ? readsOverride : undefined,
3632
3658
  permissions: deps.config.permissions,
3633
3659
  runtimeSnapshotHost: deps.pi,
@@ -3641,6 +3667,7 @@ async function runSinglePath(data, deps) {
3641
3667
  cwd: singleCwd,
3642
3668
  requestedCwd: data.requestedCwd,
3643
3669
  signal,
3670
+ abortedAsStopped: params.workflowParentRunId !== undefined,
3644
3671
  interruptSignal: interruptController.signal,
3645
3672
  allowIntercomDetach: agentConfig.systemPrompt?.includes(INTERCOM_BRIDGE_MARKER) === true,
3646
3673
  intercomEvents: deps.pi.events,
@@ -3720,8 +3747,7 @@ async function runSinglePath(data, deps) {
3720
3747
  }
3721
3748
  finally {
3722
3749
  try {
3723
- if (foregroundControl)
3724
- finishForegroundChild(foregroundControl, 0);
3750
+ finishTrackedForegroundChild();
3725
3751
  }
3726
3752
  finally {
3727
3753
  removeForegroundControlIfIdle(deps.state, runId, deps.trackRetainedNestedRoute);
@@ -3752,8 +3778,7 @@ async function runSinglePath(data, deps) {
3752
3778
  if (!r?.detached) {
3753
3779
  if (!artifactConfig.enabled)
3754
3780
  cleanupStructuredOutputRuntime(structuredRuntime);
3755
- if (foregroundControl)
3756
- finishForegroundChild(foregroundControl, 0);
3781
+ finishTrackedForegroundChild();
3757
3782
  }
3758
3783
  }
3759
3784
  if (!r.detached) {
@@ -3855,7 +3880,7 @@ async function runSinglePath(data, deps) {
3855
3880
  };
3856
3881
  }
3857
3882
  function inferExecutionMode(params) {
3858
- if (params.workflowScript !== undefined)
3883
+ if (params.workflowScript !== undefined || params.workflow !== undefined)
3859
3884
  return "workflow";
3860
3885
  if ((params.chain?.length ?? 0) > 0)
3861
3886
  return "chain";
@@ -3936,16 +3961,19 @@ function workflowChildResult(key, result, childParams = {}, resumeState, forcedT
3936
3961
  ? result.details.results[0].finalOutput
3937
3962
  : receiptOutput;
3938
3963
  const childError = result.details.results.map((child) => child.error).find((error) => Boolean(error));
3939
- const failureErrorBase = childError && receiptOutput
3940
- ? receiptOutput.includes(childError) ? receiptOutput : `${childError}\n\n${receiptOutput}`
3941
- : childError || receiptOutput || output || "Child run failed.";
3964
+ const stopped = result.details.results.some((child) => child.stopped);
3965
+ // A stopped child's receipt text carries the run fan-out annotation; report the structured stop reason instead.
3966
+ const failureErrorBase = stopped && childError
3967
+ ? childError
3968
+ : childError && receiptOutput
3969
+ ? receiptOutput.includes(childError) ? receiptOutput : `${childError}\n\n${receiptOutput}`
3970
+ : childError || receiptOutput || output || "Child run failed.";
3942
3971
  const savedOutputEvidence = [...new Set(result.details.results.map((child) => child.savedOutputPath).filter((value) => Boolean(value)))]
3943
3972
  .filter((savedOutputPath) => !failureErrorBase.includes(savedOutputPath))
3944
3973
  .map((savedOutputPath) => `Saved output: ${savedOutputPath}`);
3945
3974
  const failureError = [failureErrorBase, ...savedOutputEvidence].join("\n");
3946
3975
  const detached = result.details.results.some((child) => child.detached);
3947
3976
  const interrupted = result.details.results.some((child) => child.interrupted);
3948
- const stopped = result.details.results.some((child) => child.stopped);
3949
3977
  const terminalOutcome = forcedTerminalOutcome
3950
3978
  ?? (result.details.results.some((child) => child.timedOut)
3951
3979
  ? { state: "partial", reason: "timeout" }
@@ -4047,9 +4075,9 @@ function workflowRunningChildrenSummary(children) {
4047
4075
  return running.length ? `${running.length} child ${running.length === 1 ? "run remains" : "runs remain"} running or uncollected: ${running.map((child) => `${child.key}=${child.runId}`).join(", ")}. These launch receipts contain no final child results.` : "";
4048
4076
  }
4049
4077
  function workflowResultChildren(children, status, includeFailureFields) {
4050
- return children.map((child) => {
4078
+ return withWorkflowRevivals(DIRS.async, status.runId, children).map((child) => {
4051
4079
  const sessionName = status.steps?.find((step) => step.workflowKey === child.key)?.sessionName;
4052
- return { workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(sessionName ? { sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, ...(child.state === "running" ? { state: "running" } : { success: child.ok }), ...(child.asyncDir ? { asyncDir: child.asyncDir } : {}), ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(includeFailureFields && child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(includeFailureFields && child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.outputArtifactPath || child.outputReference ? { artifactPaths: { outputPath: child.outputArtifactPath ?? child.outputReference } } : {}) };
4080
+ return { workflowKey: child.key, ...(child.agent ? { agent: child.agent } : {}), ...(child.runId ? { runId: child.runId } : {}), ...(child.revival ? { revival: formatWorkflowKeyRevival(child.revival) } : {}), ...(sessionName ? { sessionName } : {}), ...workflowChildAccountingFields(child), output: child.output, outputState: child.output.trim() || child.structuredOutput !== undefined ? "present" : "absent", structuredOutput: child.structuredOutput, ...(child.state === "running" ? { state: "running" } : { success: child.ok }), ...(child.asyncDir ? { asyncDir: child.asyncDir } : {}), ...(child.outputReference ? { outputReference: child.outputReference } : {}), ...(includeFailureFields && child.terminalOutcome ? { terminalOutcome: child.terminalOutcome } : {}), ...(child.outputPathMapping ? { outputPathMapping: child.outputPathMapping } : {}), ...(child.stopped ? { stopped: true } : {}), ...(child.interrupted ? { interrupted: true } : {}), ...(includeFailureFields && child.detached && status.state !== "complete" ? { detached: true } : {}), ...(child.outputArtifactPath || child.outputReference ? { artifactPaths: { outputPath: child.outputArtifactPath ?? child.outputReference } } : {}) };
4053
4081
  });
4054
4082
  }
4055
4083
  function workflowOutputPathMappingSummary(children) {
@@ -4200,7 +4228,7 @@ function resolveWorkflowResume(reference, deps, parentSessionFile, index) {
4200
4228
  }
4201
4229
  }
4202
4230
  function terminalWorkflowReceipt(workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps, resource, argsDigest) {
4203
- return buildWorkflowReceipt({ workflowRunId, state, children, workflowChildren, terminalOutcome, hostSteps, resource, ...(argsDigest ? { argsDigest } : {}) });
4231
+ return buildWorkflowReceipt({ workflowRunId, state, children: withWorkflowRevivals(DIRS.async, workflowRunId, children), workflowChildren, terminalOutcome, hostSteps, resource, ...(argsDigest ? { argsDigest } : {}) });
4204
4232
  }
4205
4233
  function workflowFailureTerminalOutcome(error, _children, usageBudget) {
4206
4234
  if (usageBudget?.exhausted)
@@ -4580,6 +4608,20 @@ export function createSubagentExecutor(deps) {
4580
4608
  const workflowPermitContexts = new WeakMap();
4581
4609
  const warnedArtifactPackageDirs = new Set();
4582
4610
  const scheduledOwnerExecutors = new Map();
4611
+ const disabledFeatures = resolveDisabledFeatureSurface(deps.config);
4612
+ const disabledFeatureResult = (params) => {
4613
+ const error = disabledFeatureUseError(params, disabledFeatures);
4614
+ if (!error)
4615
+ return undefined;
4616
+ return { content: [{ type: "text", text: error }], isError: true, details: { mode: params.action === undefined ? inferExecutionMode(params) : "management", results: [] } };
4617
+ };
4618
+ const admitEnabledWorkflowChildren = (calls) => {
4619
+ for (const call of calls) {
4620
+ const error = disabledFeatureUseError(call.params, disabledFeatures, `workflow child '${call.key}'`);
4621
+ if (error)
4622
+ throw new Error(error);
4623
+ }
4624
+ };
4583
4625
  const execute = async (_id, params, signal, onUpdate, ctx, preserveActiveSession = false, parentModelOverride) => {
4584
4626
  const workflowLaunchObserver = workflowLaunchObservers.get(params);
4585
4627
  const inheritedUsageBudget = workflowOwnedUsageBudgets.get(params);
@@ -4614,8 +4656,8 @@ export function createSubagentExecutor(deps) {
4614
4656
  : rememberParentModel(deps.state, resolveCurrentSessionId(ctx.sessionManager), currentParentModel)) ?? null;
4615
4657
  })();
4616
4658
  try {
4617
- if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined && requestParams.workflowScriptPath === undefined) {
4618
- throw new Error("preflight requires workflowScript or workflowScriptPath.");
4659
+ if (requestParams.preflight !== undefined && requestParams.workflowScript === undefined) {
4660
+ throw new Error("preflight requires workflow: true or a workflow script path.");
4619
4661
  }
4620
4662
  workflowPreflight = normalizeWorkflowPreflight(requestParams.preflight);
4621
4663
  if (workflowPreflight)
@@ -4662,8 +4704,12 @@ export function createSubagentExecutor(deps) {
4662
4704
  if (launchBlockingErrors.length > 0) {
4663
4705
  return buildRequestedModeError(requestParams, `Workflow '${_id}' validation failed before child launch; no children launched. ${launchBlockingErrors.map((error) => error.message).join(" ")}`);
4664
4706
  }
4665
- for (const warning of workflowValidation.warnings ?? [])
4666
- console.warn(`[pi-subagents] ${warning.message}`);
4707
+ // A package chain's per-step stop check defeats static launch counting; runtime fan-out enforcement still applies.
4708
+ const structuredResource = workflowResource?.provenance.name === "chain" || workflowResource?.provenance.name === "tasks";
4709
+ for (const warning of workflowValidation.warnings ?? []) {
4710
+ if (!(structuredResource && warning.kind === "dynamic-spawn-count"))
4711
+ console.warn(`[pi-subagents] ${warning.message}`);
4712
+ }
4667
4713
  const acceptanceErrors = validateAcceptanceInput(requestParams.acceptance);
4668
4714
  if (acceptanceErrors.length > 0)
4669
4715
  return buildRequestedModeError(requestParams, acceptanceErrors.join(" "));
@@ -4804,6 +4850,12 @@ export function createSubagentExecutor(deps) {
4804
4850
  const workflowCapabilityCeiling = intersectSubagentCapabilityCeilings(requestParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(currentSessionId));
4805
4851
  const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
4806
4852
  deps.state.completionOwnerId = completionOwnerId;
4853
+ const workflowScriptSourceDigest = workflowScriptDigest(requestParams.workflowScript);
4854
+ const workflowReuseSource = findWorkflowReuseSource(DIRS.async, currentSessionId, workflowScriptSourceDigest, workflowArgsDigest);
4855
+ const workflowRunEvidence = { ...workflowArgsEvidence, scriptDigest: workflowScriptSourceDigest };
4856
+ if (workflowReuseSource)
4857
+ workflowRunEvidence.reusedFrom = workflowReuseSource.runId;
4858
+ const workflowJournalFingerprints = new Map();
4807
4859
  try {
4808
4860
  fs.mkdirSync(asyncDir, { recursive: true });
4809
4861
  writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
@@ -4837,7 +4889,7 @@ export function createSubagentExecutor(deps) {
4837
4889
  ...(workflowCapabilityCeiling ? { admissionCapabilityCeiling: workflowCapabilityCeiling } : {}),
4838
4890
  steps: [],
4839
4891
  ...(workflowPreflight ? { preflight: workflowPreflight } : {}),
4840
- workflow: { trace: [], emits: [], console: [], ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}) },
4892
+ workflow: { trace: [], emits: [], console: [], ...workflowRunEvidence, ...(workflowResource ? { resource: workflowResource.provenance } : {}) },
4841
4893
  workflowChildren: workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "running", inventoryComplete: false }),
4842
4894
  runFanoutBudget: getRunFanoutBudgetSnapshot(workflowFanoutBudget),
4843
4895
  };
@@ -5381,6 +5433,8 @@ export function createSubagentExecutor(deps) {
5381
5433
  workflowSteps.set(entry.key, step);
5382
5434
  }
5383
5435
  const projectedStep = workflowSteps.get(entry.key);
5436
+ if (entry.reused && projectedStep)
5437
+ projectedStep.reused = true;
5384
5438
  if (entry.state === "stopped" && projectedStep) {
5385
5439
  appendWorkflowEvent({
5386
5440
  type: "subagent.child-status",
@@ -5414,7 +5468,7 @@ export function createSubagentExecutor(deps) {
5414
5468
  timeoutMs: timeout,
5415
5469
  signal: controller.signal,
5416
5470
  continueAfterAbortWhenChildrenSettled: (abortError) => {
5417
- if (abortError.message !== "Workflow stopped because the extension session was replaced or reloaded.")
5471
+ if (!workflowStopCause(abortError))
5418
5472
  return false;
5419
5473
  const activeAsyncChild = [...(deps.state.asyncJobs?.values() ?? [])].some((job) => job.parentWorkflowRunId === workflowRunId && (job.status === "queued" || job.status === "running"));
5420
5474
  const activeForegroundChild = [...deps.state.foregroundControls.values()].some((control) => control.parentWorkflowRunId === workflowRunId && (control.activeChildren?.size ?? 0) > 0);
@@ -5429,6 +5483,9 @@ export function createSubagentExecutor(deps) {
5429
5483
  ...(workflowState ? { state: workflowState } : {}),
5430
5484
  onTrace: updateTrace,
5431
5485
  onChildSettled: (notification) => {
5486
+ const journalFingerprint = workflowJournalFingerprints.get(notification.childKey);
5487
+ if (journalFingerprint)
5488
+ appendWorkflowChildJournal(asyncDir, { type: "settle", key: notification.childKey, fingerprint: journalFingerprint, result: notification.result });
5432
5489
  appendWorkflowEvent({
5433
5490
  type: "subagent.workflow.child_settled",
5434
5491
  childKey: notification.childKey,
@@ -5461,6 +5518,7 @@ export function createSubagentExecutor(deps) {
5461
5518
  } });
5462
5519
  },
5463
5520
  admit: async (calls, admissionSignal) => {
5521
+ admitEnabledWorkflowChildren(calls);
5464
5522
  await preflightWorkflowWorktrees({ workflowDefaults: workflowChildDefaults, defaultWorktree: deps.config.worktree, calls, ctxCwd: parentCwd, signal: admissionSignal, deadlineAt: workflowDeadlineAt });
5465
5523
  const outputClaims = workflowChildOutputClaims({ ctxCwd: parentCwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
5466
5524
  if (outputClaims.error)
@@ -5483,6 +5541,33 @@ export function createSubagentExecutor(deps) {
5483
5541
  appendWorkflowEvent({ type: "subagent.workflow.emit", value: emits.at(-1) });
5484
5542
  },
5485
5543
  launch: async (key, childParams, workflowSignal, admission) => {
5544
+ const journalFingerprint = workflowJournalFingerprints.get(key) ?? workflowChildFingerprint(childParams);
5545
+ workflowJournalFingerprints.set(key, journalFingerprint);
5546
+ const priorChild = workflowReuseSource ? matchWorkflowReuse(workflowReuseSource, key, journalFingerprint) : undefined;
5547
+ if (priorChild?.kind === "settled") {
5548
+ if (priorChild.result.runId)
5549
+ workflowChildRunIds.set(key, priorChild.result.runId);
5550
+ return { ...priorChild.result, reused: true };
5551
+ }
5552
+ if (priorChild?.kind === "started") {
5553
+ // The previous run's awaited async child may still be running: wait for that exact run
5554
+ // instead of launching again. Journal it first so another reload can re-attach too.
5555
+ const priorRunId = priorChild.runId;
5556
+ const priorAsyncDir = path.join(DIRS.async, priorRunId);
5557
+ appendWorkflowChildJournal(asyncDir, { type: "start", key, fingerprint: journalFingerprint, runId: priorRunId });
5558
+ const stopListener = stopAwaitedAsyncChildOnAbort(workflowSignal, deps.state, priorRunId, priorAsyncDir, deps.kill);
5559
+ const reattached = await awaitExistingAsyncRun(priorAsyncDir, priorRunId, workflowSignal).finally(stopListener.remove);
5560
+ const claimedPath = reattached.status === "settled" ? claimWorkflowAwaitedResult(priorAsyncDir, workflowRunId) : undefined;
5561
+ if (reattached.status === "settled" && claimedPath) {
5562
+ const imported = importWorkflowAwaitedChildResult(reattached.result, { runId: priorRunId, asyncDir: priorAsyncDir, resultPath: claimedPath, task: typeof childParams.task === "string" ? childParams.task : "", parentWorkflowRunId: workflowReuseSource?.runId, details: { mode: "single", asyncId: priorRunId, asyncDir: priorAsyncDir, results: [] }, state: deps.state, pi: deps.pi });
5563
+ const reattachedResult = workflowChildResult(key, imported, childParams, deps.state);
5564
+ // Like a settled journal match: only a successful child is reused; a failed or stopped one launches fresh.
5565
+ if (reattachedResult.ok) {
5566
+ workflowChildRunIds.set(key, priorRunId);
5567
+ return { ...reattachedResult, reused: true };
5568
+ }
5569
+ }
5570
+ }
5486
5571
  if (workflowUsageBudget.budget && childParams.async === true)
5487
5572
  return workflowChildResult(key, buildRequestedModeError(childParams, "workflow usageBudget does not support async runs.run launches."), childParams, deps.state);
5488
5573
  const budgetState = usageBudgetState(workflowUsageBudget.budget, sumResultsCost(workflowResults));
@@ -5504,6 +5589,8 @@ export function createSubagentExecutor(deps) {
5504
5589
  if (workflowUsageBudget.budget)
5505
5590
  workflowOwnedUsageBudgets.set(childRequest, workflowUsageBudget.budget);
5506
5591
  workflowLaunchObservers.set(childRequest, (launch) => {
5592
+ if (launch.runId)
5593
+ appendWorkflowChildJournal(asyncDir, { type: "start", key, fingerprint: journalFingerprint, runId: launch.runId });
5507
5594
  const step = status.steps?.find((candidate) => candidate.workflowKey === key);
5508
5595
  if (step) {
5509
5596
  step.agent = launch.agent;
@@ -5605,7 +5692,7 @@ export function createSubagentExecutor(deps) {
5605
5692
  const workflowUsage = sumResultsUsage(workflowResults);
5606
5693
  settleWorkflowSteerInbox("complete");
5607
5694
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState: "completed", inventoryComplete: true, trace: workflow.trace, children: workflow.children, steps: status.steps });
5608
- status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
5695
+ status = { ...status, state: "complete", endedAt: Date.now(), workflow: { value: workflow.value, trace: finalPreflightTrace, emits: workflow.emits, console: workflow.console, ...workflowRunEvidence, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren, totalTokens: { input: workflowUsage.input, output: workflowUsage.output, total: workflowUsage.input + workflowUsage.output }, totalCost: sumResultsCost(workflowResults) };
5609
5696
  const receipt = terminalWorkflowReceipt(workflowRunId, "complete", workflow.children, workflowChildren, undefined, validHostStepNodes(status.workflowGraph), workflowResource?.provenance, workflowArgsDigest);
5610
5697
  delete status.workflowReceiptPath;
5611
5698
  let workflowReceipt;
@@ -5628,6 +5715,9 @@ export function createSubagentExecutor(deps) {
5628
5715
  catch (error) {
5629
5716
  const partial = error instanceof WorkflowScriptError ? error.partial : { trace: [], emits: [], console: [], children: [] };
5630
5717
  const stopped = controller.signal.aborted;
5718
+ const stopCause = stopped ? workflowStopCause(controller.signal.reason) : undefined;
5719
+ if (stopCause)
5720
+ workflowRunEvidence.stopCause = stopCause;
5631
5721
  const detachedChildKeys = new Set(partial.children.filter((child) => child.detached).map((child) => child.key));
5632
5722
  const hasRealFailedChild = partial.children.some((child) => !child.ok && child.state !== "running" && !child.detached);
5633
5723
  const pauseForDetached = !stopped && error instanceof WorkflowScriptError && error.errorKind === "detached-child" && detachedChildKeys.size > 0 && !hasRealFailedChild;
@@ -5650,7 +5740,7 @@ export function createSubagentExecutor(deps) {
5650
5740
  const workflowChildren = workflowChildSummary({ parentToolCallId: toolCallId, workflowRunId, workflowState, inventoryComplete: true, trace: partial.trace, children: partial.children, steps: status.steps });
5651
5741
  const finalPreflightWarnings = workflowPreflightWarnings(workflowPreflight, partial.trace, { settled: true });
5652
5742
  const finalPreflightTrace = annotateWorkflowPreflightTrace(partial.trace, workflowPreflight);
5653
- status = compactOptional({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(error instanceof WorkflowScriptError && error.errorKind ? { failureKind: error.errorKind } : {}), ...(workflowArgsEvidence ?? {}), ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
5743
+ status = compactOptional({ ...status, state, stopped: stopped || undefined, activityState: pauseForDetached || status.activityState === "needs_attention" ? "needs_attention" : undefined, error: workflowFailureMessage(error, workflowRunId, partial.children), endedAt: Date.now(), workflow: { trace: finalPreflightTrace, emits: partial.emits, console: partial.console, ...(error instanceof WorkflowScriptError && error.errorKind ? { failureKind: error.errorKind } : {}), ...workflowRunEvidence, ...(workflowResource ? { resource: workflowResource.provenance } : {}), ...(finalPreflightWarnings.length ? { preflightWarnings: finalPreflightWarnings } : {}) }, workflowChildren });
5654
5744
  if (pauseForDetached) {
5655
5745
  const promoted = promotePausedWorkflowIfSettled(status);
5656
5746
  if (promoted)
@@ -5660,7 +5750,7 @@ export function createSubagentExecutor(deps) {
5660
5750
  const errorPreview = status.error && status.error.length > 1_000 ? `${status.error.slice(0, 1_000)}… (truncated; full error: ${statusPath} (error))` : status.error;
5661
5751
  const terminalSummary = `${status.state === "complete"
5662
5752
  ? "Workflow completed after detached child finished."
5663
- : errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5753
+ : errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${stopCause ? ` ${WORKFLOW_RUNTIME_REPLACED_RELAUNCH_NOTICE}` : ""}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5664
5754
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
5665
5755
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
5666
5756
  const receiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
@@ -5774,6 +5864,7 @@ export function createSubagentExecutor(deps) {
5774
5864
  sendWorkflowProgress();
5775
5865
  },
5776
5866
  admit: async (calls, admissionSignal) => {
5867
+ admitEnabledWorkflowChildren(calls);
5777
5868
  await preflightWorkflowWorktrees({ workflowDefaults: workflowChildDefaults, defaultWorktree: deps.config.worktree, calls, ctxCwd: ctx.cwd, signal: admissionSignal, deadlineAt: workflowDeadlineAt });
5778
5869
  const outputClaims = workflowChildOutputClaims({ ctxCwd: ctx.cwd, workflowCwd, artifactsDir: workflowArtifactsDir, workflowRunId: foregroundWorkflowRunId, aggregateOutputPath: workflowAggregateOutputPath, configuredOutputBaseDir, discoverAgents: discoverWorkflowAgents, agents: workflowAgents, workflowAgentScope: workflowChildDefaults.agentScope, state: deps.state, claimedOutputPaths, entries: calls });
5779
5870
  if (outputClaims.error)
@@ -6245,8 +6336,10 @@ export function createSubagentExecutor(deps) {
6245
6336
  }
6246
6337
  if (action === "guide") {
6247
6338
  try {
6339
+ const guide = readSubagentGuide(paramsWithResolvedCwd.topic);
6340
+ const notice = paramsWithResolvedCwd.topic === "tool-reference" ? disabledFeatureNotice(disabledFeatures) : undefined;
6248
6341
  return {
6249
- content: [{ type: "text", text: readSubagentGuide(paramsWithResolvedCwd.topic) }],
6342
+ content: [{ type: "text", text: notice ? `${notice}\n\n${guide}` : guide }],
6250
6343
  details: { mode: "management", results: [] },
6251
6344
  };
6252
6345
  }
@@ -6476,7 +6569,7 @@ export function createSubagentExecutor(deps) {
6476
6569
  }
6477
6570
  if (action.startsWith("schedule.")) {
6478
6571
  if (!isScheduledRunAction(action)) {
6479
- return { content: [{ type: "text", text: unknownSubagentActionMessage(action) }], isError: true, details: { mode: "management", results: [] } };
6572
+ return { content: [{ type: "text", text: unknownSubagentActionMessage(action, disabledFeatures) }], isError: true, details: { mode: "management", results: [] } };
6480
6573
  }
6481
6574
  if (deps.allowMutatingManagementActions === false && MUTATING_MANAGEMENT_ACTIONS.has(action)) {
6482
6575
  return {
@@ -6520,55 +6613,47 @@ export function createSubagentExecutor(deps) {
6520
6613
  }
6521
6614
  if (action === "stop") {
6522
6615
  const targetRunId = paramsWithResolvedCwd.runId ?? paramsWithResolvedCwd.id;
6523
- const workflowController = targetRunId ? deps.state.workflowControllers?.get(targetRunId) : undefined;
6524
- if (workflowController && targetRunId) {
6525
- const stopChild = deps.state.workflowChildStops?.get(targetRunId);
6526
- if (paramsWithResolvedCwd.childId !== undefined) {
6527
- const workflowRunId = targetRunId;
6528
- const asyncJob = deps.state.asyncJobs.get(workflowRunId);
6529
- if (!asyncJob?.asyncDir)
6530
- return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
6531
- const status = readStatus(asyncJob.asyncDir);
6532
- if (!status)
6533
- return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
6534
- const resolution = resolveAsyncStatusChild(status, paramsWithResolvedCwd.childId);
6535
- if (!resolution.ok)
6536
- return { content: [{ type: "text", text: resolution.message }], isError: true, details: { mode: "management", results: [] } };
6537
- if (!isStoppableAsyncStatusStep(resolution.child.step))
6538
- return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in async run '${targetRunId}' is ${resolution.child.step.status}; stop only supports pending or running children.` }], isError: true, details: { mode: "management", results: [] } };
6539
- if (!stopChild)
6540
- return { content: [{ type: "text", text: `Workflow ${targetRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
6541
- if (!stopChild(resolution.child.id, `Workflow child '${resolution.child.id}' stopped.`))
6542
- return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
6543
- try {
6544
- fs.appendFileSync(path.join(asyncJob.asyncDir, "events.jsonl"), `${JSON.stringify({
6545
- type: "subagent.child-status",
6546
- version: 1,
6547
- runId: workflowRunId,
6548
- childId: resolution.child.id,
6549
- status: "stopping",
6550
- ts: Date.now(),
6551
- reason: "subagent-action",
6552
- source: "async",
6553
- stepIndex: resolution.child.index,
6554
- agent: resolution.child.step.agent,
6555
- ...(resolution.child.step.runId ? { childRunId: resolution.child.step.runId } : {}),
6556
- ...(resolution.child.step.workflowKey ? { workflowKey: resolution.child.step.workflowKey } : {}),
6557
- ...(resolution.child.step.phase ? { phase: resolution.child.step.phase } : {}),
6558
- ...(resolution.child.step.label ? { label: resolution.child.step.label } : {}),
6559
- })}\n`, "utf-8");
6560
- }
6561
- catch (error) {
6562
- console.error(`Failed to append child status event for workflow ${workflowRunId}:`, error);
6563
- }
6564
- return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
6616
+ // Whole-workflow stops reach stopAsyncRun below, which stops a live workflow in-process.
6617
+ const workflowRunId = targetRunId && paramsWithResolvedCwd.childId !== undefined ? resolveLiveWorkflowRunId(deps, targetRunId) : undefined;
6618
+ if (workflowRunId && paramsWithResolvedCwd.childId !== undefined) {
6619
+ const stopChild = deps.state.workflowChildStops?.get(workflowRunId);
6620
+ const asyncJob = deps.state.asyncJobs.get(workflowRunId);
6621
+ if (!asyncJob?.asyncDir)
6622
+ return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
6623
+ const status = readStatus(asyncJob.asyncDir);
6624
+ if (!status)
6625
+ return { content: [{ type: "text", text: `Status file not found for async workflow '${workflowRunId}'.` }], isError: true, details: { mode: "management", results: [] } };
6626
+ const resolution = resolveAsyncStatusChild(status, paramsWithResolvedCwd.childId);
6627
+ if (!resolution.ok)
6628
+ return { content: [{ type: "text", text: resolution.message }], isError: true, details: { mode: "management", results: [] } };
6629
+ if (!isStoppableAsyncStatusStep(resolution.child.step))
6630
+ return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in async run '${workflowRunId}' is ${resolution.child.step.status}; stop only supports pending or running children.` }], isError: true, details: { mode: "management", results: [] } };
6631
+ if (!stopChild)
6632
+ return { content: [{ type: "text", text: `Workflow ${workflowRunId} child stop is unavailable in this extension runtime.` }], isError: true, details: { mode: "management", results: [] } };
6633
+ if (!stopChild(resolution.child.id, `Workflow child '${resolution.child.id}' stopped.`))
6634
+ return { content: [{ type: "text", text: `Child '${paramsWithResolvedCwd.childId}' in workflow ${workflowRunId} is not available to stop.` }], isError: true, details: { mode: "management", results: [] } };
6635
+ try {
6636
+ fs.appendFileSync(path.join(asyncJob.asyncDir, "events.jsonl"), `${JSON.stringify({
6637
+ type: "subagent.child-status",
6638
+ version: 1,
6639
+ runId: workflowRunId,
6640
+ childId: resolution.child.id,
6641
+ status: "stopping",
6642
+ ts: Date.now(),
6643
+ reason: "subagent-action",
6644
+ source: "async",
6645
+ stepIndex: resolution.child.index,
6646
+ agent: resolution.child.step.agent,
6647
+ ...(resolution.child.step.runId ? { childRunId: resolution.child.step.runId } : {}),
6648
+ ...(resolution.child.step.workflowKey ? { workflowKey: resolution.child.step.workflowKey } : {}),
6649
+ ...(resolution.child.step.phase ? { phase: resolution.child.step.phase } : {}),
6650
+ ...(resolution.child.step.label ? { label: resolution.child.step.label } : {}),
6651
+ })}\n`, "utf-8");
6652
+ }
6653
+ catch (error) {
6654
+ console.error(`Failed to append child status event for workflow ${workflowRunId}:`, error);
6565
6655
  }
6566
- const asyncJob = deps.state.asyncJobs.get(targetRunId);
6567
- const status = asyncJob?.asyncDir ? readStatus(asyncJob.asyncDir) : undefined;
6568
- if (status)
6569
- stopStoppableAsyncStatusChildren(status, stopChild, "Workflow stopped.");
6570
- workflowController.abort(new Error("Workflow stopped."));
6571
- return { content: [{ type: "text", text: `Stop requested for async workflow ${targetRunId}.` }], details: { mode: "management", results: [] } };
6656
+ return { content: [{ type: "text", text: `Stop requested for child ${resolution.child.id} in async workflow ${workflowRunId}.` }], details: { mode: "management", results: [] } };
6572
6657
  }
6573
6658
  let resolved;
6574
6659
  if (paramsWithResolvedCwd.dir) {
@@ -6646,7 +6731,7 @@ export function createSubagentExecutor(deps) {
6646
6731
  }
6647
6732
  if (!SUBAGENT_ACTIONS.includes(action)) {
6648
6733
  return {
6649
- content: [{ type: "text", text: unknownSubagentActionMessage(action) }],
6734
+ content: [{ type: "text", text: unknownSubagentActionMessage(action, disabledFeatures) }],
6650
6735
  isError: true,
6651
6736
  details: { mode: "management", results: [] },
6652
6737
  };
@@ -7009,6 +7094,7 @@ export function createSubagentExecutor(deps) {
7009
7094
  contextPolicy,
7010
7095
  modelScope,
7011
7096
  parentModel: requestParentModel,
7097
+ scopedModelIds: scopedModelIdsFromContext(ctx),
7012
7098
  parentSessionId: requestSessionId,
7013
7099
  parentPiSessionId: requestPiSessionId,
7014
7100
  capabilityCeiling: intersectSubagentCapabilityCeilings(effectiveParams.capabilityCeiling, resolveCurrentSubagentCapabilityCeiling(requestSessionId)),
@@ -7102,6 +7188,7 @@ export function createSubagentExecutor(deps) {
7102
7188
  params: effectiveParams,
7103
7189
  agents,
7104
7190
  parentModel: requestParentModel,
7191
+ scopedModelIds: scopedModelIdsFromContext(ctx),
7105
7192
  availableModels: ctx.modelRegistry.getAvailable().map(toModelInfo),
7106
7193
  currentProvider: requestParentModel?.provider,
7107
7194
  modelScope,
@@ -7254,33 +7341,57 @@ export function createSubagentExecutor(deps) {
7254
7341
  }
7255
7342
  };
7256
7343
  const executePublic = (id, params, signal, onUpdate, ctx) => {
7257
- const normalized = normalizePublicSubagentExecution(params);
7344
+ const disabledFeatureError = disabledFeatureResult(params);
7345
+ if (disabledFeatureError)
7346
+ return Promise.resolve(disabledFeatureError);
7347
+ const normalized = normalizePublicSubagentExecution(params, { structuredWorkflows: disabledFeatures.features.has("workflow-scripts") });
7258
7348
  if (!normalized.ok) {
7259
7349
  return Promise.resolve({ content: [{ type: "text", text: normalized.error }], isError: true, details: { mode: normalized.mode, results: [] } });
7260
7350
  }
7261
7351
  let publicParams = normalized.params;
7262
- if (publicParams.workflow !== undefined) {
7263
- const resolved = resolveWorkflowResource(publicParams.workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
7352
+ const workflow = publicParams.workflow;
7353
+ const errorResult = (text) => Promise.resolve({ content: [{ type: "text", text }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
7354
+ // Models tend to put script text in the workflow string; say how to pass it instead.
7355
+ const scriptTextHint = " A workflow string is a named workflow resource or a script file path. To run script text, write it in one ```js workflow block in the same reply and call subagent({ workflow: true }).";
7356
+ if (publicParams.tasks !== undefined || publicParams.chain !== undefined) {
7357
+ // Normalization admits chain/tasks only with workflow scripts disabled; the original input was checked above.
7358
+ const kind = publicParams.tasks !== undefined ? "tasks" : "chain";
7359
+ const resolved = resolveStructuredWorkflowResource({ kind, steps: publicParams[kind], task: publicParams.task });
7264
7360
  if (!resolved.ok)
7265
- return Promise.resolve({ content: [{ type: "text", text: resolved.error }], isError: true, details: { mode: "workflow", results: [] } });
7361
+ return errorResult(resolved.error);
7362
+ const { tasks: _tasks, chain: _chain, task: _task, ...withoutStructuredInput } = publicParams;
7363
+ publicParams = { ...withoutStructuredInput, workflowScript: resolved.resource.script };
7364
+ workflowResourcePermits.set(publicParams, resolved.resource.permit);
7365
+ }
7366
+ else if (typeof workflow === "string" && !isWorkflowScriptPath(workflow)) {
7367
+ const resolved = resolveWorkflowResource(workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
7368
+ if (!resolved.ok)
7369
+ return Promise.resolve({ content: [{ type: "text", text: resolved.error + scriptTextHint }], isError: true, details: { mode: "workflow", results: [] } });
7266
7370
  const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
7267
7371
  publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
7268
7372
  workflowResourcePermits.set(publicParams, resolved.resource.permit);
7269
7373
  }
7270
- else if (publicParams.workflowScript !== undefined || publicParams.workflowScriptPath !== undefined) {
7374
+ else if (workflow !== undefined || publicParams.workflowScript !== undefined) {
7271
7375
  const normalizedArgs = normalizeWorkflowArgs(publicParams.args);
7272
7376
  if ("error" in normalizedArgs)
7273
- return Promise.resolve({ content: [{ type: "text", text: normalizedArgs.error }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
7274
- publicParams = { ...publicParams, args: deepFreezeWorkflowArgs(normalizedArgs.args) };
7275
- }
7276
- const loaded = loadWorkflowScriptPath(publicParams, ctx.cwd);
7277
- if (loaded.error) {
7278
- return Promise.resolve({ content: [{ type: "text", text: loaded.error }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
7377
+ return errorResult(normalizedArgs.error);
7378
+ let workflowScript = publicParams.workflowScript;
7379
+ if (workflow !== undefined) {
7380
+ const source = workflow === true ? readReplyWorkflowScript(ctx.sessionManager, id) : readWorkflowScriptFile(workflow, publicParams.cwd, ctx.cwd);
7381
+ if ("error" in source)
7382
+ return errorResult(workflow === true ? source.error : source.error + scriptTextHint);
7383
+ workflowScript = source.script;
7384
+ }
7385
+ const { workflow: _workflow, ...withoutWorkflowSource } = publicParams;
7386
+ publicParams = { ...withoutWorkflowSource, workflowScript, args: deepFreezeWorkflowArgs(normalizedArgs.args) };
7279
7387
  }
7280
- publicExecutions.add(loaded.params);
7281
- return executeWithSingleDispatchGuard(id, loaded.params, signal, onUpdate, ctx);
7388
+ publicExecutions.add(publicParams);
7389
+ return executeWithSingleDispatchGuard(id, publicParams, signal, onUpdate, ctx);
7282
7390
  };
7283
7391
  const executeDelegated = async (id, params, signal, onUpdate, ctx) => {
7392
+ const disabledFeatureError = disabledFeatureResult(params);
7393
+ if (disabledFeatureError)
7394
+ return disabledFeatureError;
7284
7395
  const delegatedParams = { ...params };
7285
7396
  const privateParams = delegatedParams;
7286
7397
  const thinkingOverride = privateParams.delegatedThinkingOverride;