@selesai/code 0.8.4 → 0.8.6

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 (147) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +7 -1
  3. package/dist/core/slash-commands.js +1 -0
  4. package/dist/defaults/models.json +46 -55
  5. package/dist/defaults/settings.json +7 -8
  6. package/dist/extensions/agent-browser.test.ts +178 -2
  7. package/dist/extensions/agent-browser.ts +2 -0
  8. package/dist/extensions/copy-turn.test.ts +229 -0
  9. package/dist/extensions/copy-turn.ts +11 -0
  10. package/dist/extensions/grep-app/index.test.ts +356 -1
  11. package/dist/extensions/grep-app/index.ts +6 -1
  12. package/dist/extensions/handoff-new.test.ts +351 -161
  13. package/dist/extensions/handoff-new.ts +3 -0
  14. package/dist/extensions/inline-skills.test.ts +93 -0
  15. package/dist/extensions/inline-skills.ts +3 -0
  16. package/dist/extensions/pi-subagents/CHANGELOG.md +26 -3
  17. package/dist/extensions/pi-subagents/README.md +2 -0
  18. package/dist/extensions/pi-subagents/agents/builder.md +5 -2
  19. package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
  20. package/dist/extensions/pi-subagents/docs/configuration.md +54 -2
  21. package/dist/extensions/pi-subagents/docs/observability.md +1 -1
  22. package/dist/extensions/pi-subagents/docs/tool-reference.md +1 -1
  23. package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
  24. package/dist/extensions/pi-subagents/package-lock.json +2 -2
  25. package/dist/extensions/pi-subagents/package.json +1 -1
  26. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +1 -1
  27. package/dist/extensions/pi-subagents/src/agents/agents.ts +9 -3
  28. package/dist/extensions/pi-subagents/src/extension/config.ts +12 -0
  29. package/dist/extensions/pi-subagents/src/extension/doctor.ts +40 -0
  30. package/dist/extensions/pi-subagents/src/extension/index.ts +104 -14
  31. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +5 -0
  32. package/dist/extensions/pi-subagents/src/extension/rpc.ts +3 -9
  33. package/dist/extensions/pi-subagents/src/extension/schemas.ts +2 -2
  34. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +4 -1
  35. package/dist/extensions/pi-subagents/src/missions/lifecycle.ts +4 -7
  36. package/dist/extensions/pi-subagents/src/missions/store.ts +4 -4
  37. package/dist/extensions/pi-subagents/src/missions/workflow-state.ts +6 -2
  38. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +374 -0
  39. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +9 -5
  40. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +117 -29
  41. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +12 -6
  42. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +7 -1
  43. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +14 -5
  44. package/dist/extensions/pi-subagents/src/runs/background/chain-append.ts +33 -15
  45. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
  46. package/dist/extensions/pi-subagents/src/runs/background/owned-process-tree.ts +104 -0
  47. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +17 -3
  48. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +19 -1
  49. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +2 -2
  50. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +5 -2
  51. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +12 -6
  52. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +3 -3
  53. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +84 -36
  54. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
  55. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +37 -2
  56. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +135 -22
  57. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-control.ts +12 -0
  58. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
  59. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +171 -0
  60. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +653 -196
  61. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +13 -4
  62. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +286 -0
  63. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +23 -0
  64. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  65. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +44 -1
  66. package/dist/extensions/pi-subagents/src/runs/shared/run-fanout-budget.ts +280 -0
  67. package/dist/extensions/pi-subagents/src/runs/shared/single-output.ts +4 -2
  68. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +19 -3
  69. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +17 -5
  70. package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
  71. package/dist/extensions/pi-subagents/src/shared/types.ts +108 -0
  72. package/dist/extensions/pi-subagents/src/shared/utils.ts +3 -1
  73. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +7 -5
  74. package/dist/extensions/pi-subagents/src/tui/fleet.ts +226 -13
  75. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +22 -5
  76. package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
  77. package/dist/extensions/pi-subagents/test/integration/acceptance-file-report.test.ts +87 -0
  78. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +96 -6
  79. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +28 -5
  80. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +8 -5
  81. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +83 -2
  82. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +211 -15
  83. package/dist/extensions/pi-subagents/test/integration/parallel-execution.test.ts +41 -3
  84. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +314 -10
  85. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +31 -1
  86. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +317 -0
  87. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +21 -2
  88. package/dist/extensions/pi-subagents/test/unit/async-recovery-descriptor.test.ts +16 -1
  89. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +6 -1
  90. package/dist/extensions/pi-subagents/test/unit/chain-append.test.ts +75 -4
  91. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +18 -0
  92. package/dist/extensions/pi-subagents/test/unit/doctor.test.ts +29 -1
  93. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +31 -5
  94. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +139 -1
  95. package/dist/extensions/pi-subagents/test/unit/foreground-control.test.ts +10 -0
  96. package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
  97. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +22 -0
  98. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +2 -1
  99. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +171 -0
  100. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +30 -4
  101. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +14 -0
  102. package/dist/extensions/pi-subagents/test/unit/owned-process-tree.test.ts +69 -0
  103. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +83 -0
  104. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +2 -2
  105. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +55 -2
  106. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +12 -1
  107. package/dist/extensions/pi-subagents/test/unit/rpc.test.ts +7 -7
  108. package/dist/extensions/pi-subagents/test/unit/run-fanout-budget.test.ts +121 -0
  109. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +9 -0
  110. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -3
  111. package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
  112. package/dist/extensions/pi-subagents/test/unit/single-output.test.ts +28 -0
  113. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +10 -1
  114. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +39 -0
  115. package/dist/extensions/pi-subagents/test/unit/timeout-defaults.test.ts +65 -0
  116. package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +45 -0
  117. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +61 -0
  118. package/dist/extensions/ponytail/index.js +11 -0
  119. package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
  120. package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
  121. package/dist/extensions/ponytail/test/extension.test.js +274 -140
  122. package/dist/extensions/ponytail/test/helpers.test.js +280 -92
  123. package/dist/extensions/question/index.ts +18 -0
  124. package/dist/extensions/question/question-list.ts +22 -0
  125. package/dist/extensions/question/tests/batch.test.ts +103 -65
  126. package/dist/extensions/question/tests/helpers.test.ts +68 -35
  127. package/dist/extensions/question/tests/question-list.test.ts +506 -204
  128. package/dist/extensions/question/tests/row-layout.test.ts +180 -114
  129. package/dist/extensions/question/tests/schemas.test.ts +42 -19
  130. package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
  131. package/dist/extensions/question/tests/wizard.test.ts +771 -50
  132. package/dist/extensions/rtk.test.ts +180 -1
  133. package/dist/extensions/tokenin-onboarding.ts +3 -0
  134. package/dist/extensions/undo.test.ts +720 -0
  135. package/dist/extensions/undo.ts +6 -0
  136. package/dist/extensions/web-agent-onboarding.test.ts +415 -0
  137. package/dist/extensions/workflow/extension.ts +3 -3
  138. package/dist/extensions/workflow/modes.ts +41 -19
  139. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  140. package/dist/modes/interactive/interactive-mode.js +48 -1
  141. package/dist/skills/pi-subagents/references/execution-controls.md +1 -1
  142. package/docs/plans/workflow-autoloop-reference.md +1 -2
  143. package/docs/plans/workflow-handoff-carryover.md +108 -0
  144. package/docs/quickstart.md +27 -43
  145. package/docs/settings.md +4 -0
  146. package/docs/usage.md +1 -0
  147. package/package.json +5 -1
@@ -25,11 +25,14 @@ import {
25
25
  type LaunchResolvedChildExtensionsV1,
26
26
  type RuntimeAcknowledgedChildExtensionsV1,
27
27
  type ModelAttempt,
28
+ type PiWriterProcessInstanceExitV1,
29
+ type ProcessTreeTerminalV1,
28
30
  type NestedRouteInfo,
29
31
  type NestedRunSummary,
30
32
  type ResolvedControlConfig,
31
33
  type ResolvedTurnBudget,
32
34
  type ResolvedToolBudget,
35
+ type RunFanoutBudgetDescriptor,
33
36
  type SubagentRunMode,
34
37
  type SubagentOutputState,
35
38
  type UsageBudgetConfig,
@@ -68,15 +71,16 @@ import {
68
71
  DEFAULT_GLOBAL_CONCURRENCY_LIMIT,
69
72
  Semaphore,
70
73
  } from "../shared/parallel-utils.ts";
71
- import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
74
+ import { applyThinkingSuffix, buildPiArgs, cleanupTempDir, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan, type SubagentTaskDelivery } from "../shared/pi-args.ts";
72
75
  import { readRuntimeAcknowledgedExtensions } from "../shared/runtime-acknowledged-extensions.ts";
73
76
  import { outputEntryFromAsyncResult, resolveOutputReferences } from "../shared/chain-outputs.ts";
74
77
  import { createStructuredOutputRuntime, MISSING_STRUCTURED_OUTPUT_CALL_ERROR, readStructuredOutput } from "../shared/structured-output.ts";
75
78
  import { formatProcessSignalError, isUnexplainedProcessSignal } from "../shared/process-signal.ts";
76
79
  import { readChildToolDiagnosticError } from "../shared/tool-availability.ts";
77
80
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection } from "../shared/dynamic-fanout.ts";
81
+ import { claimRunFanoutBatch, getRunFanoutBudgetSnapshot } from "../shared/run-fanout-budget.ts";
78
82
  import { nestedSummaryFromAsyncStatus, projectNestedEvents, resolveNestedAsyncDir, writeNestedEvent } from "../shared/nested-events.ts";
79
- import { formatModelAttemptNote, isRetryableModelFailure } from "../shared/model-fallback.ts";
83
+ import { formatModelAttemptNote, formatModelRetryNote, isRetryableModelFailure, MODEL_RETRY_DELAYS_MS } from "../shared/model-fallback.ts";
80
84
  import {
81
85
  SUBAGENT_STARTUP_RETRY_DELAYS_MS,
82
86
  formatSubagentStartupRetryExhaustedError,
@@ -85,9 +89,10 @@ import {
85
89
  waitForSubagentStartupRetry,
86
90
  } from "../shared/subagent-startup-retry.ts";
87
91
  import { markProcessTerminalCandidateLeaseRelease, writeProcessTerminalCandidate, type ProcessTerminalCandidate } from "./process-terminal.ts";
92
+ import { createOwnedProcessTreeController, type OwnedProcessTreeController } from "./owned-process-tree.ts";
88
93
  import { createSteeringStatus, recordSteeringRequest, steeringStatus, terminalSteeringNoticeState, updateSteeringTarget } from "./steering.ts";
89
94
  import { attachPostExitStdioGuard, trySignalChild } from "../../shared/post-exit-stdio-guard.ts";
90
- import { detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
95
+ import { PROMPT_REDACTED, detectSubagentError, extractTextFromContent, extractToolArgsPreview, getFinalOutput, hasEmptyTerminalAssistantResponse, readStatus } from "../../shared/utils.ts";
91
96
  import { evaluateCompletionMutationGuard } from "../shared/completion-guard.ts";
92
97
  import {
93
98
  createMutatingFailureState,
@@ -177,6 +182,7 @@ interface SubagentRunConfig {
177
182
  /** Global cap on simultaneously-running subagent tasks within this run. */
178
183
  globalConcurrencyLimit?: number;
179
184
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
185
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
180
186
  launchContractDigest?: string;
181
187
  launchResolvedExtensions?: LaunchResolvedChildExtensionsV1;
182
188
  runtimeAcknowledgedExtensions?: RuntimeAcknowledgedChildExtensionsV1;
@@ -232,7 +238,7 @@ interface StepResult {
232
238
  structuredOutputSchemaPath?: string;
233
239
  acceptance?: import("../../shared/types.ts").AcceptanceLedger;
234
240
  watchdog?: import("../../shared/types.ts").ChildWatchdogProgress;
235
- writerProcesses?: Array<{ processInstanceId: string; kind: "pi-writer"; attempt: number; closeObservedAt: number; exitCode: number | null; signal: string | null }>;
241
+ writerProcesses?: PiWriterProcessInstanceExitV1[];
236
242
  writerAttemptCount?: number;
237
243
  runner?: ExternalCliRunnerStatus;
238
244
  externalProcess?: ExternalProcessStatus;
@@ -512,6 +518,7 @@ interface RunPiStreamingResult {
512
518
  processInstanceId: string;
513
519
  processCloseObservedAt?: number;
514
520
  processSignal?: string | null;
521
+ processTree: ProcessTreeTerminalV1;
515
522
  }
516
523
 
517
524
  function runPiStreaming(
@@ -548,7 +555,9 @@ function runPiStreaming(
548
555
  stdio: ["ignore", "pipe", "pipe"],
549
556
  env: spawnEnv,
550
557
  windowsHide: true,
558
+ detached: process.platform !== "win32",
551
559
  });
560
+ let processTreeController: OwnedProcessTreeController | undefined;
552
561
  const stderrTail = createBoundedByteTail();
553
562
  const rawStdoutTail = createBoundedByteTail();
554
563
  const messages: Message[] = [];
@@ -556,6 +565,7 @@ function runPiStreaming(
556
565
  let model: string | undefined;
557
566
  let writerRegistrationError: string | undefined;
558
567
  if (typeof child.pid === "number") {
568
+ processTreeController = createOwnedProcessTreeController(child.pid);
559
569
  try {
560
570
  onWriterProcess?.({ state: "running", pid: child.pid });
561
571
  } catch (writerError) {
@@ -703,7 +713,6 @@ function runPiStreaming(
703
713
  // a lingering stdio holder after `exit`, or a child that never exits.
704
714
  const FINAL_STOP_GRACE_MS = 1000;
705
715
  const HARD_KILL_MS = 3000;
706
- const TIMEOUT_HARD_KILL_MS = 3000;
707
716
  let childExited = false;
708
717
  let forcedTerminationSignal = false;
709
718
  let cleanTerminalAssistantStopReceived = false;
@@ -711,7 +720,6 @@ function runPiStreaming(
711
720
  let finalDrainTimer: NodeJS.Timeout | undefined;
712
721
  let finalHardKillTimer: NodeJS.Timeout | undefined;
713
722
  let watchdogTailTimer: NodeJS.Timeout | undefined;
714
- let timeoutHardKillTimer: NodeJS.Timeout | undefined;
715
723
  let turnBudgetTerminationTimer: NodeJS.Timeout | undefined;
716
724
  let turnBudgetHardKillTimer: NodeJS.Timeout | undefined;
717
725
  let protocolHardKillTimer: NodeJS.Timeout | undefined;
@@ -776,22 +784,16 @@ function runPiStreaming(
776
784
  timedOut = true;
777
785
  interrupted = false;
778
786
  error = timeoutMessage ?? "Subagent timed out.";
779
- trySignalChild(child, "SIGTERM");
780
- timeoutHardKillTimer = setTimeout(() => {
781
- if (!settled) trySignalChild(child, "SIGKILL");
782
- }, TIMEOUT_HARD_KILL_MS);
783
- timeoutHardKillTimer.unref?.();
787
+ if (processTreeController) void processTreeController.terminate();
788
+ else trySignalChild(child, "SIGTERM");
784
789
  });
785
790
  registerStop?.(() => {
786
791
  if (settled || timedOut || stopped) return;
787
792
  stopped = true;
788
793
  interrupted = false;
789
794
  error = stopMessage ?? "Subagent stopped by user.";
790
- trySignalChild(child, "SIGTERM");
791
- timeoutHardKillTimer = setTimeout(() => {
792
- if (!settled) trySignalChild(child, "SIGKILL");
793
- }, TIMEOUT_HARD_KILL_MS);
794
- timeoutHardKillTimer.unref?.();
795
+ if (processTreeController) void processTreeController.terminate();
796
+ else trySignalChild(child, "SIGTERM");
795
797
  });
796
798
  registerTurnBudgetAbort?.((message, state) => {
797
799
  if (settled || timedOut || stopped || turnBudgetExceeded) return;
@@ -820,10 +822,6 @@ function runPiStreaming(
820
822
  finalHardKillTimer = undefined;
821
823
  }
822
824
  clearWatchdogTailTimer();
823
- if (timeoutHardKillTimer) {
824
- clearTimeout(timeoutHardKillTimer);
825
- timeoutHardKillTimer = undefined;
826
- }
827
825
  if (turnBudgetTerminationTimer) {
828
826
  clearTimeout(turnBudgetTerminationTimer);
829
827
  turnBudgetTerminationTimer = undefined;
@@ -885,9 +883,12 @@ function runPiStreaming(
885
883
  childExited = true;
886
884
  clearDrainTimers();
887
885
  });
888
- child.on("close", (exitCode, signal) => {
886
+ child.on("close", async (exitCode, signal) => {
889
887
  settled = true;
890
888
  const processCloseObservedAt = Date.now();
889
+ const processTree = processTreeController
890
+ ? await processTreeController.finishAfterWriterClose()
891
+ : { state: "unknown" as const, reason: "verification-failed" as const, diagnostic: "Writer PID was unavailable." };
891
892
  try {
892
893
  onWriterProcess?.({ state: "none" });
893
894
  } catch {
@@ -939,6 +940,7 @@ function runPiStreaming(
939
940
  processInstanceId,
940
941
  processCloseObservedAt,
941
942
  processSignal: signal,
943
+ processTree,
942
944
  }));
943
945
  });
944
946
 
@@ -961,7 +963,7 @@ function runPiStreaming(
961
963
  const stderr = stderrTail.text();
962
964
  const finalOutput = getFinalOutput(messages) || rawStdoutTail.text().trim();
963
965
  const spawnErrorMessage = spawnError instanceof Error ? spawnError.message : String(spawnError);
964
- resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId }));
966
+ resolve(omitUndefinedProperties({ stderr, exitCode: 1, messages, usage, toolCount, durationMs: Date.now() - startedAt, model, error: stopped ? (stopMessage ?? "Subagent stopped by user.") : timedOut ? (timeoutMessage ?? "Subagent timed out.") : turnBudgetExceeded ? turnBudgetMessage : error ?? assistantError ?? spawnErrorMessage, protocolError, finalOutput: (timedOut || stopped) && !finalOutput.trim() ? (stopped ? stopMessage ?? "Subagent stopped by user." : timeoutMessage ?? "Subagent timed out.") : finalOutput, outputState: finalOutput.trim() ? "present" : "absent", timedOut, stopped, turnBudget, turnBudgetExceeded, wrapUpRequested: turnBudget?.outcome === "wrap-up-requested" || turnBudget?.outcome === "termination-deferred" || turnBudgetExceeded || undefined, observedMutationAttempt, structuredOutputToolInvoked, structuredOutputMessageStartIndex, watchdog: childWatchdogState, processInstanceId, processTree: { state: "unknown", reason: "verification-failed", diagnostic: spawnErrorMessage } }));
965
967
  });
966
968
  });
967
969
  }
@@ -1105,6 +1107,7 @@ interface SingleStepContext {
1105
1107
  orchestratorIntercomTarget?: string;
1106
1108
  nestedRoute?: NestedRouteInfo;
1107
1109
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
1110
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
1108
1111
  onAttemptStart?: (attempt: { model?: string; thinking?: string }) => void;
1109
1112
  onChildEvent?: (event: ChildEvent) => void;
1110
1113
  onWriterProcess?: (writer: { state: "none" | "spawning" } | { state: "running"; pid: number }) => void;
@@ -1206,7 +1209,7 @@ async function runSingleStep(
1206
1209
  artifactPaths = getArtifactPaths(ctx.artifactsDir, ctx.id, step.agent, index);
1207
1210
  fs.mkdirSync(ctx.artifactsDir, { recursive: true });
1208
1211
  if (ctx.artifactConfig?.includeInput !== false) {
1209
- fs.writeFileSync(artifactPaths.inputPath, `# Task for ${step.agent}\n\n${task}`, "utf-8");
1212
+ fs.writeFileSync(artifactPaths.inputPath, `# Task for ${step.agent}\n\n${PROMPT_REDACTED}; live Prompt Audit only.\n`, "utf-8");
1210
1213
  }
1211
1214
  if (ctx.artifactConfig?.includeTranscript !== false) {
1212
1215
  transcriptWriter = createChildTranscriptWriter({
@@ -1219,7 +1222,7 @@ async function runSingleStep(
1219
1222
  });
1220
1223
  }
1221
1224
  }
1222
- transcriptWriter?.writeInitialUserMessage(task);
1225
+ transcriptWriter?.writeInitialUserMessage(`${PROMPT_REDACTED}; live Prompt Audit only.`);
1223
1226
 
1224
1227
  if (step.runner?.type === "external-cli") {
1225
1228
  const runner: ExternalCliRunnerStatus = {
@@ -1262,7 +1265,7 @@ async function runSingleStep(
1262
1265
  artifactPaths,
1263
1266
  artifactConfig: ctx.artifactConfig,
1264
1267
  output: formatOutputArtifactContent(omitUndefinedProperties({ output: resolvedOutput.fullOutput, error: external.error, metadataPath: ctx.artifactConfig?.includeMetadata === false ? undefined : artifactPaths.metadataPath })),
1265
- metadata: { runId: ctx.id, agent: step.agent, task, runner, externalProcess: external.externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
1268
+ metadata: { runId: ctx.id, agent: step.agent, task: PROMPT_REDACTED, runner, externalProcess: external.externalProcess, exitCode: external.exitCode, error: external.error, timestamp: Date.now() },
1266
1269
  })
1267
1270
  : {};
1268
1271
  return omitUndefinedProperties({
@@ -1292,7 +1295,7 @@ async function runSingleStep(
1292
1295
  let capabilityAudit: import("../shared/capability-ceiling.ts").SubagentCapabilityAudit | undefined;
1293
1296
  let launchResolvedExtensions = step.launchResolvedExtensions;
1294
1297
  const modelAttempts: ModelAttempt[] = [];
1295
- const writerProcesses: Array<{ processInstanceId: string; kind: "pi-writer"; attempt: number; closeObservedAt: number; exitCode: number | null; signal: string | null }> = [];
1298
+ const writerProcesses: PiWriterProcessInstanceExitV1[] = [];
1296
1299
  let writerAttemptCount = 0;
1297
1300
  const attemptNotes: string[] = [];
1298
1301
  const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
@@ -1306,6 +1309,10 @@ async function runSingleStep(
1306
1309
 
1307
1310
  let modelIndex = 0;
1308
1311
  let startupAttemptIndex = 0;
1312
+ let modelRetryIndex = 0;
1313
+ // Escalated to "file" after an unexplained zero-activity startup failure so
1314
+ // retries keep the task text out of argv (endpoint pre-exec scans may deny it).
1315
+ let taskDeliveryOverride: SubagentTaskDelivery | undefined;
1309
1316
  modelAttemptsLoop: while (modelIndex < candidates.length) {
1310
1317
  if (ctx.timeoutSignal?.aborted || ctx.stopSignal?.aborted || ctx.skipAcceptance?.()) break;
1311
1318
  const candidate = candidates[modelIndex];
@@ -1331,6 +1338,7 @@ async function runSingleStep(
1331
1338
  parentSessionId: step.parentSessionId,
1332
1339
  baseArgs: ["--mode", "json", "-p"],
1333
1340
  task,
1341
+ taskDelivery: taskDeliveryOverride,
1334
1342
  sessionEnabled,
1335
1343
  sessionDir,
1336
1344
  sessionFile: step.sessionFile,
@@ -1356,6 +1364,10 @@ async function runSingleStep(
1356
1364
  parentControlInbox: ctx.nestedRoute?.controlInbox,
1357
1365
  parentRootRunId: ctx.nestedRoute?.rootRunId,
1358
1366
  parentCapabilityToken: ctx.nestedRoute?.capabilityToken,
1367
+ runFanoutBudget: ctx.runFanoutBudget ? {
1368
+ ...ctx.runFanoutBudget,
1369
+ ...(step.runFanoutPath ? { parentPath: `${ctx.runFanoutBudget.parentPath ? `${ctx.runFanoutBudget.parentPath}/` : ""}${step.runFanoutPath}` } : {}),
1370
+ } : undefined,
1359
1371
  steerInboxDir: ctx.steerInboxDir,
1360
1372
  steerCapabilityPath: ctx.steerCapabilityPath,
1361
1373
  steerAckDir: ctx.steerAckDir,
@@ -1429,6 +1441,7 @@ async function runSingleStep(
1429
1441
  closeObservedAt: run.processCloseObservedAt,
1430
1442
  exitCode: run.exitCode,
1431
1443
  signal: run.processSignal ?? null,
1444
+ processTree: run.processTree,
1432
1445
  });
1433
1446
  }
1434
1447
  if (run.turnBudget) turnBudget = run.turnBudget;
@@ -1583,6 +1596,10 @@ async function runSingleStep(
1583
1596
  });
1584
1597
  const shouldRetry = await waitForSubagentStartupRetry(retryDelayMs, [ctx.timeoutSignal, ctx.stopSignal]);
1585
1598
  if (!shouldRetry || ctx.skipAcceptance?.()) break modelAttemptsLoop;
1599
+ if (!taskDeliveryOverride && run.processSignal === "SIGKILL") {
1600
+ taskDeliveryOverride = "file";
1601
+ attemptNotes.push("[startup-retry] retrying with file task delivery to keep the task text out of the child process argv.");
1602
+ }
1586
1603
  attempt.error = retryNote;
1587
1604
  attemptNotes.push(retryNote);
1588
1605
  startupAttemptIndex += 1;
@@ -1598,10 +1615,32 @@ async function runSingleStep(
1598
1615
  finalResult.finalOutput = startupError;
1599
1616
  break modelAttemptsLoop;
1600
1617
  }
1601
- if (!isRetryableModelFailure(error) || modelIndex === candidates.length - 1) break modelAttemptsLoop;
1602
- attemptNotes.push(formatModelAttemptNote(attempt, candidates[modelIndex + 1]));
1603
- modelIndex += 1;
1604
- startupAttemptIndex = 0;
1618
+ if (isRetryableModelFailure(error)) {
1619
+ if (modelIndex < candidates.length - 1) {
1620
+ attemptNotes.push(formatModelAttemptNote(attempt, candidates[modelIndex + 1]));
1621
+ modelIndex += 1;
1622
+ modelRetryIndex = 0;
1623
+ startupAttemptIndex = 0;
1624
+ continue;
1625
+ }
1626
+ const modelRetryDelayMs = MODEL_RETRY_DELAYS_MS[modelRetryIndex];
1627
+ if (modelRetryDelayMs !== undefined) {
1628
+ const retryNote = formatModelRetryNote({
1629
+ model: attempt.model,
1630
+ attempt: modelRetryIndex + 1,
1631
+ maxAttempts: MODEL_RETRY_DELAYS_MS.length + 1,
1632
+ delayMs: modelRetryDelayMs,
1633
+ });
1634
+ const shouldRetry = await waitForSubagentStartupRetry(modelRetryDelayMs, [ctx.timeoutSignal, ctx.stopSignal]);
1635
+ if (!shouldRetry || ctx.skipAcceptance?.()) break modelAttemptsLoop;
1636
+ attempt.error = retryNote;
1637
+ attemptNotes.push(retryNote);
1638
+ modelRetryIndex += 1;
1639
+ startupAttemptIndex = 0;
1640
+ continue;
1641
+ }
1642
+ }
1643
+ break modelAttemptsLoop;
1605
1644
  }
1606
1645
 
1607
1646
  const rawOutput = finalResult?.finalOutput ?? "";
@@ -1700,7 +1739,7 @@ async function runSingleStep(
1700
1739
  metadata: {
1701
1740
  runId: ctx.id,
1702
1741
  agent: step.agent,
1703
- task,
1742
+ task: PROMPT_REDACTED,
1704
1743
  exitCode: effectiveFinalExitCode,
1705
1744
  model: finalResult?.model,
1706
1745
  attemptedModels: attemptedModels.length > 0 ? attemptedModels : undefined,
@@ -2171,6 +2210,7 @@ async function runSubagent(
2171
2210
  ...(config.launchContractDigest ? { launchContractDigest: config.launchContractDigest } : {}),
2172
2211
  ...(config.launchResolvedExtensions ? { launchResolvedExtensions: config.launchResolvedExtensions } : {}),
2173
2212
  ...(config.capabilityCeiling ? { capabilityCeiling: config.capabilityCeiling } : {}),
2213
+ ...(config.runFanoutBudget ? { runFanoutBudget: getRunFanoutBudgetSnapshot(config.runFanoutBudget) } : {}),
2174
2214
  ...(config.parentWorkflowRunId ? { parentWorkflowRunId: config.parentWorkflowRunId } : {}),
2175
2215
  ...(config.workflowKey ? { workflowKey: config.workflowKey } : {}),
2176
2216
  ...(config.runnerProcessInstanceId ? { processTerminal: { version: 1 as const, state: "pending" as const, runId: id, runnerProcessInstanceId: config.runnerProcessInstanceId } } : {}),
@@ -2260,10 +2300,10 @@ async function runSubagent(
2260
2300
  refreshWorkflowGraph();
2261
2301
  writeAtomicJson(statusPath, statusPayload);
2262
2302
  const activeState = isActiveAsyncState(statusPayload.state);
2263
- if (activeState !== lastIndexedActiveState) {
2303
+ if (activeState && activeState !== lastIndexedActiveState) {
2264
2304
  updateActiveRunIndex(asyncDir, statusPayload.state);
2265
- lastIndexedActiveState = activeState;
2266
2305
  }
2306
+ lastIndexedActiveState = activeState;
2267
2307
  emitNestedSelfEvent(statusPayload.state === "running" || statusPayload.state === "queued" ? "subagent.nested.updated" : "subagent.nested.completed");
2268
2308
  };
2269
2309
  const statusWriteCoalescer = createFileCoalescer(writeStatusPayloadNow, 100);
@@ -3035,7 +3075,6 @@ async function runSubagent(
3035
3075
  if (stopped || timedOut || interrupted || statusPayload.state !== "running") return;
3036
3076
  stopped = true;
3037
3077
  const now = Date.now();
3038
- statusPayload.state = "stopped";
3039
3078
  statusPayload.stopped = true;
3040
3079
  statusPayload.error = stopMessage;
3041
3080
  currentActivityState = undefined;
@@ -3068,7 +3107,6 @@ async function runSubagent(
3068
3107
  timedOut = true;
3069
3108
  const now = Date.now();
3070
3109
  const message = timeoutMessage ?? "Subagent timed out.";
3071
- statusPayload.state = "failed";
3072
3110
  statusPayload.timedOut = true;
3073
3111
  statusPayload.error = message;
3074
3112
  currentActivityState = undefined;
@@ -3258,6 +3296,9 @@ async function runSubagent(
3258
3296
  throw new DynamicFanoutError(`Dynamic chain step ${stepIndex + 1} materialized ${materialized.parallel.length} items that resolve output to the same path: ${step.parallel.outputPath}. Remove the explicit output path or use an inherited relative agent output so each item can be isolated.`);
3259
3297
  }
3260
3298
  if (materialized.collectedOnEmpty) await validateDynamicCollection(step.collect.outputSchema, materialized.collectedOnEmpty);
3299
+ if (!config.runFanoutBudget) throw new Error("Async runner is missing its run fan-out budget identity.");
3300
+ const runFanoutBudget = claimRunFanoutBatch(config.runFanoutBudget, materialized.parallel.map((_, itemIndex) => `chain[${stepIndex}].expand[${itemIndex}]`));
3301
+ statusPayload.runFanoutBudget = runFanoutBudget;
3261
3302
  } catch (error) {
3262
3303
  const now = Date.now();
3263
3304
  const message = error instanceof DynamicFanoutError ? error.message : error instanceof Error ? error.message : String(error);
@@ -3364,6 +3405,7 @@ async function runSubagent(
3364
3405
  const materializedTask = step.parallel.namespaceOutputPath ? injectSingleOutputInstruction(taskText, outputPath, step.parallel) : taskText;
3365
3406
  return omitUndefinedProperties({
3366
3407
  ...step.parallel,
3408
+ runFanoutPath: `chain[${stepIndex}].expand[${itemIndex}]`,
3367
3409
  task: materializedTask,
3368
3410
  effectiveAcceptance: resolveEffectiveAcceptance(omitUndefinedProperties({
3369
3411
  explicit: step.parallel.acceptanceInput,
@@ -3523,6 +3565,7 @@ async function runSubagent(
3523
3565
  orchestratorIntercomTarget: config.controlIntercomTarget,
3524
3566
  nestedRoute: config.nestedRoute,
3525
3567
  capabilityCeiling: config.capabilityCeiling,
3568
+ runFanoutBudget: config.runFanoutBudget,
3526
3569
  registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
3527
3570
  registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
3528
3571
  registerStop: (stop) => registerStepStop(fi, stop),
@@ -3905,6 +3948,7 @@ async function runSubagent(
3905
3948
  orchestratorIntercomTarget: config.controlIntercomTarget,
3906
3949
  nestedRoute: config.nestedRoute,
3907
3950
  capabilityCeiling: config.capabilityCeiling,
3951
+ runFanoutBudget: config.runFanoutBudget,
3908
3952
  registerInterrupt: (interrupt) => registerStepInterrupt(fi, interrupt),
3909
3953
  registerTimeout: (interrupt) => registerStepTimeout(fi, interrupt),
3910
3954
  registerStop: (stop) => registerStepStop(fi, stop),
@@ -4194,6 +4238,7 @@ async function runSubagent(
4194
4238
  orchestratorIntercomTarget: config.controlIntercomTarget,
4195
4239
  nestedRoute: config.nestedRoute,
4196
4240
  capabilityCeiling: config.capabilityCeiling,
4241
+ runFanoutBudget: config.runFanoutBudget,
4197
4242
  registerInterrupt: (interrupt) => registerStepInterrupt(flatIndex, interrupt),
4198
4243
  registerTimeout: (interrupt) => registerStepTimeout(flatIndex, interrupt),
4199
4244
  registerStop: (stop) => registerStepStop(flatIndex, stop),
@@ -4438,6 +4483,9 @@ async function runSubagent(
4438
4483
  clearTimeout(timeoutTimer);
4439
4484
  timeoutTimer = undefined;
4440
4485
  }
4486
+ if (!timedOut && !stopped && !interrupted && config.timeoutMs !== undefined && results.some((result) => result.timedOut === true && result.error === timeoutMessage)) {
4487
+ timedOut = true;
4488
+ }
4441
4489
  const signalTerminated = !stopped && !timedOut && !turnBudgetExceeded && !interrupted && results.some((result) => result.exitCode !== 0 && isUnexplainedProcessSignal(omitUndefinedProperties({
4442
4490
  processSignal: result.processSignal,
4443
4491
  interrupted: result.interrupted,
@@ -4630,7 +4678,7 @@ async function runSubagent(
4630
4678
  shareError,
4631
4679
  }));
4632
4680
  if (config.runnerProcessInstanceId) {
4633
- const writers: Record<string, Array<{ processInstanceId: string; kind: "pi-writer"; attempt: number; closeObservedAt: number; exitCode: number | null; signal: string | null }>> = {};
4681
+ const writers: Record<string, PiWriterProcessInstanceExitV1[]> = {};
4634
4682
  const expectedWriters: Record<string, number> = {};
4635
4683
  for (const [index, result] of results.entries()) {
4636
4684
  writers[String(index)] = result.writerProcesses ?? [];
@@ -245,7 +245,8 @@ export function createWaitSubscriptionManager(
245
245
  try {
246
246
  const record = parseRecord(JSON.parse(fs.readFileSync(path.join(subscriptionsDir, file), "utf-8")));
247
247
  if (!record || file !== `${record.token}.json`) continue;
248
- if (record.sessionId === state.currentSessionId) {
248
+ const acceptedSessions = new Set(state.sessionLineage?.length ? state.sessionLineage : [state.currentSessionId]);
249
+ if (acceptedSessions.has(record.sessionId)) {
249
250
  subscriptions.set(record.token, record);
250
251
  if (record.targetKind === "foreground" && !state.foregroundRuns?.has(record.runId)) unresolvedRestoredForegroundTokens.add(record.token);
251
252
  }
@@ -34,6 +34,7 @@ import {
34
34
  import { discoverAvailableSkills, normalizeSkillInput } from "../../agents/skills.ts";
35
35
  import { INTERCOM_BRIDGE_MARKER } from "../../intercom/intercom-bridge.ts";
36
36
  import { runSync } from "./execution.ts";
37
+ import { createTaskMutationArbiter } from "../shared/llm-intent-arbiter.ts";
37
38
  import { workflowForegroundSteeringLaunchOptions } from "./workflow-foreground-steering.ts";
38
39
  import {
39
40
  beginForegroundChild,
@@ -42,6 +43,7 @@ import {
42
43
  settleForegroundSchedulingOwner,
43
44
  updateForegroundChild,
44
45
  } from "./foreground-control.ts";
46
+ import { updateLiveEffectivePrompt } from "./prompt-audit.ts";
45
47
  import { buildChainSummary } from "../../shared/formatters.ts";
46
48
  import { compactForegroundDetails, getSingleResultOutput, mapConcurrent, resolveChildCwd, sumResultsCost, sumResultsUsage } from "../../shared/utils.ts";
47
49
  import { DEFAULT_GLOBAL_CONCURRENCY_LIMIT, Semaphore } from "../shared/parallel-utils.ts";
@@ -71,6 +73,7 @@ import {
71
73
  type ResolvedControlConfig,
72
74
  type ResolvedTurnBudget,
73
75
  type ResolvedToolBudget,
76
+ type RunFanoutBudgetDescriptor,
74
77
  type SingleResult,
75
78
  type ToolBudgetConfig,
76
79
  type ChainCheckpointState,
@@ -86,6 +89,7 @@ import { ChainOutputValidationError, outputEntryFromResult, resolveOutputReferen
86
89
  import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
87
90
  import { collectDynamicResults, DynamicFanoutError, materializeDynamicParallelStep, validateDynamicCollection, type DynamicCollectedResult } from "../shared/dynamic-fanout.ts";
88
91
  import { acceptanceFailureMessage, aggregateAcceptanceReport, evaluateAcceptance, resolveEffectiveAcceptance } from "../shared/acceptance.ts";
92
+ import { claimRunFanoutBatch, RunFanoutLimitError } from "../shared/run-fanout-budget.ts";
89
93
  import { isAgentContractV1 } from "../shared/agent-contract.ts";
90
94
  import type { ChainOutputMap } from "../../shared/types.ts";
91
95
  import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
@@ -167,6 +171,7 @@ interface ParallelChainRunInput {
167
171
  configToolBudget?: ToolBudgetConfig;
168
172
  globalSemaphore?: Semaphore;
169
173
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
174
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
170
175
  permissions?: PermissionConfig;
171
176
  dynamic?: boolean;
172
177
  }
@@ -368,7 +373,11 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
368
373
  beginForegroundChild(input.foregroundControl, {
369
374
  index: childIndex,
370
375
  agent: task.agent,
371
- description: cleanTask.trim(),
376
+ authoredTask: cleanTask,
377
+ effectivePrompt: taskStr,
378
+ cwd: taskCwd,
379
+ ...(outputPath ? { outputPath } : {}),
380
+ description: `${task.agent} child`,
372
381
  ...(effectiveModel ? { model: effectiveModel } : {}),
373
382
  ...(thinking ? { thinking } : {}),
374
383
  interrupt: () => {
@@ -388,8 +397,10 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
388
397
  result = await runSync(input.ctx.cwd, input.agents, task.agent, taskStr, {
389
398
  permissions: input.permissions,
390
399
  parentSessionId: input.ctx.sessionManager.getSessionId() ?? undefined,
400
+ llmIntentArbiter: createTaskMutationArbiter(input.ctx),
391
401
  ...workflowForegroundSteeringLaunchOptions(input.foregroundControl, childIndex),
392
402
  capabilityCeiling: input.capabilityCeiling,
403
+ runFanoutBudget: input.runFanoutBudget ? { ...input.runFanoutBudget, parentPath: `${input.runFanoutBudget.parentPath ? `${input.runFanoutBudget.parentPath}/` : ""}chain[${input.stepIndex}]${input.dynamic ? `.expand[${taskIndex}]` : `.parallel[${taskIndex}]`}` } : undefined,
393
404
  context: input.contextForAgent?.(task.agent),
394
405
  cwd: taskCwd,
395
406
  signal: input.signal,
@@ -422,6 +433,7 @@ async function runParallelChainTasks(input: ParallelChainRunInput): Promise<Sing
422
433
  agentContract,
423
434
  acceptance: task.acceptance,
424
435
  acceptanceContext: { mode: "chain", dynamic: input.dynamic && task.acceptance === undefined },
436
+ onEffectivePrompt: input.foregroundControl ? (prompt) => updateLiveEffectivePrompt(input.foregroundControl!, childIndex, prompt) : undefined,
425
437
  timeoutMs: input.timeoutMs,
426
438
  deadlineAt: input.deadlineAt,
427
439
  turnBudget: input.turnBudget,
@@ -540,6 +552,7 @@ interface ChainExecutionParams {
540
552
  /** Global cap on simultaneously-running tasks within this chain. Defaults to DEFAULT_GLOBAL_CONCURRENCY_LIMIT. */
541
553
  globalConcurrencyLimit?: number;
542
554
  capabilityCeiling?: ResolvedSubagentCapabilityCeiling;
555
+ runFanoutBudget?: RunFanoutBudgetDescriptor;
543
556
  }
544
557
 
545
558
  interface ChainExecutionResult {
@@ -876,6 +889,7 @@ ${step.message}` : ""}` }],
876
889
  configToolBudget: params.configToolBudget,
877
890
  globalSemaphore,
878
891
  permissions: params.permissions,
892
+ runFanoutBudget: params.runFanoutBudget,
879
893
  });
880
894
  globalTaskIndex += step.parallel.length;
881
895
 
@@ -1002,6 +1016,19 @@ ${step.message}` : ""}` }],
1002
1016
  return buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
1003
1017
  }
1004
1018
 
1019
+ try {
1020
+ if (params.runFanoutBudget) claimRunFanoutBatch(params.runFanoutBudget, materialized.parallel.map((_, itemIndex) => `chain[${stepIndex}].expand[${itemIndex}]`));
1021
+ } catch (error) {
1022
+ const message = error instanceof Error ? error.message : String(error);
1023
+ dynamicGroupStatuses[stepIndex] = { status: "failed", error: message };
1024
+ const result = buildChainExecutionErrorResult(message, makeDetailsInput({ currentStepIndex: stepIndex, currentFlatIndex: globalTaskIndex }));
1025
+ if (error instanceof RunFanoutLimitError) {
1026
+ result.details.runFanoutBudget = error.snapshot;
1027
+ result.details.runFanoutRejection = error.rejection;
1028
+ }
1029
+ return result;
1030
+ }
1031
+
1005
1032
  dynamicChildren[stepIndex] = materialized.items.map((item, itemIndex) => ({
1006
1033
  agent: step.parallel.agent,
1007
1034
  label: materialized.parallel[itemIndex]?.label,
@@ -1137,6 +1164,7 @@ ${step.message}` : ""}` }],
1137
1164
  configToolBudget: params.configToolBudget,
1138
1165
  globalSemaphore,
1139
1166
  permissions: params.permissions,
1167
+ runFanoutBudget: params.runFanoutBudget,
1140
1168
  dynamic: true,
1141
1169
  });
1142
1170
  globalTaskIndex = dynamicStartIndex + reservedDynamicItems;
@@ -1332,7 +1360,11 @@ ${step.message}` : ""}` }],
1332
1360
  beginForegroundChild(foregroundControl, {
1333
1361
  index: childIndex,
1334
1362
  agent: seqStep.agent,
1335
- description: cleanTask.trim(),
1363
+ authoredTask: cleanTask,
1364
+ effectivePrompt: stepTask,
1365
+ cwd,
1366
+ ...(outputPath ? { outputPath } : {}),
1367
+ description: `${seqStep.agent} child`,
1336
1368
  ...(effectiveModel ? { model: effectiveModel } : {}),
1337
1369
  ...(thinking ? { thinking } : {}),
1338
1370
  interrupt: () => {
@@ -1369,8 +1401,10 @@ ${step.message}` : ""}` }],
1369
1401
  r = await runSync(ctx.cwd, agents, seqStep.agent, stepTask, {
1370
1402
  permissions: params.permissions,
1371
1403
  parentSessionId: ctx.sessionManager.getSessionId() ?? undefined,
1404
+ llmIntentArbiter: createTaskMutationArbiter(ctx),
1372
1405
  ...workflowForegroundSteeringLaunchOptions(params.foregroundControl, childIndex),
1373
1406
  capabilityCeiling: params.capabilityCeiling,
1407
+ runFanoutBudget: params.runFanoutBudget ? { ...params.runFanoutBudget, parentPath: `${params.runFanoutBudget.parentPath ? `${params.runFanoutBudget.parentPath}/` : ""}chain[${stepIndex}]` } : undefined,
1374
1408
  context: params.contextForAgent?.(seqStep.agent),
1375
1409
  cwd: resolveChildCwd(cwd ?? ctx.cwd, seqStep.cwd),
1376
1410
  signal,
@@ -1403,6 +1437,7 @@ ${step.message}` : ""}` }],
1403
1437
  agentContract,
1404
1438
  acceptance: seqStep.acceptance,
1405
1439
  acceptanceContext: { mode: "chain" },
1440
+ onEffectivePrompt: foregroundControl ? (prompt) => updateLiveEffectivePrompt(foregroundControl, childIndex, prompt) : undefined,
1406
1441
  timeoutMs: params.timeoutMs,
1407
1442
  deadlineAt,
1408
1443
  turnBudget: params.turnBudget,