@selesai/code 0.13.12 → 0.13.14

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 (203) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/extensions/pi-subagents/CHANGELOG.md +77 -1
  3. package/dist/extensions/pi-subagents/VISION.md +12 -1
  4. package/dist/extensions/pi-subagents/docs/agents.md +13 -6
  5. package/dist/extensions/pi-subagents/docs/configuration.md +35 -9
  6. package/dist/extensions/pi-subagents/docs/extension-api.md +1 -1
  7. package/dist/extensions/pi-subagents/docs/missions.md +1 -1
  8. package/dist/extensions/pi-subagents/docs/models.md +6 -6
  9. package/dist/extensions/pi-subagents/docs/observability.md +6 -3
  10. package/dist/extensions/pi-subagents/docs/tool-reference.md +3 -3
  11. package/dist/extensions/pi-subagents/docs/watchdog.md +92 -114
  12. package/dist/extensions/pi-subagents/docs/workflows.md +1 -1
  13. package/dist/extensions/pi-subagents/install.mjs +1 -2
  14. package/dist/extensions/pi-subagents/package.json +1 -1
  15. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +1 -1
  16. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +6 -6
  17. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +2 -2
  18. package/dist/extensions/pi-subagents/skills/pi-subagents/references/multi-lane-orchestration.md +1 -1
  19. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +4 -4
  20. package/dist/extensions/pi-subagents/skills/pi-subagents/references/review-and-validation.md +1 -1
  21. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +41 -4
  22. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +3 -0
  23. package/dist/extensions/pi-subagents/src/agents/agents.ts +120 -124
  24. package/dist/extensions/pi-subagents/src/agents/runtime-agent-registry.ts +5 -1
  25. package/dist/extensions/pi-subagents/src/api/preflight.ts +4 -0
  26. package/dist/extensions/pi-subagents/src/api/shared-types.ts +3 -0
  27. package/dist/extensions/pi-subagents/src/extension/config.ts +20 -0
  28. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +1 -0
  29. package/dist/extensions/pi-subagents/src/extension/schemas.ts +6 -2
  30. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +1 -1
  31. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +19 -13
  32. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +26 -8
  33. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +103 -23
  34. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +6 -2
  35. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +18 -2
  36. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +54 -3
  37. package/dist/extensions/pi-subagents/src/runs/background/process-terminal.ts +16 -0
  38. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +22 -2
  39. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +63 -6
  40. package/dist/extensions/pi-subagents/src/runs/background/steering.ts +4 -1
  41. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +69 -12
  42. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +13 -0
  43. package/dist/extensions/pi-subagents/src/runs/background/wait-tool.ts +3 -1
  44. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +21 -8
  45. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +59 -6
  46. package/dist/extensions/pi-subagents/src/runs/shared/acceptance.ts +95 -18
  47. package/dist/extensions/pi-subagents/src/runs/shared/async-status-projection.ts +11 -43
  48. package/dist/extensions/pi-subagents/src/runs/shared/capability-ceiling.ts +1 -0
  49. package/dist/extensions/pi-subagents/src/runs/shared/dynamic-fanout.ts +1 -1
  50. package/dist/extensions/pi-subagents/src/runs/shared/lane-metadata.ts +24 -3
  51. package/dist/extensions/pi-subagents/src/runs/shared/parallel-handoff.ts +4 -0
  52. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -6
  53. package/dist/extensions/pi-subagents/src/runs/shared/permissions.ts +1 -0
  54. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +33 -18
  55. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +73 -37
  56. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +33 -6
  57. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +4 -2
  58. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +20 -3
  59. package/dist/extensions/pi-subagents/src/runs/shared/task-intent.ts +21 -7
  60. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +1 -1
  61. package/dist/extensions/pi-subagents/src/runs/shared/worktree.ts +467 -63
  62. package/dist/extensions/pi-subagents/src/shared/atomic-json.ts +3 -1
  63. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +0 -12
  64. package/dist/extensions/pi-subagents/src/shared/fork-session-cwd.ts +27 -0
  65. package/dist/extensions/pi-subagents/src/shared/launch-contract.ts +3 -0
  66. package/dist/extensions/pi-subagents/src/shared/types.ts +42 -4
  67. package/dist/extensions/pi-subagents/src/shared/utils.ts +18 -7
  68. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
  69. package/dist/extensions/pi-subagents/src/slash/subagents-admin.ts +26 -12
  70. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +61 -2
  71. package/dist/extensions/pi-subagents/src/tui/fleet.ts +12 -7
  72. package/dist/extensions/pi-subagents/src/tui/render.ts +227 -14
  73. package/dist/extensions/pi-subagents/src/watchdog/child-status.ts +56 -34
  74. package/dist/extensions/pi-subagents/src/watchdog/diff-tool.ts +77 -0
  75. package/dist/extensions/pi-subagents/src/watchdog/emission-guard.ts +5 -3
  76. package/dist/extensions/pi-subagents/src/watchdog/guidance.ts +20 -0
  77. package/dist/extensions/pi-subagents/src/watchdog/register-child.ts +16 -13
  78. package/dist/extensions/pi-subagents/src/watchdog/register-main.ts +10 -9
  79. package/dist/extensions/pi-subagents/src/watchdog/render.ts +4 -5
  80. package/dist/extensions/pi-subagents/src/watchdog/review.ts +15 -4
  81. package/dist/extensions/pi-subagents/src/watchdog/rules.ts +70 -0
  82. package/dist/extensions/pi-subagents/src/watchdog/runtime.ts +75 -92
  83. package/dist/extensions/pi-subagents/src/watchdog/scope.ts +2 -1
  84. package/dist/extensions/pi-subagents/src/watchdog/settings.ts +48 -104
  85. package/dist/extensions/pi-subagents/src/watchdog/types.ts +18 -32
  86. package/dist/extensions/pi-subagents/src/watchdog/warning-format.ts +0 -1
  87. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +3 -2
  88. package/dist/extensions/pi-subagents/src/workflows/workflow-checklist.ts +439 -0
  89. package/dist/extensions/pi-subagents/src/workflows/workflow-preflight.ts +28 -1
  90. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +335 -21
  91. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +4 -1
  92. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +1 -1
  93. package/dist/extensions/pi-subagents/test/integration/fork-context-execution.test.ts +22 -18
  94. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +0 -1
  95. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +21 -18
  96. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +307 -22
  97. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +1 -0
  98. package/dist/extensions/pi-subagents/test/support/helpers.ts +25 -0
  99. package/dist/extensions/pi-subagents/test/support/isolated-temp-root.mjs +15 -4
  100. package/dist/extensions/pi-subagents/test/support/mock-pi-script.mjs +17 -2
  101. package/dist/extensions/pi-subagents/test/unit/acceptance.test.ts +96 -15
  102. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +53 -5
  103. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +20 -0
  104. package/dist/extensions/pi-subagents/test/unit/agent-management.test.ts +221 -8
  105. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +45 -24
  106. package/dist/extensions/pi-subagents/test/unit/agent-scan-dirs.test.ts +133 -0
  107. package/dist/extensions/pi-subagents/test/unit/async-execution.test.ts +28 -2
  108. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +1 -1
  109. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  110. package/dist/extensions/pi-subagents/test/unit/async-retention.test.ts +4 -1
  111. package/dist/extensions/pi-subagents/test/unit/async-status-projection.test.ts +18 -4
  112. package/dist/extensions/pi-subagents/test/unit/atomic-json.test.ts +16 -0
  113. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +38 -0
  114. package/dist/extensions/pi-subagents/test/unit/default-extensions.test.ts +2 -2
  115. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +21 -0
  116. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +19 -6
  117. package/dist/extensions/pi-subagents/test/unit/get-final-output.test.ts +26 -0
  118. package/dist/extensions/pi-subagents/test/unit/herdr-inspector.test.ts +22 -1
  119. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +2 -6
  120. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +66 -1
  121. package/dist/extensions/pi-subagents/test/unit/pi-args-permission-system.test.ts +1 -1
  122. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +116 -10
  123. package/dist/extensions/pi-subagents/test/unit/pi-spawn.test.ts +136 -18
  124. package/dist/extensions/pi-subagents/test/unit/preflight.test.ts +20 -0
  125. package/dist/extensions/pi-subagents/test/unit/process-terminal.test.ts +22 -0
  126. package/dist/extensions/pi-subagents/test/unit/profiles.test.ts +1 -0
  127. package/dist/extensions/pi-subagents/test/unit/project-panes-public-api.test.ts +8 -0
  128. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +127 -48
  129. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +68 -0
  130. package/dist/extensions/pi-subagents/test/unit/runtime-agent-registration.test.ts +17 -0
  131. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +88 -0
  132. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +4 -2
  133. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +9 -9
  134. package/dist/extensions/pi-subagents/test/unit/steering-action.test.ts +2 -2
  135. package/dist/extensions/pi-subagents/test/unit/steering.test.ts +9 -3
  136. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +22 -9
  137. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +91 -8
  138. package/dist/extensions/pi-subagents/test/unit/task-intent.test.ts +27 -0
  139. package/dist/extensions/pi-subagents/test/unit/temp-paths.test.ts +50 -0
  140. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +0 -1
  141. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +2 -2
  142. package/dist/extensions/pi-subagents/test/unit/wait-completions.test.ts +34 -0
  143. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +1 -2
  144. package/dist/extensions/pi-subagents/test/unit/watchdog-child-status.test.ts +73 -13
  145. package/dist/extensions/pi-subagents/test/unit/watchdog-diff-tool.test.ts +79 -0
  146. package/dist/extensions/pi-subagents/test/unit/watchdog-permission-arbiter.test.ts +2 -4
  147. package/dist/extensions/pi-subagents/test/unit/watchdog-render.test.ts +7 -8
  148. package/dist/extensions/pi-subagents/test/unit/watchdog-review.test.ts +48 -3
  149. package/dist/extensions/pi-subagents/test/unit/watchdog-rules.test.ts +37 -0
  150. package/dist/extensions/pi-subagents/test/unit/watchdog-runtime.test.ts +36 -118
  151. package/dist/extensions/pi-subagents/test/unit/watchdog-scope.test.ts +1 -1
  152. package/dist/extensions/pi-subagents/test/unit/watchdog-settings.test.ts +37 -14
  153. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +45 -26
  154. package/dist/extensions/pi-subagents/test/unit/workflow-checklist.test.ts +223 -0
  155. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +50 -1
  156. package/dist/extensions/pi-subagents/test/unit/workflow-preflight.test.ts +22 -0
  157. package/dist/extensions/pi-subagents/test/unit/worktree.test.ts +136 -4
  158. package/dist/extensions/pi-zentui/README.md +19 -5
  159. package/dist/extensions/pi-zentui/docs/configuration.md +37 -9
  160. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +29 -0
  161. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +31 -2
  162. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +55 -11
  163. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +115 -3
  164. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +119 -2
  165. package/dist/extensions/pi-zentui/extensions/zentui/thinking-experimental.ts +1768 -0
  166. package/dist/extensions/pi-zentui/extensions/zentui/thinking-status.ts +69 -0
  167. package/dist/extensions/pi-zentui/extensions/zentui/thinking-steps.ts +155 -0
  168. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +2 -1
  169. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +16 -2
  170. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
  171. package/dist/extensions/pi-zentui/test/config-load-lifecycle.test.ts +124 -0
  172. package/dist/extensions/pi-zentui/test/config.test.ts +82 -2
  173. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +19 -4
  174. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +58 -9
  175. package/dist/extensions/pi-zentui/test/package-contents.mjs +33 -0
  176. package/dist/extensions/pi-zentui/test/prototype-patch-registry.test.ts +131 -0
  177. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +4 -4
  178. package/dist/extensions/pi-zentui/test/settings-command.test.ts +289 -7
  179. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +106 -0
  180. package/dist/extensions/pi-zentui/test/thinking-experimental-docs.test.ts +51 -0
  181. package/dist/extensions/pi-zentui/test/thinking-experimental-loader-diagnostics.mjs +54 -0
  182. package/dist/extensions/pi-zentui/test/thinking-experimental-missing-export.test.ts +44 -0
  183. package/dist/extensions/pi-zentui/test/thinking-experimental-rows.test.ts +297 -0
  184. package/dist/extensions/pi-zentui/test/thinking-experimental-tui.mjs +2329 -0
  185. package/dist/extensions/pi-zentui/test/thinking-experimental.test.ts +1668 -0
  186. package/dist/extensions/pi-zentui/test/thinking-status.test.ts +85 -0
  187. package/dist/extensions/pi-zentui/test/thinking-steps.test.ts +122 -0
  188. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +58 -0
  189. package/dist/skills/brandkit/SKILL.md +0 -2
  190. package/dist/skills/{industrial-brutalist-ui → brutalist}/SKILL.md +1 -3
  191. package/dist/skills/gpt-taste/SKILL.md +0 -2
  192. package/dist/skills/image-to-code/SKILL.md +0 -2
  193. package/dist/skills/imagegen-frontend-mobile/SKILL.md +0 -2
  194. package/dist/skills/imagegen-frontend-web/SKILL.md +0 -2
  195. package/dist/skills/{minimalist-ui → minimalist}/SKILL.md +1 -3
  196. package/dist/skills/{full-output-enforcement → output}/SKILL.md +1 -3
  197. package/dist/skills/{redesign-existing-projects → redesign}/SKILL.md +1 -3
  198. package/dist/skills/{high-end-visual-design → soft}/SKILL.md +1 -3
  199. package/dist/skills/stitch/DESIGN.md +121 -0
  200. package/dist/skills/{stitch-design-taste → stitch}/SKILL.md +1 -3
  201. package/dist/skills/{design-taste-frontend → taste}/SKILL.md +1 -3
  202. package/dist/skills/{design-taste-frontend-v1 → taste-v1}/SKILL.md +2 -4
  203. package/package.json +1 -1
@@ -41,7 +41,8 @@ export function formatInspectorDashboard(input: { status: AsyncStatus; asyncDir:
41
41
  lines.push("");
42
42
  }
43
43
  lines.push(formatAsyncRunTranscript(status, asyncDir, { index: input.index, lines: 60, sessionRoots: input.sessionRoots }));
44
- const controls = [input.allowSteer === false ? undefined : "steer <message>", input.allowStop === false ? undefined : "stop", "status"].filter(Boolean);
44
+ const acceptsPlainGuidance = input.index !== undefined || status.mode === "single";
45
+ const controls = [input.allowSteer === false || !acceptsPlainGuidance ? undefined : "type guidance", input.allowSteer === false ? undefined : "steer <message>", input.allowStop === false ? undefined : "stop", "status"].filter(Boolean);
45
46
  lines.push("", `Controls: ${controls.join(" | ")}`, "Supervisor replies remain in the parent Pi session (subagent_supervisor/intercom).");
46
47
  return lines.join("\n");
47
48
  }
@@ -81,6 +82,20 @@ function isTerminal(status: AsyncStatus): boolean {
81
82
  return status.state !== "queued" && status.state !== "running";
82
83
  }
83
84
 
85
+ function queueInspectorSteer(options: RunnerOptions, status: AsyncStatus, message: string): string {
86
+ if (options.allowSteer === false) throw new Error("Authority policy does not allow steer from this inspector.");
87
+ if (isTerminal(status)) throw new Error(`Run '${options.runId}' is ${status.state} and cannot be steered.`);
88
+ const runningIndexes = (status.steps ?? []).map((step, index) => step.status === "running" ? index : undefined).filter((index): index is number => index !== undefined);
89
+ const targetIndex = options.index ?? (status.mode === "single" ? 0 : undefined);
90
+ if (targetIndex === undefined && runningIndexes.length === 0) throw new Error("No running child is available to steer. Open a child-specific inspector for a pending child.");
91
+ requestAsyncSteer(options.asyncDir, {
92
+ message,
93
+ ...(targetIndex !== undefined ? { targetIndex } : { targetIndexes: runningIndexes }),
94
+ source: "herdr-inspector",
95
+ });
96
+ return steeringReceipt(message, `Steering queued for run ${options.runId}.`);
97
+ }
98
+
84
99
  export function submitInspectorControl(options: RunnerOptions, line: string): string {
85
100
  const command = line.trim();
86
101
  if (!command || command === "status") return "Status refreshed.";
@@ -93,22 +108,13 @@ export function submitInspectorControl(options: RunnerOptions, line: string): st
93
108
  return `Stop requested for run ${options.runId}.`;
94
109
  }
95
110
  if (command.startsWith("steer ")) {
96
- if (options.allowSteer === false) throw new Error("Authority policy does not allow steer from this inspector.");
97
111
  const message = command.slice("steer ".length).trim();
98
112
  if (!message) throw new Error("steer requires a message.");
99
- if (isTerminal(status)) throw new Error(`Run '${options.runId}' is ${status.state} and cannot be steered.`);
100
- const runningIndexes = (status.steps ?? []).map((step, index) => step.status === "running" ? index : undefined).filter((index): index is number => index !== undefined);
101
- const targetIndex = options.index ?? (status.mode === "single" ? 0 : undefined);
102
- if (targetIndex === undefined && runningIndexes.length === 0) throw new Error("No running child is available to steer. Open a child-specific inspector for a pending child.");
103
- requestAsyncSteer(options.asyncDir, {
104
- message,
105
- ...(targetIndex !== undefined ? { targetIndex } : { targetIndexes: runningIndexes }),
106
- source: "herdr-inspector",
107
- });
108
- return steeringReceipt(message, `Steering queued for run ${options.runId}.`);
113
+ return queueInspectorSteer(options, status, message);
109
114
  }
110
115
  if (command.startsWith("reply ")) throw new Error("Supervisor replies are owned by the parent Pi session; use subagent_supervisor/intercom there.");
111
- throw new Error("Unknown control. Use steer <message>, stop, or status.");
116
+ if (options.index === undefined && status.mode !== "single") throw new Error("Plain guidance requires a child-specific inspector. Use steer <message> to target all running children from the aggregate inspector.");
117
+ return queueInspectorSteer(options, status, command);
112
118
  }
113
119
 
114
120
  export function runInspector(argv = process.argv.slice(2)): void {
@@ -33,6 +33,7 @@ export interface ActiveAsyncCapacityHandle {
33
33
  markStarted(runnerProcessInstanceId: string): void;
34
34
  markWorkflowStarted(): void;
35
35
  rollback(): boolean;
36
+ rollbackBeforeRunnerProceed(runnerProcessInstanceId: string): boolean;
36
37
  reconcile(liveWorkflowRunIds?: ReadonlySet<string>): ActiveAsyncCapacitySnapshot;
37
38
  }
38
39
 
@@ -43,6 +44,7 @@ interface CapacityOptions {
43
44
  abandonedSlotReleaseAfterMs?: number | false;
44
45
  pidLiveness?: (pid: number) => PidLiveness;
45
46
  afterSlotRename?: (releasedDir: string) => void;
47
+ writeOwner?: (filePath: string, owner: ActiveAsyncCapacityOwnerV1) => void;
46
48
  }
47
49
 
48
50
  export interface ActiveAsyncCapacityReleaseEvidence {
@@ -269,7 +271,6 @@ function workflowReleaseVerdict(owner: ActiveAsyncCapacityOwnerV1, status: Async
269
271
  if (liveWorkflowRunIds.has(owner.runId)) return { state: "retained", reason: "workflow controller is still live" };
270
272
  for (const step of status.steps ?? []) {
271
273
  const label = step.workflowKey ?? step.agent;
272
- if (step.status === "pending" || step.status === "running" || step.status === "paused") return { state: "retained", reason: `workflow child ${label} is still ${step.status}` };
273
274
  if (typeof step.async !== "boolean") return { state: "retained", reason: `workflow child ${label} is missing async classification` };
274
275
  if (!step.async) continue;
275
276
  if (!step.runId) return { state: "retained", reason: `async workflow child ${label} is missing run id` };
@@ -380,6 +381,7 @@ function createSlot(poolDir: string, owner: ActiveAsyncCapacityOwnerV1): boolean
380
381
 
381
382
  function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: CapacityOptions, rollbackOwner?: ActiveAsyncCapacityOwnerV1): ActiveAsyncCapacityHandle {
382
383
  const rootDir = options.rootDir ?? ACTIVE_ASYNC_CAPACITY_DIR;
384
+ const writeOwner = options.writeOwner ?? writePrivateAtomicJson;
383
385
  const dir = slotDir(sessionDir(owner.ownerSessionId, rootDir), owner.slot);
384
386
  return {
385
387
  owner,
@@ -388,14 +390,10 @@ function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: Ca
388
390
  const current = matchingOwner(dir, owner);
389
391
  if (!current) return false;
390
392
  const next = { ...current, runnerProcessInstanceId, runnerStartedAt: options.now?.() ?? Date.now() };
391
- // Mark memory first. If persistence fails after the process starts, caller
392
- // cleanup must retain the occupied slot instead of rolling it back.
393
+ // Mark memory first so pre-proceed cleanup can distinguish a failed durable
394
+ // bind from an unrelated unstarted reservation.
393
395
  Object.assign(owner, next);
394
- try {
395
- writePrivateAtomicJson(path.join(dir, "owner.json"), next);
396
- } catch (error) {
397
- console.error(`Failed to bind active async capacity to runner '${runnerProcessInstanceId}'; capacity will remain occupied:`, error);
398
- }
396
+ writeOwner(path.join(dir, "owner.json"), next);
399
397
  return true;
400
398
  });
401
399
  if (!claimed.acquired || !claimed.value) throw new Error(`Active async capacity ownership changed for run '${owner.runId}'.`);
@@ -427,6 +425,26 @@ function handleFor(owner: ActiveAsyncCapacityOwnerV1, limit: number, options: Ca
427
425
  });
428
426
  return claimed.acquired && claimed.value;
429
427
  },
428
+ rollbackBeforeRunnerProceed(runnerProcessInstanceId) {
429
+ const claimed = withSlotClaim(dir, () => {
430
+ const current = matchingOwner(dir, owner);
431
+ if (!current) return false;
432
+ const boundToRunner = current.runnerProcessInstanceId === runnerProcessInstanceId;
433
+ const bindingFailedBeforeProceed = current.runnerProcessInstanceId === undefined && current.runnerStartedAt === undefined && owner.runnerProcessInstanceId === runnerProcessInstanceId;
434
+ if (!boundToRunner && !bindingFailedBeforeProceed) return false;
435
+ if (rollbackOwner) {
436
+ writePrivateAtomicJson(path.join(dir, "owner.json"), rollbackOwner);
437
+ Object.assign(owner, rollbackOwner);
438
+ return true;
439
+ }
440
+ const releasedDir = path.join(path.dirname(dir), `.${path.basename(dir)}.released-${randomUUID()}`);
441
+ fs.renameSync(dir, releasedDir);
442
+ options.afterSlotRename?.(releasedDir);
443
+ fs.rmSync(releasedDir, { recursive: true, force: true });
444
+ return true;
445
+ });
446
+ return claimed.acquired && claimed.value;
447
+ },
430
448
  reconcile(liveWorkflowRunIds) {
431
449
  return reconcileActiveAsyncCapacity(owner.ownerSessionId, limit, { ...options, rootDir, liveWorkflowRunIds });
432
450
  },
@@ -11,15 +11,16 @@ import { createRequire } from "node:module";
11
11
  import type { ExtensionAPI } from "@selesai/code";
12
12
  import { discoverAgents, formatUnknownAgentError, unknownAgentDiagnosticContext, type AgentConfig, type UnknownAgentDiagnosticContext } from "../../agents/agents.ts";
13
13
  import { appendAgentRefinementOverlay } from "../../agents/agent-refinements.ts";
14
- import { writePrivateAtomicJson } from "../../shared/atomic-json.ts";
14
+ import { createAtomicJsonWriter, writePrivateAtomicJson } from "../../shared/atomic-json.ts";
15
15
  import { currentCompletionOwnerId } from "../../shared/completion-owner.ts";
16
16
  import { planChildLaunch, resolveStepBehavior, suppressProgressForReadOnlyTask, type ResolvedStepBehavior } from "../shared/child-launch-plan.ts";
17
17
  import { applyThinkingSuffix, projectLaunchResolvedChildExtensions, resolvePiLaunchToolPlan } from "../shared/pi-args.ts";
18
18
  import { injectOutputPathSystemPrompt, injectSingleOutputInstruction, normalizeSingleOutputOverride, resolveSingleOutputPath, validateFileOnlyOutputMode } from "../shared/single-output.ts";
19
- import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
19
+ import { applyWatchdogLaunchRules, sendRuleViolationWarning } from "../../watchdog/rules.ts";
20
+ import { buildChainInstructions, isDynamicParallelStep, isParallelStep, resolveExistingReadInstructionPaths, resolveExistingReadPaths, writeInitialProgressFile, type ChainStep, type SequentialStep, type StepOverrides } from "../../shared/settings.ts";
20
21
  import type { RunnerStep } from "../shared/parallel-utils.ts";
21
22
  import type { ContextMode } from "../shared/context-mode.ts";
22
- import { resolvePiPackageRoot } from "../shared/pi-spawn.ts";
23
+ import { resolveInstalledPiPackageRoot, resolvePiPackageRoot } from "../shared/pi-spawn.ts";
23
24
  import { backgroundProcessOptions } from "../shared/background-process-options.ts";
24
25
  import { preflightLaunchCwd } from "../shared/launch-cwd.ts";
25
26
  import { resolveNodeExecutable } from "../../shared/node-executable.ts";
@@ -31,11 +32,11 @@ import { resolveToolTimeoutMs, toolTimeoutFromEnv } from "../shared/tool-timeout
31
32
  import { resolveModelScopesForAgent, type ModelScopeConfig } from "../shared/model-scope.ts";
32
33
  import { findModelInfo, resolveEffectiveThinking } from "../../shared/model-info.ts";
33
34
  import { assertThinkingWithinCeiling, decodeThinkingCeiling, intersectThinkingCeilings, SUBAGENT_THINKING_CEILING_ENV, type ThinkingLevel } from "../../shared/thinking-ceiling.ts";
34
- import { resolveExpectedWorktreeAgentCwd } from "../shared/worktree.ts";
35
+ import { resolveExpectedWorktreeAgentCwd, resolveWorktreeProvider, shouldDeferWorktreeCwd, WORKTREE_AGENT_CWD_PLACEHOLDER } from "../shared/worktree.ts";
35
36
  import { buildWorkflowGraphSnapshot } from "../shared/workflow-graph.ts";
36
37
  import { ChainOutputValidationError, validateChainOutputBindings } from "../shared/chain-outputs.ts";
37
38
  import { createStructuredOutputRuntime } from "../shared/structured-output.ts";
38
- import { resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
39
+ import { resolveAcceptanceReportMode, resolveEffectiveAcceptance, validateAcceptanceInput, validateExecutionAcceptance } from "../shared/acceptance.ts";
39
40
  import { createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../shared/run-fanout-budget.ts";
40
41
  import { validateImplementationToolContract } from "../shared/completion-guard.ts";
41
42
  import {
@@ -70,7 +71,7 @@ import { validateToolBudgetConfig } from "../shared/tool-budget.ts";
70
71
  import { usageBudgetState } from "../shared/usage-budget.ts";
71
72
  import type { ImportedAsyncRoot } from "./chain-root-attachment.ts";
72
73
  import type { SessionLeaseRequest } from "../shared/session-lease.ts";
73
- import { finalizeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
74
+ import { finalizeProcessTerminal, initializeProcessTerminal, readProcessTerminal } from "./process-terminal.ts";
74
75
  import type { ActiveAsyncCapacityHandle } from "./active-async-capacity.ts";
75
76
  import { statusStepDescription } from "./chain-append.ts";
76
77
  import { SUBAGENT_PROCESS_TERMINAL_EVENT } from "../../shared/types.ts";
@@ -81,7 +82,7 @@ import { normalizeExtensionBindings, omitExtensionBindingsEnv, type ExtensionBin
81
82
  import { assertWorkflowLaneKey, normalizeWorkflowLaneMetadata } from "../shared/lane-metadata.ts";
82
83
 
83
84
  const require = createRequire(import.meta.url);
84
- const piPackageRoot = resolvePiPackageRoot();
85
+ const piPackageRoot = resolvePiPackageRoot() ?? resolveInstalledPiPackageRoot();
85
86
 
86
87
  function resolveJitiCliFromPackageJson(packageJsonPath: string): string | undefined {
87
88
  if (!fs.existsSync(packageJsonPath)) return undefined;
@@ -178,6 +179,8 @@ interface AsyncChainParams {
178
179
  worktreeSetupHook?: string;
179
180
  worktreeSetupHookTimeoutMs?: number;
180
181
  worktreeBaseDir?: string;
182
+ worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
183
+ worktreeBranchPrefix?: string;
181
184
  controlConfig?: ResolvedControlConfig;
182
185
  controlIntercomTarget?: string;
183
186
  childIntercomTarget?: (agent: string, index: number) => string | undefined;
@@ -245,6 +248,8 @@ interface AsyncSingleParams {
245
248
  worktreeSetupHook?: string;
246
249
  worktreeSetupHookTimeoutMs?: number;
247
250
  worktreeBaseDir?: string;
251
+ worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
252
+ worktreeBranchPrefix?: string;
248
253
  worktree?: boolean;
249
254
  controlConfig?: ResolvedControlConfig;
250
255
  intercomBridge?: IntercomBridgeConfig;
@@ -310,6 +315,8 @@ export interface AsyncRunnerStepBuildParams {
310
315
  waitToolEnabled?: boolean;
311
316
  waitToolDefaultTimeoutMs?: number;
312
317
  worktreeBaseDir?: string;
318
+ worktreeProvider?: import("../../shared/types.ts").WorktreeProvider;
319
+ worktreeBranchPrefix?: string;
313
320
  asyncDir: string;
314
321
  outputBaseDir?: string;
315
322
  validateOutputBindings?: boolean;
@@ -432,13 +439,15 @@ function waitForRunnerStartup(startupPath: string, expectedState: RunnerStartupS
432
439
  return { ok: false, error: `Timed out after ${timeoutMs}ms waiting for the async runner startup state '${expectedState}'.`, startupDidNotProceed: true };
433
440
  }
434
441
 
442
+ const writePrivateStartupControlJson = createAtomicJsonWriter({ mode: 0o600, ignoreCleanupErrorAfterSuccess: true });
443
+
435
444
  function writeRunnerStartupControl(filePath: string, payload: { action: "ack" | "proceed"; token: string }): void {
436
445
  // Delegate to the shared atomic JSON writer (temp file + rename, retrying
437
446
  // transient Windows EPERM/EBUSY/EACCES locks and cleaning up the temp file
438
447
  // on failure), so the startup handshake gets the same locking resilience as
439
448
  // every other async control/result file. This is exercised by
440
449
  // test/unit/atomic-json.test.ts.
441
- writePrivateAtomicJson(filePath, payload);
450
+ writePrivateStartupControlJson(filePath, payload);
442
451
  }
443
452
 
444
453
  function runnerIsAlive(pid: number): boolean {
@@ -520,7 +529,7 @@ export function emitProcessTerminalEvent(ctx: AsyncExecutionContext, proof: unkn
520
529
  }
521
530
  }
522
531
 
523
- function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, requestedCwd = cwd): SpawnRunnerResult {
532
+ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Omit<AsyncStatus, "pid" | "processTerminal">, initialStatusPath: string, onProcessTerminal?: (proof: unknown) => void, onBeforeProceed?: (runnerProcessInstanceId: string) => void, requestedCwd = cwd): SpawnRunnerResult {
524
533
  const cwdError = preflightLaunchCwd(requestedCwd, cwd);
525
534
  if (cwdError) return { error: cwdError };
526
535
 
@@ -637,6 +646,24 @@ function spawnRunner(cfg: object, suffix: string, cwd: string, initialStatus: Om
637
646
  });
638
647
  } catch (error) {
639
648
  const message = `Failed to persist initial async status: ${error instanceof Error ? error.message : String(error)}`;
649
+ if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
650
+ const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
651
+ return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
652
+ }
653
+ try {
654
+ if (!launchAsyncDir) throw new Error("Async runner is missing its lifecycle directory.");
655
+ initializeProcessTerminal(launchAsyncDir, launchRunId, runnerProcessInstanceId);
656
+ } catch (error) {
657
+ const message = `Failed to establish async runner lifecycle sidecar: ${error instanceof Error ? error.message : String(error)}`;
658
+ if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
659
+ const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
660
+ return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
661
+ }
662
+ try {
663
+ onBeforeProceed?.(runnerProcessInstanceId);
664
+ } catch (error) {
665
+ const message = `Failed to establish async runner capacity ownership: ${error instanceof Error ? error.message : String(error)}`;
666
+ if (launchAsyncDir) persistPreProceedStartupFailure(launchAsyncDir, launchRunId, runnerProcessInstanceId, launchSessionId, launchCompletionOwnerId, message);
640
667
  const terminationObserved = terminateRunnerBeforeProceed(proc.pid);
641
668
  return { pid: proc.pid, runnerProcessInstanceId, error: message, terminationObserved, startupDidNotProceed: true };
642
669
  }
@@ -720,6 +747,8 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
720
747
  thinkingOverridesByFlatIndex,
721
748
  maxSubagentDepth,
722
749
  worktreeBaseDir,
750
+ worktreeProvider,
751
+ worktreeBranchPrefix,
723
752
  asyncDir,
724
753
  } = params;
725
754
  const outputBaseDir = params.outputBaseDir;
@@ -727,6 +756,15 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
727
756
  const chainSkills = params.chainSkills ?? [];
728
757
  const availableModels = params.availableModels;
729
758
  const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
759
+ let managedWorktreeProvider: "native" | "worktrunk" | undefined;
760
+ try {
761
+ if (chain.some((step) => "worktree" in step && step.worktree === true)) {
762
+ const resolved = resolveWorktreeProvider(worktreeProvider, worktreeBaseDir);
763
+ managedWorktreeProvider = shouldDeferWorktreeCwd(worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
764
+ }
765
+ } catch (error) {
766
+ return { error: error instanceof Error ? error.message : String(error) };
767
+ }
730
768
  const progressDir = params.progressDir ?? runnerCwd;
731
769
  const graphChain: ChainStep[] = params.attachRoot
732
770
  ? [{
@@ -853,7 +891,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
853
891
  if (validationError) throw new AsyncStartValidationError(validationError);
854
892
  let taskTemplate = s.task ?? "{previous}";
855
893
  taskTemplate = taskTemplate.replace(/\{task\}/g, originalTask ?? "");
856
- taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, runnerCwd);
894
+ taskTemplate = taskTemplate.replace(/\{chain_dir\}/g, behaviorCwd ?? runnerCwd);
857
895
  const taskText = `${readInstructions.prefix}${taskTemplate}${progressInstructions.suffix}`;
858
896
  const task = namespaceOutputPath ? taskText : injectSingleOutputInstruction(taskText, outputPath, a);
859
897
 
@@ -902,9 +940,12 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
902
940
  throw new AsyncStartValidationError(error instanceof Error ? error.message : String(error));
903
941
  }
904
942
  }
943
+ const launchRuleError = applyWatchdogLaunchRules({ cwd: stepCwd, agent: a.name, model: modelCandidates[0] ?? model, warn: (violation) => sendRuleViolationWarning(ctx.pi, violation) });
944
+ if (launchRuleError) throw new AsyncStartValidationError(launchRuleError);
905
945
  const fast = s.fast ?? params.fast ?? a.fast;
906
946
  const toolPlan = resolvePiLaunchToolPlan({
907
947
  tools: a.tools,
948
+ excludeTools: a.excludeTools,
908
949
  allowNestedSubagents: a.allowNestedSubagents,
909
950
  extensions: a.extensions,
910
951
  subagentOnlyExtensions: a.subagentOnlyExtensions,
@@ -967,6 +1008,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
967
1008
  ...(primaryModelFromParent ? { skipPrimaryModelVerification: true } : {}),
968
1009
  ...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
969
1010
  tools: a.tools,
1011
+ excludeTools: a.excludeTools,
970
1012
  allowNestedSubagents: a.allowNestedSubagents,
971
1013
  extensions: a.extensions,
972
1014
  subagentOnlyExtensions: a.subagentOnlyExtensions,
@@ -1002,7 +1044,7 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
1002
1044
  acceptanceRole: a.acceptanceRole,
1003
1045
  ...(s.gateOn ? { gateOn: s.gateOn } : {}),
1004
1046
  ...(s.outputSchema ? { structuredOutputSchema: s.outputSchema } : {}),
1005
- ...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: s.acceptance !== false }) } : {}),
1047
+ ...(s.outputSchema ? { structuredOutput: createStructuredOutputRuntime(s.outputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(s.acceptance) }) } : {}),
1006
1048
  ...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
1007
1049
  ...(s.worktree ? { worktree: true } : {}),
1008
1050
  };
@@ -1036,7 +1078,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
1036
1078
  return {
1037
1079
  parallel: s.parallel.map((t, taskIndex) => {
1038
1080
  let behaviorCwd: string | undefined;
1039
- if (s.worktree) {
1081
+ if (s.worktree && managedWorktreeProvider === "worktrunk") {
1082
+ behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
1083
+ } else if (s.worktree && managedWorktreeProvider === "native") {
1040
1084
  try {
1041
1085
  behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, taskIndex, worktreeBaseDir);
1042
1086
  } catch {
@@ -1091,7 +1135,9 @@ export function buildAsyncRunnerSteps(id: string, params: AsyncRunnerStepBuildPa
1091
1135
  }
1092
1136
  const sequential = s as SequentialStep;
1093
1137
  let behaviorCwd: string | undefined;
1094
- if (sequential.worktree) {
1138
+ if (sequential.worktree && managedWorktreeProvider === "worktrunk") {
1139
+ behaviorCwd = WORKTREE_AGENT_CWD_PLACEHOLDER;
1140
+ } else if (sequential.worktree && managedWorktreeProvider === "native") {
1095
1141
  try {
1096
1142
  behaviorCwd = resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s${stepIndex}`, 0, worktreeBaseDir);
1097
1143
  } catch {
@@ -1161,6 +1207,8 @@ export function executeAsyncChain(
1161
1207
  worktreeSetupHook,
1162
1208
  worktreeSetupHookTimeoutMs,
1163
1209
  worktreeBaseDir,
1210
+ worktreeProvider,
1211
+ worktreeBranchPrefix,
1164
1212
  controlConfig,
1165
1213
  controlIntercomTarget,
1166
1214
  childIntercomTarget,
@@ -1171,7 +1219,7 @@ export function executeAsyncChain(
1171
1219
  chain: chain.map((step) => {
1172
1220
  if (isParallelStep(step)) return { parallel: step.parallel };
1173
1221
  if (isDynamicParallelStep(step)) return { acceptance: step.acceptance, parallel: step.parallel };
1174
- return { acceptance: step.acceptance };
1222
+ return { acceptance: step.acceptance, outputSchema: step.outputSchema };
1175
1223
  }),
1176
1224
  });
1177
1225
  if (acceptanceErrors.length > 0) return formatAsyncStartError(resultMode, acceptanceErrors.join(" "));
@@ -1217,6 +1265,8 @@ export function executeAsyncChain(
1217
1265
  waitToolEnabled: params.waitToolEnabled,
1218
1266
  waitToolDefaultTimeoutMs: params.waitToolDefaultTimeoutMs,
1219
1267
  worktreeBaseDir,
1268
+ worktreeProvider,
1269
+ worktreeBranchPrefix,
1220
1270
  asyncDir,
1221
1271
  fast: params.fast,
1222
1272
  toolBudget: params.toolBudget,
@@ -1292,6 +1342,8 @@ export function executeAsyncChain(
1292
1342
  worktreeSetupHook,
1293
1343
  worktreeSetupHookTimeoutMs,
1294
1344
  worktreeBaseDir,
1345
+ worktreeProvider,
1346
+ worktreeBranchPrefix,
1295
1347
  controlConfig,
1296
1348
  toolBudget: params.toolBudget,
1297
1349
  usageBudget: params.usageBudget,
@@ -1332,6 +1384,7 @@ export function executeAsyncChain(
1332
1384
  },
1333
1385
  path.join(asyncDir, "status.json"),
1334
1386
  (proof) => emitProcessTerminalEvent(ctx, proof),
1387
+ (runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
1335
1388
  );
1336
1389
  } catch (error) {
1337
1390
  params.activeAsyncCapacity?.rollback();
@@ -1340,7 +1393,10 @@ export function executeAsyncChain(
1340
1393
  }
1341
1394
 
1342
1395
  if (spawnResult.error) {
1343
- if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId || (spawnResult.startupDidNotProceed && spawnResult.terminationObserved)) params.activeAsyncCapacity?.rollback();
1396
+ if (spawnResult.startupDidNotProceed) {
1397
+ if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
1398
+ }
1399
+ else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
1344
1400
  else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
1345
1401
  return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': ${spawnResult.error}`);
1346
1402
  }
@@ -1348,8 +1404,6 @@ export function executeAsyncChain(
1348
1404
  params.activeAsyncCapacity?.rollback();
1349
1405
  return formatAsyncStartError(resultMode, `Failed to start async ${resultMode} '${id}': runner identity unavailable`);
1350
1406
  }
1351
- params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
1352
-
1353
1407
  if (spawnResult.pid) {
1354
1408
  const eventFirstStep = eventChain[0];
1355
1409
  if (!eventFirstStep) {
@@ -1487,6 +1541,8 @@ export function executeAsyncSingle(
1487
1541
  worktreeSetupHook,
1488
1542
  worktreeSetupHookTimeoutMs,
1489
1543
  worktreeBaseDir,
1544
+ worktreeProvider,
1545
+ worktreeBranchPrefix,
1490
1546
  controlConfig,
1491
1547
  controlIntercomTarget,
1492
1548
  childIntercomTarget,
@@ -1532,7 +1588,18 @@ export function executeAsyncSingle(
1532
1588
  return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
1533
1589
  }
1534
1590
  const runnerCwd = resolveChildCwd(ctx.cwd, cwd);
1535
- const instructionCwd = params.worktree === true
1591
+ let managedWorktreeProvider: "native" | "worktrunk" | undefined;
1592
+ if (params.worktree === true) {
1593
+ try {
1594
+ const resolved = resolveWorktreeProvider(params.worktreeProvider, worktreeBaseDir);
1595
+ managedWorktreeProvider = shouldDeferWorktreeCwd(params.worktreeProvider, worktreeBaseDir) ? "worktrunk" : resolved;
1596
+ } catch (error) {
1597
+ return formatAsyncStartError("single", error instanceof Error ? error.message : String(error));
1598
+ }
1599
+ }
1600
+ const instructionCwd = params.worktree === true && managedWorktreeProvider === "worktrunk"
1601
+ ? WORKTREE_AGENT_CWD_PLACEHOLDER
1602
+ : params.worktree === true && managedWorktreeProvider === "native"
1536
1603
  ? resolveExpectedWorktreeAgentCwd(runnerCwd, `${id}-s0`, 0, worktreeBaseDir)
1537
1604
  : runnerCwd;
1538
1605
  const readExistenceCwd = params.worktree === true ? runnerCwd : instructionCwd;
@@ -1586,7 +1653,11 @@ export function executeAsyncSingle(
1586
1653
  // Reads: caller override > agent defaultReads > none. `~`/`~/` expand to home;
1587
1654
  // absolute paths pass through; relative paths resolve against the child cwd.
1588
1655
  const reads = params.reads !== undefined ? params.reads : agentConfig.defaultReads ?? false;
1589
- const readPaths = Array.isArray(reads) ? resolveExistingReadPaths(reads, readExistenceCwd) : [];
1656
+ const readPaths = Array.isArray(reads)
1657
+ ? managedWorktreeProvider === "worktrunk"
1658
+ ? resolveExistingReadInstructionPaths(reads, instructionCwd, readExistenceCwd)
1659
+ : resolveExistingReadPaths(reads, readExistenceCwd)
1660
+ : [];
1590
1661
  const readsInstruction = readPaths.length > 0
1591
1662
  ? `[Read from: ${readPaths.join(", ")}]\n\n`
1592
1663
  : "";
@@ -1647,7 +1718,7 @@ export function executeAsyncSingle(
1647
1718
  const initialUsageBudget = usageBudgetState(params.usageBudget, undefined);
1648
1719
  const resolvedSessionDir = params.sessionDir ?? (sessionRoot ? path.join(sessionRoot, `async-${id}`) : undefined);
1649
1720
  const structuredOutput = params.structuredOutputSchema
1650
- ? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { captureAcceptanceReport: params.acceptance !== false })
1721
+ ? createStructuredOutputRuntime(params.structuredOutputSchema, path.join(asyncDir, "structured-output"), { acceptanceReport: resolveAcceptanceReportMode(params.acceptance) })
1651
1722
  : undefined;
1652
1723
  let modelCandidates: string[] = [];
1653
1724
  if (!externalRunner) {
@@ -1667,6 +1738,7 @@ export function executeAsyncSingle(
1667
1738
  }
1668
1739
  const toolPlan = resolvePiLaunchToolPlan({
1669
1740
  tools: agentConfig.tools,
1741
+ excludeTools: agentConfig.excludeTools,
1670
1742
  allowNestedSubagents: agentConfig.allowNestedSubagents,
1671
1743
  extensions: agentConfig.extensions,
1672
1744
  subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
@@ -1713,6 +1785,7 @@ export function executeAsyncSingle(
1713
1785
  inheritSkills: agentConfig.inheritSkills,
1714
1786
  skills: resolvedSkills.map((skill) => skill.name),
1715
1787
  tools: toolPlan.effectiveToolAllowlist,
1788
+ ...(toolPlan.excludeTools.length > 0 ? { excludeTools: toolPlan.excludeTools } : {}),
1716
1789
  extensions: toolPlan.extensionArgs,
1717
1790
  mcpDirectTools: toolPlan.effectiveMcpTools,
1718
1791
  ...(outputPath ? { outputPath } : {}),
@@ -1751,6 +1824,7 @@ export function executeAsyncSingle(
1751
1824
  ...(effectiveThinking ? { thinking: resolveEffectiveThinking(model, effectiveThinking) } : {}),
1752
1825
  ...(thinkingCeiling ? { thinkingCeiling } : {}),
1753
1826
  ...(recoveryAgentConfig.tools ? { tools: [...recoveryAgentConfig.tools] } : {}),
1827
+ ...(recoveryAgentConfig.excludeTools ? { excludeTools: [...recoveryAgentConfig.excludeTools] } : {}),
1754
1828
  ...(recoveryAgentConfig.allowNestedSubagents !== undefined ? { allowNestedSubagents: recoveryAgentConfig.allowNestedSubagents } : {}),
1755
1829
  ...(recoveryAgentConfig.extensions ? { extensions: [...recoveryAgentConfig.extensions] } : {}),
1756
1830
  ...(recoveryAgentConfig.subagentOnlyExtensions ? { subagentOnlyExtensions: [...recoveryAgentConfig.subagentOnlyExtensions] } : {}),
@@ -1818,6 +1892,7 @@ export function executeAsyncSingle(
1818
1892
  ...(modelOrigin === "inherited" ? { skipPrimaryModelVerification: true } : {}),
1819
1893
  ...(availableModels && availableModels.length > 0 ? { modelVerificationRegistry: availableModels } : {}),
1820
1894
  tools: agentConfig.tools,
1895
+ excludeTools: agentConfig.excludeTools,
1821
1896
  allowNestedSubagents: agentConfig.allowNestedSubagents,
1822
1897
  extensions: agentConfig.extensions,
1823
1898
  subagentOnlyExtensions: agentConfig.subagentOnlyExtensions,
@@ -1846,6 +1921,7 @@ export function executeAsyncSingle(
1846
1921
  ...(extensionBindings ? { extensionBindings } : {}),
1847
1922
  launchResolvedExtensions,
1848
1923
  effectiveAcceptance: resolvedAcceptance,
1924
+ acceptanceInput: params.acceptance,
1849
1925
  ...(structuredOutput ? { structuredOutput } : {}),
1850
1926
  ...(params.structuredOutputSchema ? { structuredOutputSchema: params.structuredOutputSchema } : {}),
1851
1927
  ...(resolvedToolBudget.budget ? { toolBudget: resolvedToolBudget.budget } : {}),
@@ -1872,6 +1948,8 @@ export function executeAsyncSingle(
1872
1948
  worktreeSetupHook,
1873
1949
  worktreeSetupHookTimeoutMs,
1874
1950
  worktreeBaseDir,
1951
+ worktreeProvider,
1952
+ worktreeBranchPrefix,
1875
1953
  controlConfig,
1876
1954
  timeoutMs,
1877
1955
  deadlineAt,
@@ -1914,6 +1992,7 @@ export function executeAsyncSingle(
1914
1992
  },
1915
1993
  path.join(asyncDir, "status.json"),
1916
1994
  (proof) => emitProcessTerminalEvent(ctx, proof),
1995
+ (runnerProcessInstanceId) => params.activeAsyncCapacity?.markStarted(runnerProcessInstanceId),
1917
1996
  params.requestedCwd ?? runnerCwd,
1918
1997
  );
1919
1998
  } catch (error) {
@@ -1923,7 +2002,10 @@ export function executeAsyncSingle(
1923
2002
  }
1924
2003
 
1925
2004
  if (spawnResult.error) {
1926
- if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId || (spawnResult.startupDidNotProceed && spawnResult.terminationObserved)) params.activeAsyncCapacity?.rollback();
2005
+ if (spawnResult.startupDidNotProceed) {
2006
+ if (!spawnResult.runnerProcessInstanceId || params.activeAsyncCapacity?.rollbackBeforeRunnerProceed(spawnResult.runnerProcessInstanceId) !== true) params.activeAsyncCapacity?.rollback();
2007
+ }
2008
+ else if (!spawnResult.pid || !spawnResult.runnerProcessInstanceId) params.activeAsyncCapacity?.rollback();
1927
2009
  else params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
1928
2010
  return formatAsyncStartError("single", `Failed to start async run '${id}': ${spawnResult.error}`);
1929
2011
  }
@@ -1931,8 +2013,6 @@ export function executeAsyncSingle(
1931
2013
  params.activeAsyncCapacity?.rollback();
1932
2014
  return formatAsyncStartError("single", `Failed to start async run '${id}': runner identity unavailable`);
1933
2015
  }
1934
- params.activeAsyncCapacity?.markStarted(spawnResult.runnerProcessInstanceId);
1935
-
1936
2016
  if (spawnResult.pid) {
1937
2017
  if (inheritedNestedRoute && nestedAddress) {
1938
2018
  const now = Date.now();
@@ -45,6 +45,8 @@ export type AsyncResumeTarget = {
45
45
  sessionName?: string;
46
46
  index: number;
47
47
  cwd?: string;
48
+ /** True when cwd is the retained managed worktree recorded by the handoff. */
49
+ managedWorktree?: boolean;
48
50
  sessionFile?: string;
49
51
  model?: string;
50
52
  thinking?: string;
@@ -315,7 +317,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
315
317
  const parsed = value as Record<string, unknown>;
316
318
  const allowedFields = new Set([
317
319
  "version", "launchContractDigest", "sourceRunId", "agentContract", "agent", "sessionFile", "cwd", "model", "modelProvider", "modelOverrideFromParent", "modelOrigin", "fallbackModels", "thinking", "thinkingCeiling", "tools", "allowNestedSubagents", "extensions",
318
- "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
320
+ "subagentOnlyExtensions", "mcpDirectTools", "excludeTools", "mutationTools", "systemPrompt", "systemPromptMode", "inheritProjectContext", "inheritGlobalContext", "inheritSkills", "skills",
319
321
  "skillPath", "agentFilePath", "completionGuard", "memory", "outputPath", "outputMode", "structuredOutputSchema", "acceptance", "sessionDir", "artifactConfig",
320
322
  "artifactsDir", "maxOutput", "controlConfig", "context", "intercomBridge", "absoluteDeadlineAt", "initialTurnBudget", "initialToolBudget", "maxSubagentDepth", "share", "capabilityCeiling",
321
323
  "launchResolvedExtensions", "runFanoutBudget", "lane",
@@ -356,7 +358,7 @@ export function readAsyncRecoveryDescriptor(asyncDir: string | undefined): Steer
356
358
  else if (typeof parsed.inheritGlobalContext !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': inheritGlobalContext must be a boolean.`);
357
359
  if (parsed.allowNestedSubagents !== undefined && typeof parsed.allowNestedSubagents !== "boolean") throw new Error(`Invalid async recovery descriptor '${descriptorPath}': allowNestedSubagents must be a boolean.`);
358
360
  if (!Number.isInteger(parsed.maxSubagentDepth) || (parsed.maxSubagentDepth as number) < 0) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': maxSubagentDepth must be a non-negative integer.`);
359
- for (const field of ["fallbackModels", "tools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
361
+ for (const field of ["fallbackModels", "tools", "excludeTools", "extensions", "subagentOnlyExtensions", "mcpDirectTools", "mutationTools", "skills", "skillPath"] as const) {
360
362
  const item = parsed[field];
361
363
  if (item !== undefined && (!Array.isArray(item) || item.some((entry) => typeof entry !== "string" || !entry.trim()))) throw new Error(`Invalid async recovery descriptor '${descriptorPath}': ${field} must contain non-empty strings.`);
362
364
  }
@@ -569,6 +571,7 @@ export function resolveAsyncResumeTarget(params: AsyncResumeParams, deps: AsyncR
569
571
  ...(statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName ? { sessionName: statusSteps[index]?.sessionName ?? resultSteps[index]?.sessionName } : {}),
570
572
  index,
571
573
  ...(resumeCwd ? { cwd: resumeCwd } : {}),
574
+ ...(managedWorktreeCwd ? { managedWorktree: true } : {}),
572
575
  ...(resolvedSessionFile ? { sessionFile: resolvedSessionFile } : {}),
573
576
  ...(stepModel ? { model: stepModel } : {}),
574
577
  ...(stepThinking ? { thinking: stepThinking } : {}),
@@ -590,6 +593,7 @@ export function applySteeringRecoveryAgentConfig(agentConfig: AgentConfig, descr
590
593
  thinking: descriptor.thinking,
591
594
  maxThinking: intersectThinkingCeilings(descriptor.thinkingCeiling, agentConfig.maxThinking),
592
595
  tools: descriptor.tools ? [...descriptor.tools] : undefined,
596
+ excludeTools: descriptor.excludeTools ? [...descriptor.excludeTools] : undefined,
593
597
  allowNestedSubagents: descriptor.allowNestedSubagents,
594
598
  extensions: descriptor.extensions ? [...descriptor.extensions] : undefined,
595
599
  subagentOnlyExtensions: descriptor.subagentOnlyExtensions ? [...descriptor.subagentOnlyExtensions] : undefined,
@@ -3,7 +3,7 @@ import * as path from "node:path";
3
3
  import { formatDuration, formatModelThinking, formatTokens, shortenPath } from "../../shared/formatters.ts";
4
4
  import { previewDisplayText } from "../../shared/display-text.ts";
5
5
  import { formatActivityLabel, formatParallelOutcome } from "../../shared/status-format.ts";
6
- import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
6
+ import { type ActivityState, type AsyncJobStep, type AsyncParallelGroupStatus, type AsyncStatus, type CostSummary, type Details, type HostStepNodeV1, type HostStepState, type LaunchResolvedChildExtensionsV1, type RuntimeAcknowledgedChildExtensionsV1, type NestedRunSummary, type SteeringStatus, type SubagentRunMode, type TimeoutRecoveryProjection, type TokenUsage, type TurnBudgetState, type UsageBudgetState, type WorktreeNaming, type WorkflowPreflightV1, type WorkflowGraphSnapshot } from "../../shared/types.ts";
7
7
  import type { ResolvedSubagentCapabilityCeiling, SubagentCapabilityAudit } from "../shared/capability-ceiling.ts";
8
8
  import { readStatus } from "../../shared/utils.ts";
9
9
  import { attachRootChildrenToSteps, buildNestedRouteIndex, findNestedRouteForRootId, type NestedRoute, projectNestedEvents } from "../shared/nested-events.ts";
@@ -24,6 +24,7 @@ import { validateAsyncStatusLaneMetadata } from "../shared/lane-metadata.ts";
24
24
  import { formatWorkflowPreflightPlanSummary, formatWorkflowPreflightWarningSummary } from "../../workflows/workflow-preflight.ts";
25
25
  import { workflowGraphStageNodes } from "../shared/workflow-graph.ts";
26
26
  import { formatTimeoutRecoveryLines, projectTimeoutRecovery } from "../shared/mutation-evidence.ts";
27
+ import { formatWorkflowChecklistText, projectWorkflowChecklist } from "../../workflows/workflow-checklist.ts";
27
28
 
28
29
  interface AsyncRunStepSummary {
29
30
  index: number;
@@ -39,6 +40,8 @@ interface AsyncRunStepSummary {
39
40
  lane?: AsyncJobStep["lane"];
40
41
  worktreePath?: string;
41
42
  branch?: string;
43
+ provider?: "native" | "worktrunk";
44
+ naming?: WorktreeNaming;
42
45
  runId?: string;
43
46
  outputName?: string;
44
47
  structured?: boolean;
@@ -337,6 +340,8 @@ function statusToSummary(asyncDir: string, status: AsyncStatus & { cwd?: string
337
340
  ...(step.lane ? { lane: step.lane } : {}),
338
341
  ...(step.worktreePath ? { worktreePath: step.worktreePath } : {}),
339
342
  ...(step.branch ? { branch: step.branch } : {}),
343
+ ...(step.provider ? { provider: step.provider } : {}),
344
+ ...(step.naming ? { naming: step.naming } : {}),
340
345
  ...(step.runId ? { runId: step.runId } : {}),
341
346
  ...(step.outputName ? { outputName: step.outputName } : {}),
342
347
  ...(step.structured ? { structured: step.structured } : {}),
@@ -623,7 +628,7 @@ function formatStepLine(step: AsyncRunStepSummary): string {
623
628
  if (step.durationMs !== undefined) parts.push(formatDuration(step.durationMs));
624
629
  if (step.tokens) parts.push(`${formatTokens(step.tokens.total)} tok`);
625
630
  if (step.lane) parts.push(`lane ${step.lane.key}`);
626
- if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}`);
631
+ if (step.worktreePath) parts.push(`worktree ${shortenPath(step.worktreePath)} · branch ${step.branch ?? "unknown"}${step.provider ? ` · provider ${step.provider}` : ""}`);
627
632
  return parts.join(" | ");
628
633
  }
629
634
 
@@ -716,6 +721,17 @@ export function formatAsyncRunList(runs: AsyncRunSummary[], heading = "Active as
716
721
  if (run.preflight) lines.push(formatWorkflowPreflightPlanSummary(run.preflight, { indent: " " }));
717
722
  const preflightWarning = formatWorkflowPreflightWarningSummary(run.workflow?.preflightWarnings, { indent: " " });
718
723
  if (preflightWarning) lines.push(preflightWarning);
724
+ if (run.mode === "workflow") {
725
+ const checklist = projectWorkflowChecklist({
726
+ graph: run.workflowGraph,
727
+ steps: run.steps,
728
+ hostSteps: run.hostSteps,
729
+ preflight: run.preflight,
730
+ trace: run.workflow?.trace,
731
+ now: run.lastUpdate ?? run.endedAt ?? Date.now(),
732
+ });
733
+ lines.push(...formatWorkflowChecklistText(checklist, " ", { includeItems: false }));
734
+ }
719
735
  for (const step of run.steps) {
720
736
  lines.push(` ${formatStepLine(step)}`);
721
737
  lines.push(...formatTimeoutRecoveryLines(step.timeoutRecovery, " "));