pi-subagents 0.77.0 → 0.79.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 (263) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/agents/evidence-auditor.md +1 -0
  3. package/agents/oracle.md +1 -0
  4. package/agents/reviewer.md +1 -0
  5. package/docs/agents.md +4 -2
  6. package/docs/configuration.md +22 -6
  7. package/docs/extension-api.md +9 -1
  8. package/docs/observability.md +11 -3
  9. package/docs/tool-reference.md +14 -3
  10. package/docs/workflows.md +6 -4
  11. package/package.json +1 -1
  12. package/skills/pi-subagents/SKILL.md +2 -1
  13. package/skills/pi-subagents/references/execution-controls.md +2 -0
  14. package/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  15. package/src/api/external-runs.d.ts.map +1 -1
  16. package/src/api/external-runs.js +5 -4
  17. package/src/api/external-runs.js.map +1 -1
  18. package/src/api/preflight.js +1 -1
  19. package/src/api/preflight.js.map +1 -1
  20. package/src/extension/active-runs-prompt.d.ts +8 -0
  21. package/src/extension/active-runs-prompt.d.ts.map +1 -0
  22. package/src/extension/active-runs-prompt.js +30 -0
  23. package/src/extension/active-runs-prompt.js.map +1 -0
  24. package/src/extension/config.d.ts.map +1 -1
  25. package/src/extension/config.js +6 -0
  26. package/src/extension/config.js.map +1 -1
  27. package/src/extension/declaration-pinning.d.ts +5 -0
  28. package/src/extension/declaration-pinning.d.ts.map +1 -1
  29. package/src/extension/declaration-pinning.js +24 -3
  30. package/src/extension/declaration-pinning.js.map +1 -1
  31. package/src/extension/index.d.ts +0 -1
  32. package/src/extension/index.d.ts.map +1 -1
  33. package/src/extension/index.js +73 -18
  34. package/src/extension/index.js.map +1 -1
  35. package/src/extension/public-execution.d.ts +1 -0
  36. package/src/extension/public-execution.d.ts.map +1 -1
  37. package/src/extension/public-execution.js +3 -0
  38. package/src/extension/public-execution.js.map +1 -1
  39. package/src/extension/rpc.d.ts.map +1 -1
  40. package/src/extension/rpc.js +5 -3
  41. package/src/extension/rpc.js.map +1 -1
  42. package/src/extension/schemas.d.ts +2 -0
  43. package/src/extension/schemas.d.ts.map +1 -1
  44. package/src/extension/schemas.js +1 -0
  45. package/src/extension/schemas.js.map +1 -1
  46. package/src/extension/tool-activation.d.ts.map +1 -1
  47. package/src/extension/tool-activation.js +25 -6
  48. package/src/extension/tool-activation.js.map +1 -1
  49. package/src/intercom/native-supervisor-channel.d.ts +18 -2
  50. package/src/intercom/native-supervisor-channel.d.ts.map +1 -1
  51. package/src/intercom/native-supervisor-channel.js +130 -15
  52. package/src/intercom/native-supervisor-channel.js.map +1 -1
  53. package/src/runs/background/active-async-capacity.d.ts +2 -1
  54. package/src/runs/background/active-async-capacity.d.ts.map +1 -1
  55. package/src/runs/background/active-async-capacity.js +10 -6
  56. package/src/runs/background/active-async-capacity.js.map +1 -1
  57. package/src/runs/background/async-execution.js +3 -3
  58. package/src/runs/background/async-execution.js.map +1 -1
  59. package/src/runs/background/async-resume.d.ts.map +1 -1
  60. package/src/runs/background/async-resume.js +5 -2
  61. package/src/runs/background/async-resume.js.map +1 -1
  62. package/src/runs/background/async-status-snapshot.d.ts.map +1 -1
  63. package/src/runs/background/async-status-snapshot.js +4 -2
  64. package/src/runs/background/async-status-snapshot.js.map +1 -1
  65. package/src/runs/background/async-status.d.ts.map +1 -1
  66. package/src/runs/background/async-status.js +10 -6
  67. package/src/runs/background/async-status.js.map +1 -1
  68. package/src/runs/background/chain-root-attachment.js +4 -4
  69. package/src/runs/background/chain-root-attachment.js.map +1 -1
  70. package/src/runs/background/control-channel.d.ts +1 -0
  71. package/src/runs/background/control-channel.d.ts.map +1 -1
  72. package/src/runs/background/control-channel.js +27 -3
  73. package/src/runs/background/control-channel.js.map +1 -1
  74. package/src/runs/background/inspect-rpc.d.ts +5 -0
  75. package/src/runs/background/inspect-rpc.d.ts.map +1 -1
  76. package/src/runs/background/inspect-rpc.js +20 -3
  77. package/src/runs/background/inspect-rpc.js.map +1 -1
  78. package/src/runs/background/notify.d.ts +1 -1
  79. package/src/runs/background/notify.d.ts.map +1 -1
  80. package/src/runs/background/notify.js +8 -4
  81. package/src/runs/background/notify.js.map +1 -1
  82. package/src/runs/background/result-delivery-ownership.d.ts +0 -2
  83. package/src/runs/background/result-delivery-ownership.d.ts.map +1 -1
  84. package/src/runs/background/result-delivery-ownership.js +5 -21
  85. package/src/runs/background/result-delivery-ownership.js.map +1 -1
  86. package/src/runs/background/result-files.d.ts +5 -3
  87. package/src/runs/background/result-files.d.ts.map +1 -1
  88. package/src/runs/background/result-files.js +10 -6
  89. package/src/runs/background/result-files.js.map +1 -1
  90. package/src/runs/background/result-watcher.d.ts.map +1 -1
  91. package/src/runs/background/result-watcher.js +15 -8
  92. package/src/runs/background/result-watcher.js.map +1 -1
  93. package/src/runs/background/resume-guidance.d.ts.map +1 -1
  94. package/src/runs/background/resume-guidance.js +3 -2
  95. package/src/runs/background/resume-guidance.js.map +1 -1
  96. package/src/runs/background/run-child-session.d.ts.map +1 -1
  97. package/src/runs/background/run-child-session.js +13 -17
  98. package/src/runs/background/run-child-session.js.map +1 -1
  99. package/src/runs/background/run-status.d.ts.map +1 -1
  100. package/src/runs/background/run-status.js +11 -4
  101. package/src/runs/background/run-status.js.map +1 -1
  102. package/src/runs/background/runner-aliases.d.ts.map +1 -1
  103. package/src/runs/background/runner-aliases.js +11 -8
  104. package/src/runs/background/runner-aliases.js.map +1 -1
  105. package/src/runs/background/subagent-runner.d.ts.map +1 -1
  106. package/src/runs/background/subagent-runner.js +212 -283
  107. package/src/runs/background/subagent-runner.js.map +1 -1
  108. package/src/runs/background/workflow-terminal-proof.d.ts +1 -1
  109. package/src/runs/background/workflow-terminal-proof.d.ts.map +1 -1
  110. package/src/runs/background/workflow-terminal-proof.js +12 -3
  111. package/src/runs/background/workflow-terminal-proof.js.map +1 -1
  112. package/src/runs/foreground/async-dismiss-action.d.ts.map +1 -1
  113. package/src/runs/foreground/async-dismiss-action.js +2 -1
  114. package/src/runs/foreground/async-dismiss-action.js.map +1 -1
  115. package/src/runs/foreground/async-steering-action.d.ts.map +1 -1
  116. package/src/runs/foreground/async-steering-action.js +3 -2
  117. package/src/runs/foreground/async-steering-action.js.map +1 -1
  118. package/src/runs/foreground/async-stop-action.d.ts +1 -0
  119. package/src/runs/foreground/async-stop-action.d.ts.map +1 -1
  120. package/src/runs/foreground/async-stop-action.js +9 -8
  121. package/src/runs/foreground/async-stop-action.js.map +1 -1
  122. package/src/runs/foreground/command-action.d.ts.map +1 -1
  123. package/src/runs/foreground/command-action.js +2 -1
  124. package/src/runs/foreground/command-action.js.map +1 -1
  125. package/src/runs/foreground/execution.d.ts.map +1 -1
  126. package/src/runs/foreground/execution.js +67 -60
  127. package/src/runs/foreground/execution.js.map +1 -1
  128. package/src/runs/foreground/subagent-executor.d.ts +2 -0
  129. package/src/runs/foreground/subagent-executor.d.ts.map +1 -1
  130. package/src/runs/foreground/subagent-executor.js +124 -67
  131. package/src/runs/foreground/subagent-executor.js.map +1 -1
  132. package/src/runs/shared/async-status-projection.d.ts +4 -0
  133. package/src/runs/shared/async-status-projection.d.ts.map +1 -1
  134. package/src/runs/shared/async-status-projection.js +23 -1
  135. package/src/runs/shared/async-status-projection.js.map +1 -1
  136. package/src/runs/shared/child-commands.d.ts +2 -0
  137. package/src/runs/shared/child-commands.d.ts.map +1 -1
  138. package/src/runs/shared/child-commands.js +4 -0
  139. package/src/runs/shared/child-commands.js.map +1 -1
  140. package/src/runs/shared/child-runtime-config.d.ts +6 -0
  141. package/src/runs/shared/child-runtime-config.d.ts.map +1 -1
  142. package/src/runs/shared/child-runtime-config.js +6 -0
  143. package/src/runs/shared/child-runtime-config.js.map +1 -1
  144. package/src/runs/shared/child-session.d.ts +2 -2
  145. package/src/runs/shared/child-session.d.ts.map +1 -1
  146. package/src/runs/shared/child-session.js +104 -42
  147. package/src/runs/shared/child-session.js.map +1 -1
  148. package/src/runs/shared/effective-system-prompt.d.ts +4 -4
  149. package/src/runs/shared/effective-system-prompt.d.ts.map +1 -1
  150. package/src/runs/shared/effective-system-prompt.js +5 -4
  151. package/src/runs/shared/effective-system-prompt.js.map +1 -1
  152. package/src/runs/shared/external-cli-runner.d.ts.map +1 -1
  153. package/src/runs/shared/external-cli-runner.js +3 -2
  154. package/src/runs/shared/external-cli-runner.js.map +1 -1
  155. package/src/runs/shared/long-running-guard.d.ts +3 -1
  156. package/src/runs/shared/long-running-guard.d.ts.map +1 -1
  157. package/src/runs/shared/long-running-guard.js +4 -14
  158. package/src/runs/shared/long-running-guard.js.map +1 -1
  159. package/src/runs/shared/model-resolution-diagnostic.d.ts +7 -5
  160. package/src/runs/shared/model-resolution-diagnostic.d.ts.map +1 -1
  161. package/src/runs/shared/model-resolution-diagnostic.js +13 -10
  162. package/src/runs/shared/model-resolution-diagnostic.js.map +1 -1
  163. package/src/runs/shared/model-resolution.d.ts +5 -1
  164. package/src/runs/shared/model-resolution.d.ts.map +1 -1
  165. package/src/runs/shared/model-resolution.js +8 -1
  166. package/src/runs/shared/model-resolution.js.map +1 -1
  167. package/src/runs/shared/nested-events.d.ts.map +1 -1
  168. package/src/runs/shared/nested-events.js +2 -0
  169. package/src/runs/shared/nested-events.js.map +1 -1
  170. package/src/runs/shared/orca-progress-tabs.js +1 -1
  171. package/src/runs/shared/orca-progress-tabs.js.map +1 -1
  172. package/src/runs/shared/partial-output.d.ts +7 -0
  173. package/src/runs/shared/partial-output.d.ts.map +1 -1
  174. package/src/runs/shared/partial-output.js +7 -0
  175. package/src/runs/shared/partial-output.js.map +1 -1
  176. package/src/runs/shared/run-ownership.d.ts +4 -0
  177. package/src/runs/shared/run-ownership.d.ts.map +1 -0
  178. package/src/runs/shared/run-ownership.js +9 -0
  179. package/src/runs/shared/run-ownership.js.map +1 -0
  180. package/src/runs/shared/single-output.d.ts +11 -1
  181. package/src/runs/shared/single-output.d.ts.map +1 -1
  182. package/src/runs/shared/single-output.js +48 -26
  183. package/src/runs/shared/single-output.js.map +1 -1
  184. package/src/runs/shared/structured-output.d.ts +2 -0
  185. package/src/runs/shared/structured-output.d.ts.map +1 -1
  186. package/src/runs/shared/structured-output.js +9 -0
  187. package/src/runs/shared/structured-output.js.map +1 -1
  188. package/src/runs/shared/subagent-control.d.ts +1 -0
  189. package/src/runs/shared/subagent-control.d.ts.map +1 -1
  190. package/src/runs/shared/subagent-control.js +2 -1
  191. package/src/runs/shared/subagent-control.js.map +1 -1
  192. package/src/runs/shared/subagent-prompt-runtime.d.ts +56 -0
  193. package/src/runs/shared/subagent-prompt-runtime.d.ts.map +1 -1
  194. package/src/runs/shared/subagent-prompt-runtime.js +192 -6
  195. package/src/runs/shared/subagent-prompt-runtime.js.map +1 -1
  196. package/src/runs/shared/tool-budget.js +1 -1
  197. package/src/runs/shared/tool-budget.js.map +1 -1
  198. package/src/shared/child-transcript.js +1 -1
  199. package/src/shared/child-transcript.js.map +1 -1
  200. package/src/shared/extension-context.d.ts +5 -0
  201. package/src/shared/extension-context.d.ts.map +1 -1
  202. package/src/shared/extension-context.js +7 -0
  203. package/src/shared/extension-context.js.map +1 -1
  204. package/src/shared/file-lease.d.ts +7 -1
  205. package/src/shared/file-lease.d.ts.map +1 -1
  206. package/src/shared/file-lease.js +8 -2
  207. package/src/shared/file-lease.js.map +1 -1
  208. package/src/shared/fork-context.d.ts +3 -1
  209. package/src/shared/fork-context.d.ts.map +1 -1
  210. package/src/shared/fork-context.js +23 -13
  211. package/src/shared/fork-context.js.map +1 -1
  212. package/src/shared/parent-wake.d.ts +6 -0
  213. package/src/shared/parent-wake.d.ts.map +1 -1
  214. package/src/shared/parent-wake.js +11 -0
  215. package/src/shared/parent-wake.js.map +1 -1
  216. package/src/shared/session-succession.d.ts +5 -0
  217. package/src/shared/session-succession.d.ts.map +1 -0
  218. package/src/shared/session-succession.js +25 -0
  219. package/src/shared/session-succession.js.map +1 -0
  220. package/src/shared/types.d.ts +22 -9
  221. package/src/shared/types.d.ts.map +1 -1
  222. package/src/shared/types.js +7 -4
  223. package/src/shared/types.js.map +1 -1
  224. package/src/shared/utils.d.ts +13 -0
  225. package/src/shared/utils.d.ts.map +1 -1
  226. package/src/shared/utils.js +19 -0
  227. package/src/shared/utils.js.map +1 -1
  228. package/src/slash/slash-commands.d.ts.map +1 -1
  229. package/src/slash/slash-commands.js +22 -11
  230. package/src/slash/slash-commands.js.map +1 -1
  231. package/src/slash/subagents-admin.d.ts.map +1 -1
  232. package/src/slash/subagents-admin.js +3 -2
  233. package/src/slash/subagents-admin.js.map +1 -1
  234. package/src/tui/fleet-status.d.ts +5 -0
  235. package/src/tui/fleet-status.d.ts.map +1 -1
  236. package/src/tui/fleet-status.js +24 -3
  237. package/src/tui/fleet-status.js.map +1 -1
  238. package/src/tui/fleet.js +2 -2
  239. package/src/tui/fleet.js.map +1 -1
  240. package/src/watchdog/change-signature.d.ts.map +1 -1
  241. package/src/watchdog/change-signature.js +1 -3
  242. package/src/watchdog/change-signature.js.map +1 -1
  243. package/src/watchdog/register-main.js +2 -2
  244. package/src/watchdog/register-main.js.map +1 -1
  245. package/src/watchdog/runtime.d.ts.map +1 -1
  246. package/src/watchdog/runtime.js +3 -5
  247. package/src/watchdog/runtime.js.map +1 -1
  248. package/src/workflows/scripted-workflow.d.ts +1 -1
  249. package/src/workflows/scripted-workflow.d.ts.map +1 -1
  250. package/src/workflows/scripted-workflow.js +13 -4
  251. package/src/workflows/scripted-workflow.js.map +1 -1
  252. package/src/workflows/workflow-child-summary.d.ts +5 -0
  253. package/src/workflows/workflow-child-summary.d.ts.map +1 -1
  254. package/src/workflows/workflow-child-summary.js +10 -0
  255. package/src/workflows/workflow-child-summary.js.map +1 -1
  256. package/src/workflows/workflow-reuse.d.ts +6 -1
  257. package/src/workflows/workflow-reuse.d.ts.map +1 -1
  258. package/src/workflows/workflow-reuse.js +22 -1
  259. package/src/workflows/workflow-reuse.js.map +1 -1
  260. package/src/runs/shared/run-history.d.ts +0 -67
  261. package/src/runs/shared/run-history.d.ts.map +0 -1
  262. package/src/runs/shared/run-history.js +0 -270
  263. package/src/runs/shared/run-history.js.map +0 -1
@@ -27,7 +27,6 @@ import { normalizeParentModel, resolveEffectiveSubagentModel, resolveModelOrigin
27
27
  import { projectChainOutputSchemas, resolveEffectiveOutputSchema } from "../shared/child-launch-plan.js";
28
28
  import { formatRetainedChildren, listRetainedChildren } from "../background/retained-children.js";
29
29
  import { resolveModelScopesForAgent } from "../shared/model-scope.js";
30
- import { recordRun } from "../shared/run-history.js";
31
30
  import { getStepAgents, isParallelStep, isDynamicParallelStep, resolveExistingReadPaths, } from "../../shared/settings.js";
32
31
  import { normalizeSkillInput } from "../../agents/skills.js";
33
32
  import { buildAsyncRunnerSteps, DEFAULT_ASYNC_TIMEOUT_MS, executeAsyncChain, executeAsyncSingle, formatAsyncStartedMessage, isAsyncAvailable, workflowAwaitedAsyncResultPath } from "../background/async-execution.js";
@@ -69,6 +68,7 @@ import { canQueueRetainedAsyncFollowUp, steerAsyncRun } from "./async-steering-a
69
68
  import { resolveWorkflowForegroundSteeringTarget, steerWorkflowForegroundTarget, steerWorkflowRun, } from "./workflow-foreground-steering.js";
70
69
  import { stopAsyncRun } from "./async-stop-action.js";
71
70
  import { dismissRecoveredWorkflow } from "./async-dismiss-action.js";
71
+ import { inheritedSessionIds } from "../../shared/session-succession.js";
72
72
  import { promotePausedWorkflowIfSettled, reconcileDetachedWorkflowChildCompletion } from "./workflow-detach-reconcile.js";
73
73
  import { reconcileAsyncRun } from "../background/stale-run-reconciler.js";
74
74
  import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.js";
@@ -95,7 +95,7 @@ import { getInspectorPlugins } from "../../inspectors/plugins.js";
95
95
  import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../inspectors/herdr/project-panes.js";
96
96
  import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError } from "../../workflows/scripted-workflow.js";
97
97
  import { formatIncrementalChildCompletion, incrementalChildCompletionTriggersTurn } from "../background/notify.js";
98
- import { appendWorkflowChildJournal, findWorkflowReuseSource, matchWorkflowReuse, workflowChildFingerprint, workflowScriptDigest, workflowStopCause, WORKFLOW_RUNTIME_REPLACED_RELAUNCH_NOTICE } from "../../workflows/workflow-reuse.js";
98
+ import { appendWorkflowChildJournal, findWorkflowReuseSource, matchWorkflowReuse, readNamedWorkflowReuseSource, workflowChildFingerprint, workflowRuntimeReplacedRelaunchNotice, workflowScriptDigest, workflowStopCause } from "../../workflows/workflow-reuse.js";
99
99
  import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath } from "../../workflows/host-command.js";
100
100
  import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt } from "../../workflows/workflow-receipt.js";
101
101
  import { formatWorkflowKeyRevival, recordWorkflowRevival, withWorkflowRevivals } from "../../workflows/workflow-revival.js";
@@ -271,11 +271,12 @@ function nestedResolutionScopeForExecutor(deps) {
271
271
  ...(address ? { descendantOf: { parentRunId: address.parentRunId, ...(address.parentStepIndex !== undefined ? { parentStepIndex: address.parentStepIndex } : {}) } } : {}),
272
272
  };
273
273
  }
274
- function trustedSessionRootsForStatus(ctx, deps) {
274
+ function trustedSessionRoots(parentSessionFile, deps) {
275
275
  const roots = deps.config.defaultSessionDir ? [path.resolve(deps.expandTilde(deps.config.defaultSessionDir))] : [];
276
- const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
276
+ // Inherited runs keep their children under the root of the session that launched them.
277
277
  if (parentSessionFile)
278
- roots.push(deps.getSubagentSessionRoot(parentSessionFile));
278
+ for (const sessionFile of [parentSessionFile, ...inheritedSessionIds(parentSessionFile)])
279
+ roots.push(deps.getSubagentSessionRoot(sessionFile));
279
280
  return [...new Set(roots)];
280
281
  }
281
282
  /** Children follow the launching session's trust; hosts older than Pi's trust concept keep Pi's default. */
@@ -637,6 +638,7 @@ function resolveForegroundResumeTarget(params, state, options = {}) {
637
638
  ...(child.thinking ? { thinking: child.thinking } : {}),
638
639
  ...(child.launchContractDigest ? { launchContractDigest: child.launchContractDigest } : {}),
639
640
  ...(child.resumeContract ? { resumeContract: child.resumeContract } : {}),
641
+ ...(child.savedOutputPath ? { savedOutputPath: child.savedOutputPath } : {}),
640
642
  ...(child.extensionBindings ? { extensionBindings: normalizeExtensionBindings(child.extensionBindings).value } : {}),
641
643
  ...(child.requiredExtensions ? { requiredExtensions: child.requiredExtensions } : {}),
642
644
  ...(child.capabilityCeiling ? { capabilityCeiling: child.capabilityCeiling } : {}),
@@ -1129,7 +1131,7 @@ function pathWithin(base, candidate) {
1129
1131
  const resolvedCandidate = path.resolve(candidate);
1130
1132
  return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
1131
1133
  }
1132
- function validateNestedSessionFile(run, trustedSessionRoots) {
1134
+ function validateNestedSessionFile(run, trustedSessionRoots, resolveSource, visited = []) {
1133
1135
  const sessionFile = nestedRunSessionFile(run);
1134
1136
  if (!sessionFile)
1135
1137
  throw new Error(`Nested run '${run.id}' does not have a persisted session file to resume from.`);
@@ -1151,7 +1153,24 @@ function validateNestedSessionFile(run, trustedSessionRoots) {
1151
1153
  throw new Error(`Nested run '${run.id}' session file is outside trusted nested session roots: ${sessionFile}`);
1152
1154
  }
1153
1155
  if (!realSessionFile.split(path.sep).includes(run.id)) {
1154
- throw new Error(`Nested run '${run.id}' session file is not under that nested run's session directory: ${sessionFile}`);
1156
+ // A resume replacement continues its source's session; accept it only when that source,
1157
+ // resolved in the caller's own scope, is one the caller could resume itself (the states
1158
+ // resolveNestedResumeTarget refuses), validates on its own and owns this same file.
1159
+ const sourceId = run.revivedFrom;
1160
+ const chain = [...visited, run.id];
1161
+ const source = sourceId && chain.length <= 8 && !chain.includes(sourceId) ? resolveSource(sourceId) : undefined;
1162
+ let sourceFile;
1163
+ if (source && source.state !== "running" && source.state !== "queued" && source.state !== "stopped") {
1164
+ try {
1165
+ sourceFile = validateNestedSessionFile(source, trustedSessionRoots, resolveSource, chain);
1166
+ }
1167
+ catch {
1168
+ sourceFile = undefined;
1169
+ }
1170
+ }
1171
+ if (sourceFile !== realSessionFile) {
1172
+ throw new Error(`Nested run '${run.id}' session file is not under that nested run's session directory: ${sessionFile}`);
1173
+ }
1155
1174
  }
1156
1175
  return realSessionFile;
1157
1176
  }
@@ -1165,7 +1184,7 @@ export function readNestedRecoveryDescriptor(asyncDir, runId, agent) {
1165
1184
  throw new Error(`Nested run '${runId}' has a recovery descriptor for a different agent.`);
1166
1185
  return recoveryDescriptor;
1167
1186
  }
1168
- function resolveNestedResumeTarget(match, trustedSessionRoots) {
1187
+ function resolveNestedResumeTarget(match, trustedSessionRoots, resolveSource) {
1169
1188
  const run = match.match.run;
1170
1189
  if (run.state === "running" || run.state === "queued")
1171
1190
  throw new Error(`Nested run '${run.id}' is live; route the follow-up to the owner process instead.`);
@@ -1185,7 +1204,7 @@ function resolveNestedResumeTarget(match, trustedSessionRoots) {
1185
1204
  agent,
1186
1205
  index: 0,
1187
1206
  cwd: asyncDir ? path.dirname(asyncDir) : undefined,
1188
- sessionFile: validateNestedSessionFile(run, trustedSessionRoots),
1207
+ sessionFile: validateNestedSessionFile(run, trustedSessionRoots, resolveSource),
1189
1208
  ...(run.capabilityCeiling ? { capabilityCeiling: run.capabilityCeiling } : {}),
1190
1209
  ...(recoveryDescriptor ? { recoveryDescriptor } : {}),
1191
1210
  });
@@ -1522,9 +1541,10 @@ async function resumeExternalJobFollowUp(input) {
1522
1541
  }
1523
1542
  function resolveRequestedResumeTarget(params, deps, parentSessionFile) {
1524
1543
  const requestedId = params.id ?? params.runId;
1544
+ const resolutionDeps = omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) });
1525
1545
  let resolved;
1526
1546
  try {
1527
- resolved = requestedId ? resolveSubagentRunId(requestedId, omitUndefinedProperties({ state: deps.state, nested: nestedResolutionScopeForExecutor(deps) })) : undefined;
1547
+ resolved = requestedId ? resolveSubagentRunId(requestedId, resolutionDeps) : undefined;
1528
1548
  }
1529
1549
  catch (error) {
1530
1550
  const message = error instanceof Error ? error.message : "";
@@ -1540,11 +1560,18 @@ function resolveRequestedResumeTarget(params, deps, parentSessionFile) {
1540
1560
  const externalJobTarget = resolveNestedExternalJobResumeTarget(resolved, params, deps);
1541
1561
  if (externalJobTarget)
1542
1562
  return externalJobTarget;
1543
- const trustedSessionRoots = [
1544
- ...(deps.config.defaultSessionDir ? [path.resolve(deps.expandTilde(deps.config.defaultSessionDir))] : []),
1545
- ...(parentSessionFile ? [deps.getSubagentSessionRoot(parentSessionFile)] : []),
1546
- ];
1547
- return resolveNestedResumeTarget(resolved, trustedSessionRoots);
1563
+ const rootRunId = resolved.match.rootRunId;
1564
+ // The source must be a nested run the caller could address itself, in the same registry.
1565
+ const resolveSource = (id) => {
1566
+ try {
1567
+ const source = resolveSubagentRunId(id, resolutionDeps);
1568
+ return source?.kind === "nested" && source.id === id && source.match.rootRunId === rootRunId ? source.match.run : undefined;
1569
+ }
1570
+ catch {
1571
+ return undefined;
1572
+ }
1573
+ };
1574
+ return resolveNestedResumeTarget(resolved, trustedSessionRoots(parentSessionFile, deps), resolveSource);
1548
1575
  }
1549
1576
  return resolveResumeTarget(params, deps.state, { asyncRequireSessionFile: false });
1550
1577
  }
@@ -1882,6 +1909,13 @@ async function resumeAsyncRun(input) {
1882
1909
  const availableModels = input.ctx.modelRegistry.getAvailable().map(toModelInfo);
1883
1910
  const parentModel = input.parentModel;
1884
1911
  const revivalAsyncDir = path.join(DIRS.async, runId);
1912
+ const revivalOutputBaseDir = resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId);
1913
+ const inheritedOutput = foregroundContract?.output ?? recoveryDescriptor?.outputPath;
1914
+ // The original run owns its saved output, so a resume without its own output writes next to it instead of over it.
1915
+ // Workflow resumes are routed by the workflow's output claims instead.
1916
+ const inheritedOutputPath = input.params.output === undefined && input.params.workflowParentRunId === undefined
1917
+ ? (target.source === "foreground" ? target.savedOutputPath : undefined) ?? resolveSingleOutputPath(inheritedOutput, input.requestCwd, effectiveCwd, revivalOutputBaseDir)
1918
+ : undefined;
1885
1919
  const result = await executeAsyncSingle(runId, compactOptional({
1886
1920
  agent: target.agent,
1887
1921
  task: buildRevivedAsyncTask(target, effectiveFollowUp),
@@ -1929,7 +1963,7 @@ async function resumeAsyncRun(input) {
1929
1963
  thinkingCeiling: recoveryDescriptor?.thinkingCeiling ?? ("thinkingCeiling" in target ? target.thinkingCeiling : undefined),
1930
1964
  extensionBindings: recoveryDescriptor?.extensionBindings ?? ("extensionBindings" in target ? target.extensionBindings : undefined),
1931
1965
  requiredExtensions: recoveryDescriptor?.requiredExtensions ?? target.requiredExtensions,
1932
- outputBaseDir: resolveSingleRunOutputBaseDir(input.deps, artifactsDir, runId),
1966
+ outputBaseDir: revivalOutputBaseDir,
1933
1967
  maxSubagentDepth: recoveryDescriptor?.maxSubagentDepth ?? resolveCurrentMaxSubagentDepth(input.deps.config.maxSubagentDepth, input.deps.childRuntime),
1934
1968
  waitToolEnabled: input.deps.waitToolEnabled,
1935
1969
  waitToolDefaultTimeoutMs: input.deps.waitToolDefaultTimeoutMs,
@@ -1948,7 +1982,7 @@ async function resumeAsyncRun(input) {
1948
1982
  controlIntercomTarget: intercomBridge.active ? intercomBridge.orchestratorTarget : undefined,
1949
1983
  childIntercomTarget: intercomBridge.active ? (agent, index) => resolveSubagentIntercomTarget(runId, agent, index) : undefined,
1950
1984
  availableModels,
1951
- output: input.params.output !== undefined ? input.params.output : foregroundContract?.output ?? recoveryDescriptor?.outputPath,
1985
+ output: input.params.output !== undefined ? input.params.output : inheritedOutputPath ? suffixedOutputPath(inheritedOutputPath, runId.slice(0, 8)) : inheritedOutput,
1952
1986
  outputMode: input.params.outputMode ?? foregroundContract?.outputMode ?? recoveryDescriptor?.outputMode,
1953
1987
  outputClaimPath: input.params.workflowOutputClaimPath,
1954
1988
  ...(agentContract ? { agentContract } : {}),
@@ -3005,9 +3039,9 @@ function importWorkflowAwaitedChildResult(completed, options) {
3005
3039
  };
3006
3040
  }
3007
3041
  // A replaced runtime leaves the child running so a relaunch of the workflow can re-attach to it.
3008
- function stopAwaitedAsyncChildOnAbort(signal, state, runId, asyncDir, kill) {
3042
+ function stopAwaitedAsyncChildOnAbort(signal, state, runId, asyncDir, kill, reattachedFromOtherSession = false) {
3009
3043
  const stopOnAbort = () => { if (!workflowStopCause(signal?.reason))
3010
- stopAsyncRun(state, runId, kill, { asyncDir, resolvedId: runId }); };
3044
+ stopAsyncRun(state, runId, kill, { asyncDir, resolvedId: runId, ...(reattachedFromOtherSession ? { reattachedRunId: runId } : {}) }); };
3011
3045
  if (signal?.aborted)
3012
3046
  stopOnAbort();
3013
3047
  else
@@ -3231,11 +3265,14 @@ export function sanitizeRunPathSegment(value, maxBytes = 120) {
3231
3265
  function resolveSingleRunOutputBaseDir(deps, artifactsDir, runId) {
3232
3266
  return resolveConfiguredSingleRunOutputBaseDir(deps) ?? path.join(artifactsDir, "outputs", sanitizeRunPathSegment(runId));
3233
3267
  }
3268
+ /** `<dir>/<name>.<suffix><ext>` next to outputPath, with `.md` when it has no extension. */
3269
+ function suffixedOutputPath(outputPath, suffix) {
3270
+ const parsed = path.parse(outputPath);
3271
+ return path.join(parsed.dir, `${parsed.name}.${suffix}${parsed.ext || ".md"}`);
3272
+ }
3234
3273
  function workflowChildDefaultOutput(aggregateOutputPath, artifactsDir, workflowRunId, workflowKey) {
3235
- if (aggregateOutputPath) {
3236
- const parsed = path.parse(aggregateOutputPath);
3237
- return path.join(parsed.dir, `${parsed.name}.${workflowKey}${parsed.ext || ".md"}`);
3238
- }
3274
+ if (aggregateOutputPath)
3275
+ return suffixedOutputPath(aggregateOutputPath, workflowKey);
3239
3276
  return path.join(artifactsDir, "outputs", sanitizeRunPathSegment(workflowRunId), `${workflowKey}.md`);
3240
3277
  }
3241
3278
  function workflowHostCommandRunner(input) {
@@ -3328,9 +3365,8 @@ function resolveWorkflowChildOutputPath(input) {
3328
3365
  const recoveryOutput = "recoveryDescriptor" in target ? target.recoveryDescriptor?.outputPath : undefined;
3329
3366
  if (hasExplicitOutput)
3330
3367
  return { path: recoveryOutput, inherited: false };
3331
- // An output-less foreground resume reuses its recorded output; only an absolute path is known before the new run id exists.
3332
- const foregroundOutput = target.source === "foreground" ? target.resumeContract?.output : undefined;
3333
- return { path: recoveryOutput, inherited: false, resumedOutput: recoveryOutput ?? (typeof foregroundOutput === "string" && path.isAbsolute(foregroundOutput) ? foregroundOutput : undefined) };
3368
+ const savedPath = recoveryOutput ?? (target.source === "foreground" ? target.savedOutputPath ?? resolveSingleOutputPath(target.resumeContract?.output, input.ctxCwd) : undefined);
3369
+ return { inherited: false, ...(savedPath ? { resumedSavedPath: savedPath } : {}) };
3334
3370
  }
3335
3371
  const childCwd = resolveWorkflowChildLocalCwd(input);
3336
3372
  let agentOutput;
@@ -3370,10 +3406,12 @@ function workflowChildOutputClaims(input) {
3370
3406
  }
3371
3407
  const overrides = new Map();
3372
3408
  for (const entry of resolvedEntries) {
3373
- // An output-less resume keeps its retained path unless an earlier child of this workflow already owns it.
3374
- const resumeCollides = entry.resumedOutput !== undefined && input.claimedOutputPaths.has(resolveWorkflowHostOutputClaimPath(entry.resumedOutput));
3375
- if (resumeCollides || (entry.inherited && entry.path && (paths.get(resolveWorkflowHostOutputClaimPath(entry.path)) ?? 0) > 1)) {
3376
- const output = workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, entry.key);
3409
+ // The resumed run owns the report it saved, inside or outside this workflow, so an output-less resume writes its own.
3410
+ if (entry.resumedSavedPath || (entry.inherited && entry.path && (paths.get(resolveWorkflowHostOutputClaimPath(entry.path)) ?? 0) > 1)) {
3411
+ let output = workflowChildDefaultOutput(input.aggregateOutputPath, input.artifactsDir, input.workflowRunId, entry.key);
3412
+ // A rerun of the same workflow and key generates the very file the resumed run saved.
3413
+ if (entry.resumedSavedPath && resolveWorkflowHostOutputClaimPath(output) === resolveWorkflowHostOutputClaimPath(entry.resumedSavedPath))
3414
+ output = suffixedOutputPath(output, input.workflowRunId.slice(0, 8));
3377
3415
  overrides.set(entry.key, output);
3378
3416
  entry.path = output;
3379
3417
  }
@@ -3390,16 +3428,6 @@ function workflowChildOutputClaims(input) {
3390
3428
  claims.set(claimPath, key);
3391
3429
  childClaims.set(key, claimPath);
3392
3430
  }
3393
- // Record a discovered output-less resume path for later admissions without adding same-batch duplicate errors.
3394
- for (const { key, path: resolved, resumedOutput } of resolvedEntries) {
3395
- if (resolved || !resumedOutput)
3396
- continue;
3397
- const claimPath = resolveWorkflowHostOutputClaimPath(resumedOutput);
3398
- if (claims.has(claimPath))
3399
- continue;
3400
- claims.set(claimPath, key);
3401
- childClaims.set(key, claimPath);
3402
- }
3403
3431
  return { claims, childClaims, overrides };
3404
3432
  }
3405
3433
  function applyWorkflowChildOutputClaims(target, claims) {
@@ -3622,7 +3650,6 @@ async function runSinglePath(data, deps) {
3622
3650
  if (shouldForkAgent(contextPolicy, params.agent)) {
3623
3651
  task = wrapForkTask(task);
3624
3652
  }
3625
- const cleanTask = task;
3626
3653
  const outputPath = resolveSingleOutputPath(effectiveOutput, ctx.cwd, singleCwd, resolveSingleRunOutputBaseDir(deps, artifactsDir, runId));
3627
3654
  const validationError = validateFileOnlyOutputMode(effectiveOutputMode, outputPath, `Single run (${params.agent})`);
3628
3655
  if (validationError) {
@@ -3839,7 +3866,6 @@ async function runSinglePath(data, deps) {
3839
3866
  }
3840
3867
  }
3841
3868
  }
3842
- recordRun(params.agent, cleanTask, result.exitCode, result.progressSummary?.durationMs ?? 0, result);
3843
3869
  },
3844
3870
  timeoutMs: data.timeoutMs,
3845
3871
  deadlineAt,
@@ -3880,9 +3906,6 @@ async function runSinglePath(data, deps) {
3880
3906
  finishTrackedForegroundChild();
3881
3907
  }
3882
3908
  }
3883
- if (!r.detached) {
3884
- recordRun(params.agent, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0, r);
3885
- }
3886
3909
  let worktreeHandoff;
3887
3910
  if (worktreeSetup) {
3888
3911
  worktreeHandoff = r.detached
@@ -4056,13 +4079,16 @@ export function bindMissionWorkflowChildAsyncLaunch(params, binding, asyncByDefa
4056
4079
  }
4057
4080
  function workflowChildResult(key, result, childParams = {}, resumeState, forcedTerminalOutcome) {
4058
4081
  const running = missionWorkflowChildStatus(result) === "running";
4059
- const receiptOutput = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
4082
+ // The receipt opens with the workflow-wide run fan-out line, which says nothing about this child.
4083
+ const fanoutPrefix = result.details.runFanoutBudget ? `${formatRunFanoutBudget(result.details.runFanoutBudget)}\n` : undefined;
4084
+ const fullReceipt = result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n");
4085
+ const receiptOutput = fanoutPrefix && fullReceipt.startsWith(fanoutPrefix) ? fullReceipt.slice(fanoutPrefix.length) : fullReceipt;
4060
4086
  const output = running ? "" : result.details.results.length === 1 && result.details.results[0]?.finalOutput !== undefined
4061
4087
  ? result.details.results[0].finalOutput
4062
4088
  : receiptOutput;
4063
4089
  const childError = result.details.results.map((child) => child.error).find((error) => Boolean(error));
4064
4090
  const stopped = result.details.results.some((child) => child.stopped);
4065
- // A stopped child's receipt text carries the run fan-out annotation; report the structured stop reason instead.
4091
+ // A stopped child reports its structured stop reason alone, without the receipt's output block.
4066
4092
  const failureErrorBase = stopped && childError
4067
4093
  ? childError
4068
4094
  : childError && receiptOutput
@@ -4820,12 +4846,7 @@ export function createSubagentExecutor(deps) {
4820
4846
  if (launchBlockingErrors.length > 0) {
4821
4847
  return buildRequestedModeError(requestParams, `Workflow '${_id}' validation failed before child launch; no children launched. ${launchBlockingErrors.map((error) => error.message).join(" ")}`);
4822
4848
  }
4823
- // A package chain's per-step stop check defeats static launch counting; runtime fan-out enforcement still applies.
4824
- const structuredResource = workflowResource?.provenance.name === "chain" || workflowResource?.provenance.name === "tasks";
4825
- for (const warning of workflowValidation.warnings ?? []) {
4826
- if (!(structuredResource && warning.kind === "dynamic-spawn-count"))
4827
- console.warn(`[pi-subagents] ${warning.message}`);
4828
- }
4849
+ // Validation warnings belong to the validate action; a launch does not repeat them on every run.
4829
4850
  const acceptanceErrors = validateAcceptanceInput(requestParams.acceptance);
4830
4851
  if (acceptanceErrors.length > 0)
4831
4852
  return buildRequestedModeError(requestParams, acceptanceErrors.join(" "));
@@ -4893,6 +4914,17 @@ export function createSubagentExecutor(deps) {
4893
4914
  && !inheritedNestedRoute(deps)
4894
4915
  && !requestParams.workflowParentRunId;
4895
4916
  const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
4917
+ if (requestParams.reuseFrom !== undefined && !topLevelAsyncWorkflow) {
4918
+ return { content: [{ type: "text", text: "options.reuseFrom requires an async top-level workflow." }], isError: true, details: { mode: "workflow", results: [] } };
4919
+ }
4920
+ let namedWorkflowReuseSource;
4921
+ try {
4922
+ if (requestParams.reuseFrom !== undefined)
4923
+ namedWorkflowReuseSource = readNamedWorkflowReuseSource(DIRS.async, requestParams.reuseFrom, workflowScriptDigest(requestParams.workflowScript), workflowCwd);
4924
+ }
4925
+ catch (error) {
4926
+ return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
4927
+ }
4896
4928
  let workflowCapacity;
4897
4929
  if (workflowRunId && topLevelAsyncWorkflow) {
4898
4930
  const currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
@@ -4959,7 +4991,7 @@ export function createSubagentExecutor(deps) {
4959
4991
  const toolCallId = _id;
4960
4992
  const workflowSessionRoot = requestParams.sessionDir
4961
4993
  ? path.resolve(deps.expandTilde(requestParams.sessionDir))
4962
- : trustedSessionRootsForStatus(ctx, deps)[0];
4994
+ : trustedSessionRoots(ctx.sessionManager.getSessionFile() ?? null, deps)[0];
4963
4995
  const asyncDir = path.join(DIRS.async, workflowRunId);
4964
4996
  const resultPath = resultFilePath(DIRS.results, workflowRunId);
4965
4997
  const statusPath = path.join(asyncDir, "status.json");
@@ -4970,7 +5002,7 @@ export function createSubagentExecutor(deps) {
4970
5002
  const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
4971
5003
  deps.state.completionOwnerId = completionOwnerId;
4972
5004
  const workflowScriptSourceDigest = workflowScriptDigest(requestParams.workflowScript);
4973
- const workflowReuseSource = findWorkflowReuseSource(DIRS.async, currentSessionId, workflowScriptSourceDigest, workflowArgsDigest);
5005
+ const workflowReuseSource = namedWorkflowReuseSource ?? findWorkflowReuseSource(DIRS.async, currentSessionId, workflowScriptSourceDigest, workflowArgsDigest);
4974
5006
  const workflowRunEvidence = { ...workflowArgsEvidence, scriptDigest: workflowScriptSourceDigest };
4975
5007
  if (workflowReuseSource)
4976
5008
  workflowRunEvidence.reusedFrom = workflowReuseSource.runId;
@@ -5495,6 +5527,9 @@ export function createSubagentExecutor(deps) {
5495
5527
  if (entry.state === "reused" && existing) {
5496
5528
  continue;
5497
5529
  }
5530
+ // A runtime replacement does not stop awaited async children (stopAwaitedAsyncChildOnAbort): they keep running.
5531
+ if (entry.state === "stopped" && existing?.async === true && existing.runId && workflowStopCause(controller.signal.reason))
5532
+ continue;
5498
5533
  const mapped = entry.state === "started" || entry.state === "reused"
5499
5534
  ? "running"
5500
5535
  : entry.state === "completed"
@@ -5686,7 +5721,7 @@ export function createSubagentExecutor(deps) {
5686
5721
  const priorRunId = priorChild.runId;
5687
5722
  const priorAsyncDir = path.join(DIRS.async, priorRunId);
5688
5723
  appendWorkflowChildJournal(asyncDir, { type: "start", key, fingerprint: journalFingerprint, runId: priorRunId });
5689
- const stopListener = stopAwaitedAsyncChildOnAbort(workflowSignal, deps.state, priorRunId, priorAsyncDir, deps.kill);
5724
+ const stopListener = stopAwaitedAsyncChildOnAbort(workflowSignal, deps.state, priorRunId, priorAsyncDir, deps.kill, requestParams.reuseFrom !== undefined);
5690
5725
  const reattached = await awaitExistingAsyncRun(priorAsyncDir, priorRunId, workflowSignal).finally(stopListener.remove);
5691
5726
  const claimedPath = reattached.status === "settled" ? claimWorkflowAwaitedResult(priorAsyncDir, workflowRunId, { ...reattached.result.importedPublication, runId: priorRunId }) : undefined;
5692
5727
  if (reattached.status === "settled" && claimedPath) {
@@ -5883,7 +5918,7 @@ export function createSubagentExecutor(deps) {
5883
5918
  const errorPreview = status.error && status.error.length > 1_000 ? `${status.error.slice(0, 1_000)}… (truncated; full error: ${statusPath} (error))` : status.error;
5884
5919
  const terminalSummary = `${status.state === "complete"
5885
5920
  ? "Workflow completed after detached child finished."
5886
- : errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${stopCause ? ` ${WORKFLOW_RUNTIME_REPLACED_RELAUNCH_NOTICE}` : ""}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5921
+ : errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${stopCause ? ` ${workflowRuntimeReplacedRelaunchNotice(workflowRunId)}` : ""}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5887
5922
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
5888
5923
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
5889
5924
  const receiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
@@ -5956,6 +5991,7 @@ export function createSubagentExecutor(deps) {
5956
5991
  const workflowHostSteps = new Map();
5957
5992
  let liveWorkflow = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
5958
5993
  const childProgress = new Map();
5994
+ const childLaunches = new Map();
5959
5995
  let activityTimer;
5960
5996
  let lastProgressAt = 0;
5961
5997
  let workflowProgressClosed = false;
@@ -5968,7 +6004,7 @@ export function createSubagentExecutor(deps) {
5968
6004
  clearTimeout(activityTimer);
5969
6005
  activityTimer = undefined;
5970
6006
  lastProgressAt = Date.now();
5971
- const liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false, trace: liveWorkflow.trace, progress: childProgress });
6007
+ const liveWorkflowChildren = workflowChildSummary({ parentToolCallId: _id, workflowRunId: foregroundWorkflowRunId, workflowState: "running", inventoryComplete: false, trace: liveWorkflow.trace, progress: childProgress, launches: childLaunches });
5972
6008
  onUpdate?.(workflowProgressUpdate(foregroundWorkflowRunId, chatProgress, liveWorkflow, liveWorkflowChildren, workflowPreflight));
5973
6009
  };
5974
6010
  try {
@@ -6056,11 +6092,21 @@ export function createSubagentExecutor(deps) {
6056
6092
  throw new Error("Workflow child permit supports native Pi children only.");
6057
6093
  workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: foregroundWorkflowRunId, childKey: key } });
6058
6094
  }
6059
- workflowLaunchObservers.set(childRequest, (launch) => recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
6060
- status: "running",
6061
- agent: launch.agent,
6062
- ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
6063
- }));
6095
+ workflowLaunchObservers.set(childRequest, (launch) => {
6096
+ childLaunches.set(key, { agent: launch.agent, ...(launch.runId ? { runId: launch.runId } : {}) });
6097
+ // Telemetry must not decide the child's outcome, as with onTrace in scripted-workflow.
6098
+ try {
6099
+ sendWorkflowProgress();
6100
+ }
6101
+ catch (error) {
6102
+ console.error("Workflow progress update failed:", error);
6103
+ }
6104
+ recordMissionWorkflowChild(missionBinding, foregroundWorkflowRunId, key, {
6105
+ status: "running",
6106
+ agent: launch.agent,
6107
+ ...(launch.sessionFile ? { sessionPath: launch.sessionFile } : {}),
6108
+ });
6109
+ });
6064
6110
  return execute(randomUUID(), childRequest, workflowSignal, (update) => {
6065
6111
  const progress = update.details.progress?.[0];
6066
6112
  if (!progress)
@@ -6602,7 +6648,7 @@ export function createSubagentExecutor(deps) {
6602
6648
  };
6603
6649
  };
6604
6650
  const nestedScope = nestedResolutionScopeForExecutor(deps);
6605
- const sessionRoots = trustedSessionRootsForStatus(ctx, deps);
6651
+ const sessionRoots = trustedSessionRoots(ctx.sessionManager.getSessionFile() ?? null, deps);
6606
6652
  if (action === "debug.run") {
6607
6653
  if (!targetRunId && !hasDirectoryTarget) {
6608
6654
  return withBudget({ content: [{ type: "text", text: "action='debug.run' requires id, runId, or dir." }], isError: true, details: { mode: "management", results: [] } });
@@ -7044,12 +7090,20 @@ export function createSubagentExecutor(deps) {
7044
7090
  // Forked children keep their requested thinking level. Signed Anthropic thinking
7045
7091
  // blocks are stripped from the inherited transcript by the resolver (they are bound
7046
7092
  // to the parent session), which is not a reason to disable the child's own reasoning.
7093
+ let sessionRunId;
7047
7094
  try {
7048
7095
  assertLaunchableAgentsAllowedBeforeFork();
7049
7096
  const pruneSession = contextPolicy.usesFork && deps.config.forkContext?.mode === "pruned"
7050
7097
  ? await createPrunedForkSessionWriter(ctx, deps.config.forkContext, signal)
7051
7098
  : undefined;
7052
- const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, pruneSession ? { pruneSession } : {});
7099
+ const forkContextResolver = createForkContextResolver(ctx.sessionManager, contextPolicy.usesFork ? "fork" : undefined, {
7100
+ ...(pruneSession ? { pruneSession } : {}),
7101
+ runDirName: () => {
7102
+ if (!sessionRunId)
7103
+ throw new Error("A fork session was requested before the launch's run id was assigned.");
7104
+ return sessionRunId;
7105
+ },
7106
+ });
7053
7107
  prepareForkSessionForIndex = forkContextResolver.prepareSessionForIndex;
7054
7108
  forkSessionFileForIndex = forkContextResolver.sessionFileForIndex;
7055
7109
  }
@@ -7152,16 +7206,19 @@ export function createSubagentExecutor(deps) {
7152
7206
  console.warn(`[pi-subagents] ${warning}`);
7153
7207
  }
7154
7208
  let sessionRoot;
7209
+ // Key the session directory by the id the run is recorded under: nested resume
7210
+ // accepts a session file only inside a directory named by that run's id.
7211
+ sessionRunId = effectiveAsync ? asyncRunId : runId;
7155
7212
  if (effectiveParams.sessionDir) {
7156
7213
  // An explicit sessionDir is a root keyed by this launch's run id so
7157
7214
  // concurrent children resolve distinct per-child session files.
7158
- sessionRoot = path.join(path.resolve(deps.expandTilde(effectiveParams.sessionDir)), runId);
7215
+ sessionRoot = path.join(path.resolve(deps.expandTilde(effectiveParams.sessionDir)), sessionRunId);
7159
7216
  }
7160
7217
  else {
7161
7218
  const baseSessionRoot = deps.config.defaultSessionDir
7162
7219
  ? path.resolve(deps.expandTilde(deps.config.defaultSessionDir))
7163
7220
  : deps.getSubagentSessionRoot(parentSessionFile);
7164
- sessionRoot = path.join(baseSessionRoot, runId);
7221
+ sessionRoot = path.join(baseSessionRoot, sessionRunId);
7165
7222
  }
7166
7223
  try {
7167
7224
  fs.mkdirSync(sessionRoot, { recursive: true });