pi-subagents 0.81.0 → 0.82.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 (141) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/bundle/agents.js +3 -3
  3. package/bundle/child-tool-plan.js +2 -2
  4. package/bundle/{chunk-LSRUTHT7.js → chunk-43SCHIMD.js} +1 -1
  5. package/bundle/{chunk-G5DKFLLI.js → chunk-4SDU2BZX.js} +15 -10
  6. package/bundle/{chunk-V2JWDB5U.js → chunk-52UBALJL.js} +2 -2
  7. package/bundle/{chunk-3DC5KBVP.js → chunk-5TYMSZX3.js} +1 -1
  8. package/bundle/{chunk-CEKS3TFO.js → chunk-7IVSUMQQ.js} +6 -6
  9. package/bundle/{chunk-KSWQVGDW.js → chunk-7PCKZHR5.js} +225 -18
  10. package/bundle/{chunk-563BHAZ5.js → chunk-7YMXOYEU.js} +33 -25
  11. package/bundle/{chunk-XMFLCKMQ.js → chunk-B5EKXDHE.js} +336 -130
  12. package/bundle/{chunk-ZBC44JJ3.js → chunk-CIZQXJ5Q.js} +22 -9
  13. package/bundle/{chunk-L5ZHE4L4.js → chunk-GC4DSPMH.js} +4 -4
  14. package/bundle/{chunk-ARRKBB2B.js → chunk-GJECHSAR.js} +2 -2
  15. package/bundle/{chunk-2ZSNH26H.js → chunk-M2CTFYT3.js} +7 -7
  16. package/bundle/{chunk-TUWLXTAM.js → chunk-MHZ4G23A.js} +681 -320
  17. package/bundle/{chunk-WSBNENFS.js → chunk-NGGAFDL6.js} +14 -4
  18. package/bundle/{chunk-VJHOELQP.js → chunk-NRQ75MGH.js} +160 -78
  19. package/bundle/{chunk-BJN5LUQY.js → chunk-OYF6AFDY.js} +1 -1
  20. package/bundle/{chunk-EYNJ6VL6.js → chunk-PLCCBLPY.js} +2 -2
  21. package/bundle/{chunk-74RT5YQE.js → chunk-RMQFMMAT.js} +123 -33
  22. package/bundle/{chunk-V6MR5TOQ.js → chunk-SIJ4S3EN.js} +25 -19
  23. package/bundle/{chunk-NNLASTQE.js → chunk-VH5OAWVG.js} +19 -5
  24. package/bundle/{chunk-BPS2AGBZ.js → chunk-W2SZ22EM.js} +93 -52
  25. package/bundle/{chunk-V3BIUPLU.js → chunk-WVD4UEL4.js} +256 -52
  26. package/bundle/{chunk-ZP3HODFH.js → chunk-XSWOTIA3.js} +1 -1
  27. package/bundle/{chunk-3IF2PGW7.js → chunk-YKEWIB4M.js} +1 -1
  28. package/bundle/{chunk-26C3WLYQ.js → chunk-ZLINIYTP.js} +497 -272
  29. package/bundle/{chunk-2XYWN7QJ.js → chunk-ZQGSDHL6.js} +2 -2
  30. package/bundle/control-channel.js +2 -2
  31. package/bundle/index.js +1 -1
  32. package/bundle/intercom-bridge.js +2 -2
  33. package/bundle/preflight.js +8 -8
  34. package/bundle/project-panes.js +3 -3
  35. package/docs/agents.md +31 -0
  36. package/docs/configuration.md +9 -1
  37. package/docs/tool-reference.md +3 -1
  38. package/docs/workflows.md +16 -0
  39. package/package.json +2 -2
  40. package/skills/pi-subagents/references/execution-controls.md +5 -4
  41. package/src/agents/agent-management.js +3 -3
  42. package/src/agents/agent-memory.js +3 -1
  43. package/src/agents/agents.js +6 -6
  44. package/src/agents/frontmatter.d.ts +2 -0
  45. package/src/agents/frontmatter.js +15 -1
  46. package/src/agents/skills.js +2 -1
  47. package/src/extension/config.js +3 -0
  48. package/src/extension/herdr-pi-bridge.js +17 -10
  49. package/src/extension/index.js +82 -2
  50. package/src/extension/schemas.js +3 -3
  51. package/src/intercom/native-supervisor-channel.js +20 -0
  52. package/src/missions/lifecycle.js +7 -1
  53. package/src/runs/background/active-async-capacity.d.ts +1 -0
  54. package/src/runs/background/active-async-capacity.js +9 -3
  55. package/src/runs/background/async-execution.js +20 -10
  56. package/src/runs/background/async-job-tracker.d.ts +6 -1
  57. package/src/runs/background/async-job-tracker.js +9 -4
  58. package/src/runs/background/async-status.d.ts +2 -0
  59. package/src/runs/background/async-status.js +21 -2
  60. package/src/runs/background/await-async-run.js +3 -7
  61. package/src/runs/background/boot-id.d.ts +12 -0
  62. package/src/runs/background/boot-id.js +47 -0
  63. package/src/runs/background/interrupted-elsewhere.d.ts +22 -0
  64. package/src/runs/background/interrupted-elsewhere.js +68 -0
  65. package/src/runs/background/notify.d.ts +2 -0
  66. package/src/runs/background/notify.js +13 -0
  67. package/src/runs/background/result-delivery-ownership.d.ts +6 -0
  68. package/src/runs/background/result-delivery-ownership.js +19 -1
  69. package/src/runs/background/result-watcher.d.ts +3 -1
  70. package/src/runs/background/result-watcher.js +24 -8
  71. package/src/runs/background/run-child-session.d.ts +1 -6
  72. package/src/runs/background/run-child-session.js +1 -1
  73. package/src/runs/background/run-status.js +2 -1
  74. package/src/runs/background/stale-run-reconciler.d.ts +25 -0
  75. package/src/runs/background/stale-run-reconciler.js +52 -13
  76. package/src/runs/background/subagent-runner-bootstrap.js +3 -0
  77. package/src/runs/background/subagent-runner.d.ts +4 -0
  78. package/src/runs/background/subagent-runner.js +13 -5
  79. package/src/runs/background/subagent-wait.d.ts +1 -1
  80. package/src/runs/background/subagent-wait.js +6 -1
  81. package/src/runs/background/terminal-run-index.d.ts +1 -0
  82. package/src/runs/background/terminal-run-index.js +4 -2
  83. package/src/runs/foreground/async-stop-action.js +3 -1
  84. package/src/runs/foreground/execution.js +6 -3
  85. package/src/runs/foreground/foreground-history.d.ts +16 -2
  86. package/src/runs/foreground/foreground-history.js +133 -31
  87. package/src/runs/foreground/subagent-executor.d.ts +11 -0
  88. package/src/runs/foreground/subagent-executor.js +277 -74
  89. package/src/runs/shared/child-launch.d.ts +2 -2
  90. package/src/runs/shared/child-session.d.ts +3 -2
  91. package/src/runs/shared/child-session.js +51 -8
  92. package/src/runs/shared/child-tool-plan.d.ts +7 -0
  93. package/src/runs/shared/child-tool-plan.js +21 -6
  94. package/src/runs/shared/herdr-connection.d.ts +17 -8
  95. package/src/runs/shared/herdr-connection.js +19 -7
  96. package/src/runs/shared/herdr-external-adapters.d.ts +3 -2
  97. package/src/runs/shared/herdr-external-adapters.js +1 -1
  98. package/src/runs/shared/herdr-machine.d.ts +12 -2
  99. package/src/runs/shared/herdr-machine.js +55 -9
  100. package/src/runs/shared/herdr-pane-host.d.ts +88 -0
  101. package/src/runs/shared/herdr-pane-host.js +155 -0
  102. package/src/runs/shared/herdr-placed-run.d.ts +27 -150
  103. package/src/runs/shared/herdr-placed-run.js +48 -395
  104. package/src/runs/shared/local-machine-access.d.ts +5 -0
  105. package/src/runs/shared/local-machine-access.js +28 -0
  106. package/src/runs/shared/orca-progress-tabs.d.ts +0 -1
  107. package/src/runs/shared/orca-progress-tabs.js +1 -24
  108. package/src/runs/shared/parallel-utils.d.ts +1 -1
  109. package/src/runs/shared/placed-run-owner.d.ts +49 -0
  110. package/src/runs/shared/placed-run-owner.js +93 -0
  111. package/src/runs/shared/placement-host.d.ts +99 -0
  112. package/src/runs/shared/placement-host.js +157 -0
  113. package/src/runs/shared/rex-machine.d.ts +25 -0
  114. package/src/runs/shared/rex-machine.js +83 -0
  115. package/src/runs/shared/rex-pane-host.d.ts +62 -0
  116. package/src/runs/shared/rex-pane-host.js +327 -0
  117. package/src/shared/completion-owner.d.ts +5 -0
  118. package/src/shared/completion-owner.js +5 -0
  119. package/src/shared/session-identity.d.ts +2 -0
  120. package/src/shared/session-identity.js +26 -0
  121. package/src/shared/types.d.ts +45 -6
  122. package/src/tui/fleet.d.ts +1 -0
  123. package/src/tui/fleet.js +24 -15
  124. package/src/tui/render.d.ts +6 -0
  125. package/src/tui/render.js +3 -3
  126. package/src/tui/subagent-messages.js +1 -0
  127. package/src/workflows/scripted-workflow.d.ts +1 -1
  128. package/src/workflows/workflow-auto-resume.d.ts +32 -0
  129. package/src/workflows/workflow-auto-resume.js +102 -0
  130. package/src/workflows/workflow-launch-record.d.ts +27 -0
  131. package/src/workflows/workflow-launch-record.js +48 -0
  132. package/src/workflows/workflow-resume-claim.d.ts +82 -0
  133. package/src/workflows/workflow-resume-claim.js +210 -0
  134. package/src/workflows/workflow-resume.d.ts +56 -0
  135. package/src/workflows/workflow-resume.js +106 -0
  136. package/src/workflows/workflow-reuse.d.ts +15 -9
  137. package/src/workflows/workflow-reuse.js +77 -14
  138. package/src/workflows/workflow-revival.d.ts +2 -0
  139. package/src/workflows/workflow-revival.js +2 -1
  140. package/src/workflows/workflow-stop-cause.d.ts +6 -0
  141. package/src/workflows/workflow-stop-cause.js +8 -0
@@ -9,7 +9,7 @@ import { isStorageCapacityError } from "../../shared/file-system-retry.js";
9
9
  import { resolveEffectiveThinking, toModelInfo } from "../../shared/model-info.js";
10
10
  import { beginForegroundChild, finishForegroundChild, foregroundSchedulingSettled, settleForegroundSchedulingOwner, updateForegroundChild, } from "./foreground-control.js";
11
11
  import { getLivePromptAudit, rewritePromptWithGuidance, updateLiveEffectivePrompt } from "./prompt-audit.js";
12
- import { persistForegroundRunHistory, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.js";
12
+ import { discardRunningForegroundRun, persistForegroundRun, MAX_REMEMBERED_FOREGROUND_RUNS } from "./foreground-history.js";
13
13
  import { projectScopeRequiresTrustMessage, resolveExecutionAgentScope } from "../../agents/agent-scope.js";
14
14
  import { handleManagementAction } from "../../agents/agent-management.js";
15
15
  import { handleRefinementAction } from "../../agents/agent-refinements.js";
@@ -71,6 +71,7 @@ import { dismissRecoveredWorkflow } from "./async-dismiss-action.js";
71
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
+ import { currentRecordedPid } from "../background/boot-id.js";
74
75
  import { resolveAsyncRootResultPath, waitForImportedAsyncRoot } from "../background/chain-root-attachment.js";
75
76
  import { awaitExistingAsyncRun, claimWorkflowAwaitedResult } from "../background/await-async-run.js";
76
77
  import { fallbackResultPayloadPathForSessionRun, resultFilePath, retireResultSnapshot, writeAsyncResultFile } from "../background/result-files.js";
@@ -82,7 +83,7 @@ import { inspectSubagentStatus } from "../background/run-status.js";
82
83
  import { getExternalJobProvider } from "../../api/external-job-provider.js";
83
84
  import { externalJobFollowUpRequestDigest, externalJobFollowUpRequestId, externalJobFollowUpRunId, externalJobPromptDigest, externalJobStableJson } from "../shared/external-job-runner.js";
84
85
  import { externalCliReceiptMetadata, normalizeExternalCliRunnerStatus } from "../shared/external-cli-contract.js";
85
- import { formatHerdrMachineRunnerUnsupported, resolveHerdrMachinePlacement } from "../shared/herdr-machine.js";
86
+ import { formatHerdrMachineRunnerUnsupported, resolveMachinePlacement } from "../shared/herdr-machine.js";
86
87
  import { lookupRunnerLauncher, resolveAgentRunnerLauncher } from "../shared/runner-launcher.js";
87
88
  import { applyForceTopLevelAsyncOverride } from "../background/top-level-async.js";
88
89
  import { handleMissionAction, MISSION_ACTIONS } from "../../missions/actions.js";
@@ -96,7 +97,9 @@ import { handleHerdrProjectPaneAction, HERDR_PROJECT_PANE_ACTIONS } from "../../
96
97
  import { previewSimpleWorkflowRun, runWorkflowScript, validateWorkflowScript, WorkflowScriptError } from "../../workflows/scripted-workflow.js";
97
98
  import { formatIncrementalChildCompletion, incrementalChildCompletionTriggersTurn } from "../background/notify.js";
98
99
  import { COMPLETION_ACTION, STATUS_ACTION } from "../../shared/parent-wake.js";
99
- import { appendWorkflowChildJournal, findWorkflowReuseSource, matchWorkflowReuse, readNamedWorkflowReuseSource, workflowChildFingerprint, workflowRuntimeReplacedRelaunchNotice, workflowScriptDigest, workflowStopCause } from "../../workflows/workflow-reuse.js";
100
+ import { appendWorkflowChildJournal, claimedWorkflowRunRefusal, findWorkflowReuseSource, matchWorkflowReuse, readNamedWorkflowReuseSource, WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE, workflowChildFingerprint, workflowScriptDigest, workflowStopCause } from "../../workflows/workflow-reuse.js";
101
+ import { claimWorkflowResume, recordWorkflowResumeRefusal, releaseWorkflowResumeClaim, resumedRunStarted } from "../../workflows/workflow-resume-claim.js";
102
+ import { manualWorkflowResumeRefusalText, prepareWorkflowResume, workflowResumeRefusal, workflowResumeTargetId } from "../../workflows/workflow-resume.js";
100
103
  import { executeWorkflowHostCommand, resolveWorkflowHostOutputClaimPath } from "../../workflows/host-command.js";
101
104
  import { buildWorkflowReceipt, readWorkflowReceipt, workflowReceiptPath, resolveWorkflowReceiptResumeEntry, writeWorkflowReceipt } from "../../workflows/workflow-receipt.js";
102
105
  import { formatWorkflowKeyRevival, recordWorkflowRevival, withWorkflowRevivals } from "../../workflows/workflow-revival.js";
@@ -107,6 +110,7 @@ import { resolveWorkflowChatProgress } from "../../workflows/chat-progress.js";
107
110
  import { annotateWorkflowPreflightTrace, formatWorkflowPreflight, formatWorkflowPreflightWarnings, normalizeWorkflowPreflight, workflowPreflightWarnings } from "../../workflows/workflow-preflight.js";
108
111
  import { authorizeWorkflowResourceHost, claimWorkflowChildPermit, consumeWorkflowResourcePermit, validateWorkflowChildPermitRoot, } from "../../shared/workflow-child-permit.js";
109
112
  import { deepFreezeWorkflowArgs, normalizeWorkflowArgs, resolveStructuredWorkflowResource, resolveWorkflowResource } from "../../workflows/workflow-resources.js";
113
+ import { writeWorkflowLaunchRecord } from "../../workflows/workflow-launch-record.js";
110
114
  import { stableJsonDigest } from "../../shared/launch-contract.js";
111
115
  import { cleanupWorktrees, createWorktrees, preflightWorktreeSource, withWorktreeTransaction, diffWorktrees, formatWorktreeDiffSummary, } from "../shared/worktree.js";
112
116
  import { applyReviewedCleanupPlan, formatCleanupReceipt, loadReviewedCleanupPlan } from "../shared/worktree-cleanup-apply.js";
@@ -400,9 +404,12 @@ function trimRememberedForegroundRuns(state) {
400
404
  state.foregroundRuns.delete(oldestTerminal.runId);
401
405
  }
402
406
  }
403
- function persistRememberedForegroundRuns(state) {
407
+ function persistRememberedForegroundRun(state, runId) {
408
+ const run = state.foregroundRuns?.get(runId);
409
+ if (!run)
410
+ return;
404
411
  try {
405
- persistForegroundRunHistory(state);
412
+ persistForegroundRun(run);
406
413
  }
407
414
  catch (error) {
408
415
  console.error("Failed to persist foreground run history:", error);
@@ -422,6 +429,50 @@ function foregroundChildActivityFromProgress(progress) {
422
429
  ...(progress?.toolCount !== undefined ? { toolCount: progress.toolCount } : {}),
423
430
  };
424
431
  }
432
+ /** One remembered child: the result's outcome plus the launch contract that resume needs. */
433
+ function rememberedForegroundChild(input, result, index, status, updatedAt) {
434
+ const resumeContract = omitUndefinedProperties({
435
+ modelResponseAliases: input.modelResponseAliases,
436
+ outputSchema: input.params.outputSchema,
437
+ agentContract: input.params.agentContract,
438
+ acceptance: input.params.acceptance,
439
+ output: input.effectiveOutput,
440
+ outputMode: input.effectiveOutputMode,
441
+ });
442
+ return {
443
+ agent: result.agent,
444
+ index,
445
+ ...(result.sessionName ? { sessionName: result.sessionName } : {}),
446
+ ...(result.context ? { context: result.context } : {}),
447
+ status,
448
+ ...foregroundChildActivityFromProgress(result.progress),
449
+ updatedAt,
450
+ ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
451
+ ...(result.error ? { error: result.error } : {}),
452
+ ...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
453
+ ...(result.outputState ? { outputState: result.outputState } : {}),
454
+ ...(result.outputPartial ? { outputPartial: true } : {}),
455
+ ...(result.outputMode ? { outputMode: result.outputMode } : {}),
456
+ ...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
457
+ ...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
458
+ ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
459
+ ...(result.model ? { model: result.model } : {}),
460
+ ...(result.thinking ? { thinking: result.thinking } : {}),
461
+ ...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
462
+ ...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
463
+ ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
464
+ ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
465
+ ...(result.acceptance ? { acceptance: result.acceptance } : {}),
466
+ ...(Object.keys(resumeContract).length ? { resumeContract } : {}),
467
+ ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
468
+ ...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
469
+ ...(input.requiredExtensions?.length ? { requiredExtensions: input.requiredExtensions } : {}),
470
+ ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
471
+ ...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
472
+ ...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
473
+ ...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
474
+ };
475
+ }
425
476
  function rememberForegroundRun(state, input) {
426
477
  state.foregroundRuns ??= new Map();
427
478
  const previous = state.foregroundRuns.get(input.runId);
@@ -433,61 +484,43 @@ function rememberForegroundRun(state, input) {
433
484
  ...(input.sessionId ? { sessionId: input.sessionId } : {}),
434
485
  updatedAt,
435
486
  children: input.results.map((result, index) => {
436
- const resumeContract = omitUndefinedProperties({
437
- modelResponseAliases: input.modelResponseAliases,
438
- outputSchema: input.params.outputSchema,
439
- agentContract: input.params.agentContract,
440
- acceptance: input.params.acceptance,
441
- output: input.effectiveOutput,
442
- outputMode: input.effectiveOutputMode,
443
- });
444
- const child = {
445
- agent: result.agent,
446
- index,
447
- ...(result.sessionName ? { sessionName: result.sessionName } : {}),
448
- ...(result.context ? { context: result.context } : {}),
449
- status: resolveSubagentResultStatus(omitUndefinedProperties({
450
- exitCode: result.exitCode,
451
- interrupted: result.interrupted,
452
- detached: result.detached,
453
- processSignal: result.processSignal,
454
- timedOut: result.timedOut,
455
- stopped: result.stopped,
456
- turnBudgetExceeded: result.turnBudgetExceeded,
457
- })),
458
- ...foregroundChildActivityFromProgress(result.progress),
459
- updatedAt,
460
- ...(result.exitCode !== undefined ? { exitCode: result.exitCode } : {}),
461
- ...(result.error ? { error: result.error } : {}),
462
- ...(result.finalOutput ? { finalOutput: result.finalOutput } : {}),
463
- ...(result.outputState ? { outputState: result.outputState } : {}),
464
- ...(result.outputPartial ? { outputPartial: true } : {}),
465
- ...(result.outputMode ? { outputMode: result.outputMode } : {}),
466
- ...(result.savedOutputPath ? { savedOutputPath: result.savedOutputPath } : {}),
467
- ...(result.outputSaveError ? { outputSaveError: result.outputSaveError } : {}),
468
- ...(result.sessionFile ? { sessionFile: result.sessionFile } : {}),
469
- ...(result.model ? { model: result.model } : {}),
470
- ...(result.thinking ? { thinking: result.thinking } : {}),
471
- ...(result.artifactPaths ? { artifactPaths: result.artifactPaths } : {}),
472
- ...(result.transcriptPath ? { transcriptPath: result.transcriptPath } : {}),
473
- ...(result.transcriptError ? { transcriptError: result.transcriptError } : {}),
474
- ...(result.detachedReason ? { detachedReason: result.detachedReason } : {}),
475
- ...(result.acceptance ? { acceptance: result.acceptance } : {}),
476
- ...(Object.keys(resumeContract).length ? { resumeContract } : {}),
477
- ...(result.launchContractDigest ? { launchContractDigest: result.launchContractDigest } : {}),
478
- ...(input.extensionBindings ? { extensionBindings: input.extensionBindings } : {}),
479
- ...(input.requiredExtensions?.length ? { requiredExtensions: input.requiredExtensions } : {}),
480
- ...(result.launchResolvedExtensions ? { launchResolvedExtensions: result.launchResolvedExtensions } : {}),
481
- ...(result.runtimeAcknowledgedExtensions ? { runtimeAcknowledgedExtensions: result.runtimeAcknowledgedExtensions } : {}),
482
- ...(result.capabilityCeiling ? { capabilityCeiling: result.capabilityCeiling } : {}),
483
- ...(result.capabilityAudit ? { capabilityAudit: result.capabilityAudit } : {}),
484
- };
487
+ const child = rememberedForegroundChild(input, result, index, resolveSubagentResultStatus(omitUndefinedProperties({
488
+ exitCode: result.exitCode,
489
+ interrupted: result.interrupted,
490
+ detached: result.detached,
491
+ processSignal: result.processSignal,
492
+ timedOut: result.timedOut,
493
+ stopped: result.stopped,
494
+ turnBudgetExceeded: result.turnBudgetExceeded,
495
+ })), updatedAt);
485
496
  const recovered = previous?.children[index];
486
497
  return child.status === "detached" && recovered && recovered.status !== "detached" ? recovered : child;
487
498
  }),
488
499
  });
489
500
  trimRememberedForegroundRuns(state);
490
- persistRememberedForegroundRuns(state);
501
+ persistRememberedForegroundRun(state, input.runId);
502
+ }
503
+ /**
504
+ * Saves a starting single run to its history file only, so a Pi exit before the run ends leaves a
505
+ * record that loads as stopped and resumable. The final save replaces it by run id.
506
+ */
507
+ function rememberRunningForegroundRun(input) {
508
+ const updatedAt = Date.now();
509
+ try {
510
+ persistForegroundRun({
511
+ runId: input.runId,
512
+ mode: "single",
513
+ cwd: input.cwd,
514
+ ...(input.sessionId ? { sessionId: input.sessionId } : {}),
515
+ updatedAt,
516
+ children: [rememberedForegroundChild(input, input.child, 0, "running", updatedAt)],
517
+ owner: currentRecordedPid(),
518
+ ...(input.parentWorkflowRunId ? { parentWorkflowRunId: input.parentWorkflowRunId } : {}),
519
+ });
520
+ }
521
+ catch (error) {
522
+ console.error("Failed to persist running foreground run:", error);
523
+ }
491
524
  }
492
525
  function applyControlEventToRememberedForegroundRun(state, event) {
493
526
  const run = state.foregroundRuns?.get(event.runId);
@@ -565,7 +598,7 @@ function updateRememberedForegroundChild(state, input) {
565
598
  ...(input.result.capabilityAudit ? { capabilityAudit: input.result.capabilityAudit } : {}),
566
599
  });
567
600
  trimRememberedForegroundRuns(state);
568
- persistRememberedForegroundRuns(state);
601
+ persistRememberedForegroundRun(state, input.runId);
569
602
  const output = getSingleResultOutput(input.result).trim();
570
603
  const success = terminalStatus === "completed";
571
604
  const summary = !success && input.result.error
@@ -3040,9 +3073,9 @@ function importWorkflowAwaitedChildResult(completed, options) {
3040
3073
  };
3041
3074
  }
3042
3075
  // A replaced runtime leaves the child running so a relaunch of the workflow can re-attach to it.
3043
- function stopAwaitedAsyncChildOnAbort(signal, state, runId, asyncDir, kill, reattachedFromOtherSession = false) {
3076
+ function stopAwaitedAsyncChildOnAbort(signal, state, runId, asyncDir, kill, reattached = false) {
3044
3077
  const stopOnAbort = () => { if (!workflowStopCause(signal?.reason))
3045
- stopAsyncRun(state, runId, kill, { asyncDir, resolvedId: runId, ...(reattachedFromOtherSession ? { reattachedRunId: runId } : {}) }); };
3078
+ stopAsyncRun(state, runId, kill, { asyncDir, resolvedId: runId, ...(reattached ? { reattachedRunId: runId } : {}) }); };
3046
3079
  if (signal?.aborted)
3047
3080
  stopOnAbort();
3048
3081
  else
@@ -3563,7 +3596,7 @@ async function runSinglePath(data, deps) {
3563
3596
  if (requestedMachine) {
3564
3597
  try {
3565
3598
  assertRequiredChildExtensionsAdmitted([requiredExtensions], { agent: agentConfig.name, runnerType: agentConfig.runner?.type, machine: requestedMachine });
3566
- foregroundMachine = resolveHerdrMachinePlacement({ machine: requestedMachine, cwd: ctx.cwd, stepCwd: params.machineCwd }).machine;
3599
+ foregroundMachine = resolveMachinePlacement({ machine: requestedMachine, cwd: ctx.cwd, stepCwd: params.machineCwd }).machine;
3567
3600
  }
3568
3601
  catch (error) {
3569
3602
  return toExecutionErrorResult(params, error instanceof Error ? error : new Error(String(error)), data.contextPolicy.contextSummary);
@@ -3749,6 +3782,15 @@ async function runSinglePath(data, deps) {
3749
3782
  }
3750
3783
  : undefined;
3751
3784
  const deadlineAt = data.deadlineAt ?? (data.timeoutMs !== undefined ? Date.now() + data.timeoutMs : undefined);
3785
+ // A launch that ends without its final save drops the launch-time record, or it would later load as interrupted.
3786
+ const discardRunningRecord = () => {
3787
+ try {
3788
+ discardRunningForegroundRun(runId);
3789
+ }
3790
+ catch (discardError) {
3791
+ console.error("Failed to discard running foreground run:", discardError);
3792
+ }
3793
+ };
3752
3794
  let r;
3753
3795
  let resolveDetachedWorkflowChild;
3754
3796
  const detachedWorkflowChild = params.workflowAwaitDetached === true
@@ -3756,6 +3798,18 @@ async function runSinglePath(data, deps) {
3756
3798
  : undefined;
3757
3799
  try {
3758
3800
  const childSessionFile = sessionFileForTask(params.agent, 0, modelOverride, modelOverrideFromParent, modelOrigin);
3801
+ rememberRunningForegroundRun({
3802
+ modelResponseAliases, runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, params, effectiveOutput, effectiveOutputMode, extensionBindings: params.extensionBindings, requiredExtensions,
3803
+ child: omitUndefinedProperties({
3804
+ agent: params.agent,
3805
+ context: shouldForkAgent(contextPolicy, params.agent) ? "fork" : "fresh",
3806
+ sessionFile: childSessionFile,
3807
+ model: modelOverride,
3808
+ thinking: resolveEffectiveThinking(modelOverride, thinkingOverrideForTask()),
3809
+ capabilityCeiling: data.capabilityCeiling,
3810
+ }),
3811
+ parentWorkflowRunId: params.workflowParentRunId ?? foregroundControl?.parentWorkflowRunId,
3812
+ });
3759
3813
  const launched = await runSync(ctx.cwd, agents, params.agent, task, compactOptional({
3760
3814
  machine: foregroundMachine,
3761
3815
  parentProviderRegistry: ctx.modelRegistry,
@@ -3833,7 +3887,13 @@ async function runSinglePath(data, deps) {
3833
3887
  }
3834
3888
  try {
3835
3889
  if (worktreeSetup) {
3836
- await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent, result, workflowKey: params.workflowKey, lane });
3890
+ try {
3891
+ await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent, result, workflowKey: params.workflowKey, lane });
3892
+ }
3893
+ catch (error) {
3894
+ discardRunningRecord();
3895
+ throw error;
3896
+ }
3837
3897
  }
3838
3898
  try {
3839
3899
  updateRememberedForegroundChild(deps.state, { runId, mode: "single", cwd: singleCwd, sessionId: data.parentSessionId, index: 0, result, events: deps.pi.events, notify: true });
@@ -3894,6 +3954,7 @@ async function runSinglePath(data, deps) {
3894
3954
  r = launched.detached && detachedWorkflowChild ? await detachedWorkflowChild : launched;
3895
3955
  }
3896
3956
  catch (error) {
3957
+ discardRunningRecord();
3897
3958
  await cleanupSingleWorktree();
3898
3959
  throw error;
3899
3960
  }
@@ -3909,9 +3970,15 @@ async function runSinglePath(data, deps) {
3909
3970
  }
3910
3971
  let worktreeHandoff;
3911
3972
  if (worktreeSetup) {
3912
- worktreeHandoff = r.detached
3913
- ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
3914
- : await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent, result: r, workflowKey: params.workflowKey, lane });
3973
+ try {
3974
+ worktreeHandoff = r.detached
3975
+ ? { suffix: pendingHandoff ? formatParallelHandoffReference(pendingHandoff) : "", reference: pendingHandoff }
3976
+ : await finalizeSingleWorktreeHandoff({ worktreeSetup, artifactsDir, runId, cwd: sourceCwd, agent: params.agent, result: r, workflowKey: params.workflowKey, lane });
3977
+ }
3978
+ catch (error) {
3979
+ discardRunningRecord();
3980
+ throw error;
3981
+ }
3915
3982
  }
3916
3983
  if (r.progress)
3917
3984
  allProgress.push(r.progress);
@@ -4731,6 +4798,17 @@ function createScheduledOwnerState(source, ownerSessionId, ctx) {
4731
4798
  workflowChildStops: new Map(),
4732
4799
  };
4733
4800
  }
4801
+ /** Drops the fields that tie a launch to one run: a relaunch sets its own reuse and origin. */
4802
+ function savedWorkflowLaunchParams(params) {
4803
+ const { reuseFrom: _reuseFrom, scheduleOrigin: _scheduleOrigin, resumeOrigin: _resumeOrigin, ...saved } = params;
4804
+ return saved;
4805
+ }
4806
+ function workflowLaunchRecord(launch, cwd, scriptDigest) {
4807
+ if (launch.source === "structured")
4808
+ return { version: 1, source: "structured", scriptDigest };
4809
+ // The resolved cwd is saved: a relative one would resolve against a later session's cwd.
4810
+ return { version: 1, source: launch.source, params: { ...launch.params, cwd }, scriptDigest };
4811
+ }
4734
4812
  export function createSubagentExecutor(deps) {
4735
4813
  const executorDeps = deps;
4736
4814
  const delegatedThinkingOverrides = new WeakMap();
@@ -4738,6 +4816,9 @@ export function createSubagentExecutor(deps) {
4738
4816
  const delegatedExecutions = new WeakSet();
4739
4817
  const publicExecutions = new WeakSet();
4740
4818
  const workflowResourcePermits = new WeakMap();
4819
+ // Public launches captured by executePublic for launch.json; never built from the internal request.
4820
+ const publicWorkflowLaunches = new WeakMap();
4821
+ const workflowResumeLaunches = new WeakMap();
4741
4822
  const workflowPermitContexts = new WeakMap();
4742
4823
  const warnedArtifactPackageDirs = new Set();
4743
4824
  const scheduledOwnerExecutors = new Map();
@@ -4755,7 +4836,14 @@ export function createSubagentExecutor(deps) {
4755
4836
  throw new Error(error);
4756
4837
  }
4757
4838
  };
4758
- const execute = async (_id, params, signal, onUpdate, ctx, preserveActiveSession = false, parentModelOverride) => {
4839
+ // A plain-reuse claim is given back when its request ends, by any return or throw, before the new run's status.json exists.
4840
+ const pendingReuseClaimReleases = new WeakMap();
4841
+ const execute = (...args) => executeRequest(...args).finally(() => {
4842
+ const releaseReuseClaim = pendingReuseClaimReleases.get(args[1]);
4843
+ pendingReuseClaimReleases.delete(args[1]);
4844
+ releaseReuseClaim?.();
4845
+ });
4846
+ const executeRequest = async (_id, params, signal, onUpdate, ctx, preserveActiveSession = false, parentModelOverride) => {
4759
4847
  const projectScopeError = projectScopeTrustError(params, ctx);
4760
4848
  if (projectScopeError)
4761
4849
  return projectScopeError;
@@ -4768,6 +4856,8 @@ export function createSubagentExecutor(deps) {
4768
4856
  const delegatedExecution = delegatedExecutions.has(params);
4769
4857
  const publicExecution = publicExecutions.has(params);
4770
4858
  const workflowResourcePermit = workflowResourcePermits.get(params);
4859
+ const workflowResumeLaunch = workflowResumeLaunches.get(params);
4860
+ const publicWorkflowLaunch = publicWorkflowLaunches.get(params);
4771
4861
  const workflowPermitContext = workflowPermitContexts.get(params);
4772
4862
  const delegatedWorkflowPermit = workflowPermitContext && "root" in workflowPermitContext ? workflowPermitContext.root : undefined;
4773
4863
  const workflowChildPermitLaunch = workflowPermitContext && "child" in workflowPermitContext ? workflowPermitContext.child : undefined;
@@ -4914,14 +5004,18 @@ export function createSubagentExecutor(deps) {
4914
5004
  && workflowDepth === 0
4915
5005
  && !inheritedNestedRoute(deps)
4916
5006
  && !requestParams.workflowParentRunId;
4917
- const workflowRunId = asyncWorkflow ? randomUUID() : undefined;
5007
+ // A resume claimed its run id before launching, so the claim already names this run.
5008
+ const workflowRunId = asyncWorkflow ? workflowResumeLaunch?.presetRunId ?? randomUUID() : undefined;
4918
5009
  if (requestParams.reuseFrom !== undefined && !topLevelAsyncWorkflow) {
4919
5010
  return { content: [{ type: "text", text: "options.reuseFrom requires an async top-level workflow." }], isError: true, details: { mode: "workflow", results: [] } };
4920
5011
  }
4921
- let namedWorkflowReuseSource;
5012
+ // Both reuse readers run before capacity is taken: a run another relaunch continues is refused here.
5013
+ let workflowReuseSource;
4922
5014
  try {
4923
5015
  if (requestParams.reuseFrom !== undefined)
4924
- namedWorkflowReuseSource = readNamedWorkflowReuseSource(DIRS.async, requestParams.reuseFrom, workflowScriptDigest(requestParams.workflowScript), workflowCwd);
5016
+ workflowReuseSource = readNamedWorkflowReuseSource(DIRS.async, requestParams.reuseFrom, workflowScriptDigest(requestParams.workflowScript), workflowCwd, workflowResumeLaunch?.presetRunId);
5017
+ else if (workflowRunId)
5018
+ workflowReuseSource = findWorkflowReuseSource(DIRS.async, resolveCurrentSessionId(ctx.sessionManager), workflowScriptDigest(requestParams.workflowScript), workflowArgsDigest);
4925
5019
  }
4926
5020
  catch (error) {
4927
5021
  return { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
@@ -5003,19 +5097,43 @@ export function createSubagentExecutor(deps) {
5003
5097
  const completionOwnerId = deps.state.completionOwnerId ?? currentCompletionOwnerId();
5004
5098
  deps.state.completionOwnerId = completionOwnerId;
5005
5099
  const workflowScriptSourceDigest = workflowScriptDigest(requestParams.workflowScript);
5006
- const workflowReuseSource = namedWorkflowReuseSource ?? findWorkflowReuseSource(DIRS.async, currentSessionId, workflowScriptSourceDigest, workflowArgsDigest);
5007
5100
  const workflowRunEvidence = { ...workflowArgsEvidence, scriptDigest: workflowScriptSourceDigest };
5008
5101
  if (workflowReuseSource)
5009
5102
  workflowRunEvidence.reusedFrom = workflowReuseSource.runId;
5103
+ // In the first persisted status, before any child launches, so a later resume can count attempts.
5104
+ if (workflowResumeLaunch)
5105
+ workflowRunEvidence.resumeOrigin = workflowResumeLaunch.resumeOrigin;
5010
5106
  const workflowJournalFingerprints = new Map();
5107
+ const storageFailure = (error) => {
5108
+ workflowCapacity?.rollback();
5109
+ return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
5110
+ };
5111
+ // The relaunch continuing a stopped run holds its claim, so any other relaunch of that run is refused.
5112
+ if (workflowReuseSource) {
5113
+ const reuseSourceDir = path.join(DIRS.async, workflowReuseSource.runId);
5114
+ let claim;
5115
+ try {
5116
+ claim = claimWorkflowResume(reuseSourceDir, { newRunId: workflowRunId, trigger: "reuse" });
5117
+ }
5118
+ catch (error) {
5119
+ return storageFailure(error);
5120
+ }
5121
+ if (claim.kind !== "owner") {
5122
+ workflowCapacity?.rollback();
5123
+ return { content: [{ type: "text", text: claimedWorkflowRunRefusal(workflowReuseSource.runId, claim, requestParams.reuseFrom !== undefined) }], isError: true, details: { mode: "workflow", results: [] } };
5124
+ }
5125
+ pendingReuseClaimReleases.set(params, () => { if (!fs.existsSync(statusPath))
5126
+ releaseWorkflowResumeClaim(reuseSourceDir, workflowRunId); });
5127
+ }
5011
5128
  try {
5012
5129
  fs.mkdirSync(asyncDir, { recursive: true });
5013
5130
  writeRunFanoutBudgetDescriptor(asyncDir, workflowFanoutBudget);
5131
+ if (topLevelAsyncWorkflow && publicWorkflowLaunch)
5132
+ writeWorkflowLaunchRecord(asyncDir, workflowLaunchRecord(publicWorkflowLaunch, workflowCwd, workflowScriptSourceDigest));
5014
5133
  fs.mkdirSync(DIRS.results, { recursive: true });
5015
5134
  }
5016
5135
  catch (error) {
5017
- workflowCapacity?.rollback();
5018
- return { content: [{ type: "text", text: `Failed to create async workflow storage: ${error instanceof Error ? error.message : String(error)}` }], isError: true, details: { mode: "workflow", results: [] } };
5136
+ return storageFailure(error);
5019
5137
  }
5020
5138
  const controller = new AbortController();
5021
5139
  deps.state.workflowControllers ??= new Map();
@@ -5037,7 +5155,7 @@ export function createSubagentExecutor(deps) {
5037
5155
  cwd: workflowCwd,
5038
5156
  ...(workflowSessionRoot ? { sessionRoot: workflowSessionRoot } : {}),
5039
5157
  ...(requestParams.scheduleOrigin ? { scheduleOrigin: requestParams.scheduleOrigin } : {}),
5040
- pid: process.pid,
5158
+ ...currentRecordedPid(),
5041
5159
  ...(workflowCapabilityCeiling ? { admissionCapabilityCeiling: workflowCapabilityCeiling } : {}),
5042
5160
  steps: [],
5043
5161
  ...(workflowPreflight ? { preflight: workflowPreflight } : {}),
@@ -5741,7 +5859,8 @@ export function createSubagentExecutor(deps) {
5741
5859
  const priorAsyncDir = path.join(DIRS.async, priorRunId);
5742
5860
  appendWorkflowChildJournal(asyncDir, { type: "start", key, fingerprint: journalFingerprint, runId: priorRunId });
5743
5861
  recordReusedWorkflowChild(key, priorRunId);
5744
- const stopListener = stopAwaitedAsyncChildOnAbort(workflowSignal, deps.state, priorRunId, priorAsyncDir, deps.kill, requestParams.reuseFrom !== undefined);
5862
+ // The journal names this exact run, which may belong to another session anywhere in the relaunch chain.
5863
+ const stopListener = stopAwaitedAsyncChildOnAbort(workflowSignal, deps.state, priorRunId, priorAsyncDir, deps.kill, true);
5745
5864
  const reattached = await awaitExistingAsyncRun(priorAsyncDir, priorRunId, workflowSignal).finally(stopListener.remove);
5746
5865
  const claimedPath = reattached.status === "settled" ? claimWorkflowAwaitedResult(priorAsyncDir, workflowRunId, { ...reattached.result.importedPublication, runId: priorRunId }) : undefined;
5747
5866
  if (reattached.status === "settled" && claimedPath) {
@@ -5939,7 +6058,7 @@ export function createSubagentExecutor(deps) {
5939
6058
  const errorPreview = status.error && status.error.length > 1_000 ? `${status.error.slice(0, 1_000)}… (truncated; full error: ${statusPath} (error))` : status.error;
5940
6059
  const terminalSummary = `${status.state === "complete"
5941
6060
  ? "Workflow completed after detached child finished."
5942
- : errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${stopCause ? ` ${workflowRuntimeReplacedRelaunchNotice(workflowRunId)}` : ""}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
6061
+ : errorPreview ?? (pauseForDetached ? "Workflow paused." : "Workflow failed.")}${stopCause ? ` ${WORKFLOW_RUNTIME_REPLACED_STOP_NOTICE}` : ""}${workflowOutputPathMappingSummary(partial.children)}${finalPreflightWarnings.length ? ` ${finalPreflightWarnings.join(" ")}` : ""}`;
5943
6062
  const outputWarning = writeWorkflowAggregateOutput(workflowAggregateOutputPath, terminalSummary, producedChildOutputPaths);
5944
6063
  const resultSummary = appendWorkflowOutputWarning(terminalSummary, outputWarning);
5945
6064
  const receiptState = status.state === "complete" ? "complete" : status.state === "paused" ? "paused" : status.state === "stopped" ? "stopped" : "failed";
@@ -6013,6 +6132,7 @@ export function createSubagentExecutor(deps) {
6013
6132
  let liveWorkflow = { trace: [], emits: [], console: [], ...(workflowResource ? { resource: workflowResource.provenance } : {}) };
6014
6133
  const childProgress = new Map();
6015
6134
  const childLaunches = new Map();
6135
+ const blockingWorkflowChildren = new Set();
6016
6136
  let activityTimer;
6017
6137
  let lastProgressAt = 0;
6018
6138
  let workflowProgressClosed = false;
@@ -6114,6 +6234,10 @@ export function createSubagentExecutor(deps) {
6114
6234
  workflowPermitContexts.set(childRequest, { child: { permit: delegatedWorkflowPermit, workflowRunId: foregroundWorkflowRunId, childKey: key } });
6115
6235
  }
6116
6236
  workflowLaunchObservers.set(childRequest, (launch) => {
6237
+ if (launch.async && launch.runId && (childRequest.workflowAwaitAsync === true || childRequest.action === "resume")) {
6238
+ (deps.state.blockingWorkflowChildren ??= new Set()).add(launch.runId);
6239
+ blockingWorkflowChildren.add(launch.runId);
6240
+ }
6117
6241
  childLaunches.set(key, { agent: launch.agent, ...(launch.runId ? { runId: launch.runId } : {}) });
6118
6242
  // Telemetry must not decide the child's outcome, as with onTrace in scripted-workflow.
6119
6243
  try {
@@ -6242,6 +6366,8 @@ export function createSubagentExecutor(deps) {
6242
6366
  }
6243
6367
  finally {
6244
6368
  workflowProgressClosed = true;
6369
+ for (const runId of blockingWorkflowChildren)
6370
+ deps.state.blockingWorkflowChildren?.delete(runId);
6245
6371
  if (activityTimer)
6246
6372
  clearTimeout(activityTimer);
6247
6373
  childProgress.clear();
@@ -6705,6 +6831,9 @@ export function createSubagentExecutor(deps) {
6705
6831
  return withBudget(inspectSubagentStatus(paramsWithResolvedCwd, omitUndefinedProperties({ state: deps.state, nested: nestedScope, sessionRoots, abandonedSlotReleaseAfterMs: resolveAbandonedSlotReleaseAfterMs(deps.config.capacity?.abandonedSlotReleaseAfterMs) })));
6706
6832
  }
6707
6833
  if (action === "resume") {
6834
+ const workflowResumeSourceId = workflowResumeTargetId(DIRS.async, paramsWithResolvedCwd);
6835
+ if (workflowResumeSourceId)
6836
+ return resumeWorkflowById(_id, workflowResumeSourceId, signal, ctx);
6708
6837
  return resumeAsyncRun(omitUndefinedProperties({
6709
6838
  params: paramsWithResolvedCwd,
6710
6839
  requestCwd,
@@ -7629,6 +7758,8 @@ export function createSubagentExecutor(deps) {
7629
7758
  const errorResult = (text) => Promise.resolve({ content: [{ type: "text", text }], isError: true, details: { mode: publicParams.action ? "management" : "workflow", results: [] } });
7630
7759
  // Models tend to put script text in the workflow string; say how to pass it instead.
7631
7760
  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 }).";
7761
+ let workflowLaunch;
7762
+ const workflowResumeLaunch = workflowResumeLaunches.get(params);
7632
7763
  if (publicParams.tasks !== undefined || publicParams.chain !== undefined) {
7633
7764
  // Normalization admits chain/tasks only with workflow scripts disabled; the original input was checked above.
7634
7765
  const kind = publicParams.tasks !== undefined ? "tasks" : "chain";
@@ -7638,12 +7769,24 @@ export function createSubagentExecutor(deps) {
7638
7769
  const { tasks: _tasks, chain: _chain, task: _task, ...withoutStructuredInput } = publicParams;
7639
7770
  publicParams = { ...withoutStructuredInput, workflowScript: resolved.resource.script };
7640
7771
  workflowResourcePermits.set(publicParams, resolved.resource.permit);
7772
+ workflowLaunch = { source: "structured" };
7641
7773
  }
7642
7774
  else if (typeof workflow === "string" && !isWorkflowScriptPath(workflow)) {
7643
7775
  const resolved = resolveWorkflowResource(workflow, publicParams.args, ctx.sessionManager.getSessionId() ?? undefined);
7776
+ // A resumed resource is resolved with current authority and must still be the script that ran.
7777
+ if (workflowResumeLaunch && !resolved.ok) {
7778
+ workflowResumeLaunch.refusal = { code: "authority", reason: `workflow resource '${workflow}' can no longer be resolved: ${resolved.error}` };
7779
+ return errorResult(workflowResumeLaunch.refusal.reason);
7780
+ }
7781
+ if (workflowResumeLaunch && resolved.ok && workflowScriptDigest(resolved.resource.script) !== workflowResumeLaunch.expectedScriptDigest) {
7782
+ workflowResumeLaunch.refusal = { code: "digest", reason: `workflow resource '${workflow}' changed since the run started` };
7783
+ return errorResult(workflowResumeLaunch.refusal.reason);
7784
+ }
7644
7785
  if (!resolved.ok)
7645
7786
  return Promise.resolve({ content: [{ type: "text", text: resolved.error + scriptTextHint }], isError: true, details: { mode: "workflow", results: [] } });
7646
7787
  const { workflow: _workflow, args: _args, ...withoutResourceInput } = publicParams;
7788
+ // Saved by name: the runs.host permit cannot be saved, so a relaunch resolves the resource again.
7789
+ workflowLaunch = { source: "resource", params: savedWorkflowLaunchParams(publicParams) };
7647
7790
  publicParams = { ...withoutResourceInput, workflowScript: resolved.resource.script };
7648
7791
  workflowResourcePermits.set(publicParams, resolved.resource.permit);
7649
7792
  }
@@ -7662,10 +7805,70 @@ export function createSubagentExecutor(deps) {
7662
7805
  }
7663
7806
  const { workflow: _workflow, ...withoutWorkflowSource } = publicParams;
7664
7807
  publicParams = { ...withoutWorkflowSource, workflowScript, args: "error" in normalizedArgs ? publicParams.args : deepFreezeWorkflowArgs(normalizedArgs.args) };
7808
+ // The script is saved inline: a reply block or a script file cannot be read again later.
7809
+ workflowLaunch = { source: "script", params: savedWorkflowLaunchParams(publicParams) };
7665
7810
  }
7811
+ if (workflowLaunch && publicParams.action === undefined)
7812
+ publicWorkflowLaunches.set(publicParams, workflowLaunch);
7813
+ if (workflowResumeLaunch)
7814
+ workflowResumeLaunches.set(publicParams, workflowResumeLaunch);
7666
7815
  publicExecutions.add(publicParams);
7667
7816
  return executeWithSingleDispatchGuard(id, publicParams, signal, onUpdate, ctx);
7668
7817
  };
7818
+ const executeWorkflowResume = async (id, prepared, signal, ctx) => {
7819
+ const params = { ...prepared.params };
7820
+ const launch = { presetRunId: prepared.newRunId, resumeOrigin: prepared.resumeOrigin, expectedScriptDigest: prepared.expectedScriptDigest };
7821
+ workflowResumeLaunches.set(params, launch);
7822
+ let result;
7823
+ try {
7824
+ result = await executePublic(id, params, signal, undefined, ctx);
7825
+ }
7826
+ catch (error) {
7827
+ result = { content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], isError: true, details: { mode: "workflow", results: [] } };
7828
+ }
7829
+ if (resumedRunStarted(prepared.sourceAsyncDir, prepared.newRunId))
7830
+ return { result, launched: true };
7831
+ const firstText = result.content.find((block) => block.type === "text")?.text ?? "no result";
7832
+ const refusal = launch.refusal
7833
+ ? workflowResumeRefusal(launch.refusal.code, launch.refusal.reason, prepared.sourceRunId)
7834
+ : result.details?.activeAsyncCapacity
7835
+ ? workflowResumeRefusal("capacity", `capacity refused: ${firstText}`, prepared.sourceRunId)
7836
+ : workflowResumeRefusal("launch-failed", `launch failed: ${firstText}`, prepared.sourceRunId);
7837
+ releaseWorkflowResumeClaim(prepared.sourceAsyncDir, prepared.newRunId);
7838
+ if (prepared.trigger === "auto") {
7839
+ try {
7840
+ recordWorkflowResumeRefusal(prepared.sourceAsyncDir, { reason: refusal.reason, byHand: refusal.byHand, newRunId: prepared.newRunId });
7841
+ }
7842
+ catch (error) {
7843
+ console.error(`Failed to record why workflow ${prepared.sourceRunId} was not resumed:`, error);
7844
+ }
7845
+ }
7846
+ return { result, launched: false, refusal };
7847
+ };
7848
+ const resumeWorkflowById = async (id, sourceRunId, signal, ctx) => {
7849
+ const refused = (text) => ({ content: [{ type: "text", text }], isError: true, details: { mode: "management", results: [] } });
7850
+ let preparation;
7851
+ try {
7852
+ preparation = prepareWorkflowResume({ asyncDirRoot: DIRS.async, sourceRunId, trigger: "manual", now: Date.now(), currentSessionId: resolveCurrentSessionId(ctx.sessionManager) });
7853
+ }
7854
+ catch (error) {
7855
+ return refused(`Cannot resume workflow '${sourceRunId}': ${error instanceof Error ? error.message : String(error)}`);
7856
+ }
7857
+ if (!preparation.ok)
7858
+ return refused(manualWorkflowResumeRefusalText(sourceRunId, preparation));
7859
+ const outcome = await executeWorkflowResume(id, preparation.prepared, signal, ctx);
7860
+ if (!outcome.launched)
7861
+ return refused(manualWorkflowResumeRefusalText(sourceRunId, outcome.refusal));
7862
+ const resumedLine = `Resumed workflow '${sourceRunId}' as ${preparation.prepared.newRunId}.`;
7863
+ const textIndex = outcome.result.content.findIndex((block) => block.type === "text");
7864
+ const content = [...outcome.result.content];
7865
+ const first = content[textIndex];
7866
+ if (first?.type === "text")
7867
+ content[textIndex] = { ...first, text: `${resumedLine}\n${first.text}` };
7868
+ else
7869
+ content.unshift({ type: "text", text: resumedLine });
7870
+ return { ...outcome.result, content };
7871
+ };
7669
7872
  const executeDelegated = async (id, params, signal, onUpdate, ctx) => {
7670
7873
  const disabledFeatureError = disabledFeatureResult(params);
7671
7874
  if (disabledFeatureError)
@@ -7711,5 +7914,5 @@ export function createSubagentExecutor(deps) {
7711
7914
  for (const owner of scheduledOwnerExecutors.get(ownerId)?.values() ?? [])
7712
7915
  yield owner.state;
7713
7916
  }
7714
- return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled, getCurrentSupervisorOwnerStates };
7917
+ return { execute: executeWithSingleDispatchGuard, executePublic, executeDelegated, executeScheduled, executeWorkflowResume, getCurrentSupervisorOwnerStates };
7715
7918
  }
@@ -1,6 +1,6 @@
1
1
  import type { ChildWatchdogConfig, ChildWatchdogStatusEvent } from "../../watchdog/child-status.ts";
2
2
  import type { ThinkingLevel } from "../../shared/model-info.ts";
3
- import { type LaunchResolvedChildExtensions, type ResolvedToolBudget, type RunFanoutBudgetDescriptor, type HerdrMachineReference } from "../../shared/types.ts";
3
+ import { type LaunchResolvedChildExtensions, type ResolvedToolBudget, type RunFanoutBudgetDescriptor, type PlacementMachineReference } from "../../shared/types.ts";
4
4
  import { type McpRuntimeSnapshotHost, type ResolvedMcpDirectToolSelection } from "./mcp-direct-tool-allowlist.ts";
5
5
  import type { PermissionRules } from "./permissions.ts";
6
6
  import type { StructuredOutputRuntime } from "./structured-output.ts";
@@ -23,7 +23,7 @@ export declare const MCP_DIRECT_TOOLS_ENV = "MCP_DIRECT_TOOLS";
23
23
  export type InheritedChildRuntime = Pick<ChildRuntimeConfig, "depth" | "maxDepth" | "nestedRoute" | "nestedParent" | "capabilityCeiling" | "thinkingCeiling" | "runFanoutBudget" | "requiredExtensions">;
24
24
  export declare function inheritedChildRuntime(config: ChildRuntimeConfig | undefined): InheritedChildRuntime | undefined;
25
25
  export interface BuildInProcessChildLaunchInput {
26
- machine?: HerdrMachineReference;
26
+ machine?: PlacementMachineReference;
27
27
  remoteSkillNames?: string[];
28
28
  remoteReads?: string[] | false;
29
29
  parentSessionId?: string;