@pi-agents/orchid 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +21 -0
  3. package/README.md +246 -0
  4. package/agents/AGENTS-MANIFEST.md +42 -0
  5. package/agents/brain.md +42 -0
  6. package/agents/context-builder.md +46 -0
  7. package/agents/delegate.md +12 -0
  8. package/agents/dev-1.md +42 -0
  9. package/agents/oracle.md +73 -0
  10. package/agents/planner.md +55 -0
  11. package/agents/researcher.md +52 -0
  12. package/agents/reviewer.md +79 -0
  13. package/agents/scout.md +50 -0
  14. package/agents/tester.md +45 -0
  15. package/agents/worker.md +55 -0
  16. package/extensions/ralph.ts +1 -0
  17. package/extensions/reviewer-extension.ts +125 -0
  18. package/extensions/task-orchestrator.ts +28 -0
  19. package/package.json +63 -0
  20. package/prompts/gather-context-and-clarify.md +13 -0
  21. package/prompts/parallel-cleanup.md +59 -0
  22. package/prompts/parallel-context-build.md +53 -0
  23. package/prompts/parallel-handoff-plan.md +59 -0
  24. package/prompts/parallel-research.md +50 -0
  25. package/prompts/parallel-review.md +54 -0
  26. package/prompts/review-loop.md +41 -0
  27. package/skills/orchid/SKILL.md +214 -0
  28. package/skills/orchid/orchid-cleanup/SKILL.md +122 -0
  29. package/skills/orchid/orchid-converge/SKILL.md +124 -0
  30. package/skills/orchid/orchid-decompose/SKILL.md +201 -0
  31. package/skills/orchid/orchid-doctor/SKILL.md +162 -0
  32. package/skills/orchid/orchid-investigate/SKILL.md +102 -0
  33. package/skills/orchid/orchid-launch/SKILL.md +147 -0
  34. package/skills/ralph/SKILL.md +73 -0
  35. package/skills/subagents/pi-subagents/SKILL.md +813 -0
  36. package/src/index.ts +7 -0
  37. package/src/orchestrator/abort.ts +534 -0
  38. package/src/orchestrator/agent-bridge-extension.ts +1020 -0
  39. package/src/orchestrator/agent-host.ts +954 -0
  40. package/src/orchestrator/cleanup.ts +776 -0
  41. package/src/orchestrator/config-loader.ts +1412 -0
  42. package/src/orchestrator/config-schema.ts +690 -0
  43. package/src/orchestrator/config.ts +81 -0
  44. package/src/orchestrator/context-window.ts +66 -0
  45. package/src/orchestrator/diagnostic-reports.ts +475 -0
  46. package/src/orchestrator/diagnostics.ts +394 -0
  47. package/src/orchestrator/discovery.ts +1833 -0
  48. package/src/orchestrator/engine-worker.ts +415 -0
  49. package/src/orchestrator/engine.ts +5940 -0
  50. package/src/orchestrator/execution.ts +3104 -0
  51. package/src/orchestrator/extension.ts +5934 -0
  52. package/src/orchestrator/formatting.ts +785 -0
  53. package/src/orchestrator/git.ts +88 -0
  54. package/src/orchestrator/index.ts +28 -0
  55. package/src/orchestrator/lane-runner.ts +1787 -0
  56. package/src/orchestrator/mailbox.ts +780 -0
  57. package/src/orchestrator/merge.ts +3414 -0
  58. package/src/orchestrator/messages.ts +1062 -0
  59. package/src/orchestrator/migrations.ts +278 -0
  60. package/src/orchestrator/naming.ts +117 -0
  61. package/src/orchestrator/path-resolver.ts +275 -0
  62. package/src/orchestrator/persistence.ts +2625 -0
  63. package/src/orchestrator/process-registry.ts +452 -0
  64. package/src/orchestrator/quality-gate.ts +1085 -0
  65. package/src/orchestrator/resume.ts +3488 -0
  66. package/src/orchestrator/sessions.ts +57 -0
  67. package/src/orchestrator/settings-loader.ts +136 -0
  68. package/src/orchestrator/settings-tui.ts +2208 -0
  69. package/src/orchestrator/sidecar-telemetry.ts +267 -0
  70. package/src/orchestrator/supervisor.ts +4548 -0
  71. package/src/orchestrator/task-executor-core.ts +675 -0
  72. package/src/orchestrator/tmux-compat.ts +37 -0
  73. package/src/orchestrator/tool-allowlist-constants.ts +37 -0
  74. package/src/orchestrator/types.ts +4465 -0
  75. package/src/orchestrator/verification.ts +547 -0
  76. package/src/orchestrator/waves.ts +1564 -0
  77. package/src/orchestrator/workspace.ts +707 -0
  78. package/src/orchestrator/worktree.ts +2725 -0
  79. package/src/ralph/index.ts +825 -0
  80. package/src/subagents/agents/agent-management.ts +648 -0
  81. package/src/subagents/agents/agent-scope.ts +6 -0
  82. package/src/subagents/agents/agent-selection.ts +23 -0
  83. package/src/subagents/agents/agent-serializer.ts +86 -0
  84. package/src/subagents/agents/agents.ts +832 -0
  85. package/src/subagents/agents/chain-serializer.ts +137 -0
  86. package/src/subagents/agents/frontmatter.ts +29 -0
  87. package/src/subagents/agents/identity.ts +30 -0
  88. package/src/subagents/agents/skills.ts +632 -0
  89. package/src/subagents/extension/config.ts +16 -0
  90. package/src/subagents/extension/control-notices.ts +92 -0
  91. package/src/subagents/extension/doctor.ts +199 -0
  92. package/src/subagents/extension/fanout-child.ts +170 -0
  93. package/src/subagents/extension/index.ts +573 -0
  94. package/src/subagents/extension/schemas.ts +168 -0
  95. package/src/subagents/intercom/intercom-bridge.ts +379 -0
  96. package/src/subagents/intercom/result-intercom.ts +377 -0
  97. package/src/subagents/runs/background/async-execution.ts +712 -0
  98. package/src/subagents/runs/background/async-job-tracker.ts +310 -0
  99. package/src/subagents/runs/background/async-resume.ts +345 -0
  100. package/src/subagents/runs/background/async-status.ts +325 -0
  101. package/src/subagents/runs/background/completion-dedupe.ts +63 -0
  102. package/src/subagents/runs/background/notify.ts +108 -0
  103. package/src/subagents/runs/background/parallel-groups.ts +45 -0
  104. package/src/subagents/runs/background/result-watcher.ts +307 -0
  105. package/src/subagents/runs/background/run-id-resolver.ts +83 -0
  106. package/src/subagents/runs/background/run-status.ts +269 -0
  107. package/src/subagents/runs/background/stale-run-reconciler.ts +336 -0
  108. package/src/subagents/runs/background/subagent-runner.ts +1808 -0
  109. package/src/subagents/runs/background/top-level-async.ts +13 -0
  110. package/src/subagents/runs/foreground/chain-clarify.ts +1333 -0
  111. package/src/subagents/runs/foreground/chain-execution.ts +938 -0
  112. package/src/subagents/runs/foreground/execution.ts +918 -0
  113. package/src/subagents/runs/foreground/subagent-executor.ts +2527 -0
  114. package/src/subagents/runs/shared/completion-guard.ts +147 -0
  115. package/src/subagents/runs/shared/long-running-guard.ts +175 -0
  116. package/src/subagents/runs/shared/mcp-direct-tool-allowlist.ts +365 -0
  117. package/src/subagents/runs/shared/model-fallback.ts +103 -0
  118. package/src/subagents/runs/shared/nested-events.ts +819 -0
  119. package/src/subagents/runs/shared/nested-path.ts +52 -0
  120. package/src/subagents/runs/shared/nested-render.ts +115 -0
  121. package/src/subagents/runs/shared/parallel-utils.ts +109 -0
  122. package/src/subagents/runs/shared/pi-args.ts +220 -0
  123. package/src/subagents/runs/shared/pi-spawn.ts +115 -0
  124. package/src/subagents/runs/shared/run-history.ts +60 -0
  125. package/src/subagents/runs/shared/single-output.ts +164 -0
  126. package/src/subagents/runs/shared/subagent-control.ts +226 -0
  127. package/src/subagents/runs/shared/subagent-prompt-runtime.ts +170 -0
  128. package/src/subagents/runs/shared/worktree.ts +577 -0
  129. package/src/subagents/shared/artifacts.ts +98 -0
  130. package/src/subagents/shared/atomic-json.ts +16 -0
  131. package/src/subagents/shared/file-coalescer.ts +40 -0
  132. package/src/subagents/shared/fork-context.ts +76 -0
  133. package/src/subagents/shared/formatters.ts +133 -0
  134. package/src/subagents/shared/jsonl-writer.ts +81 -0
  135. package/src/subagents/shared/model-info.ts +78 -0
  136. package/src/subagents/shared/post-exit-stdio-guard.ts +85 -0
  137. package/src/subagents/shared/session-identity.ts +10 -0
  138. package/src/subagents/shared/session-tokens.ts +44 -0
  139. package/src/subagents/shared/settings.ts +397 -0
  140. package/src/subagents/shared/status-format.ts +49 -0
  141. package/src/subagents/shared/types.ts +822 -0
  142. package/src/subagents/shared/utils.ts +450 -0
  143. package/src/subagents/slash/prompt-template-bridge.ts +397 -0
  144. package/src/subagents/slash/slash-bridge.ts +174 -0
  145. package/src/subagents/slash/slash-commands.ts +528 -0
  146. package/src/subagents/slash/slash-live-state.ts +292 -0
  147. package/src/subagents/tui/render-helpers.ts +80 -0
  148. package/src/subagents/tui/render.ts +1358 -0
  149. package/templates/agents/local/supervisor.md +33 -0
  150. package/templates/agents/local/task-merger.md +27 -0
  151. package/templates/agents/local/task-reviewer.md +30 -0
  152. package/templates/agents/local/task-worker.md +34 -0
  153. package/templates/agents/supervisor-routing.md +92 -0
  154. package/templates/agents/supervisor.md +229 -0
  155. package/templates/agents/task-merger.md +214 -0
  156. package/templates/agents/task-reviewer.md +260 -0
  157. package/templates/agents/task-worker-segment.md +44 -0
  158. package/templates/agents/task-worker.md +557 -0
  159. package/templates/tasks/CONTEXT.md +30 -0
  160. package/templates/tasks/EXAMPLE-001-hello-world/PROMPT.md +98 -0
  161. package/templates/tasks/EXAMPLE-001-hello-world/STATUS.md +73 -0
  162. package/templates/tasks/EXAMPLE-002-parallel-smoke/PROMPT.md +97 -0
  163. package/templates/tasks/EXAMPLE-002-parallel-smoke/STATUS.md +73 -0
@@ -0,0 +1,415 @@
1
+ /**
2
+ * Engine Child Process Entry Point (TP-071)
3
+ *
4
+ * This module serves two purposes:
5
+ * 1. Exports types and helpers used by extension.ts (main thread)
6
+ * 2. When forked as a child process, runs the engine in a separate Node.js process
7
+ *
8
+ * Uses child_process.fork() instead of worker_threads because Node v25's
9
+ * default --experimental-strip-types rejects .ts files inside node_modules.
10
+ * Fork creates a new process where --experimental-transform-types takes effect.
11
+ *
12
+ * Communication:
13
+ * - Child → Parent: process.send() for notify, monitor-update, engine-event, state-sync, complete, error
14
+ * - Parent → Child: child.send() for init, pause, resume, abort
15
+ *
16
+ * @module orch/engine-worker
17
+ */
18
+ import type {
19
+ AllocatedLane,
20
+ EngineEvent,
21
+ MonitorState,
22
+ OrchBatchPhase,
23
+ OrchBatchRuntimeState,
24
+ OrchestratorConfig,
25
+ SupervisorAlert,
26
+ TaskRunnerConfig,
27
+ WorkspaceConfig,
28
+ WorkspaceRepoConfig,
29
+ } from "./types.ts";
30
+
31
+ // ── Types for worker <-> main thread messages ────────────────────────
32
+
33
+ /**
34
+ * Messages sent FROM the worker TO the main thread.
35
+ */
36
+ export type WorkerErrorSource = "enginePromise" | "uncaughtException" | "unhandledRejection";
37
+
38
+ export type WorkerToMainMessage =
39
+ | { type: "notify"; msg: string; level: "info" | "warning" | "error" }
40
+ | { type: "monitor-update"; state: MonitorState }
41
+ | { type: "engine-event"; event: EngineEvent }
42
+ | { type: "supervisor-alert"; alert: SupervisorAlert }
43
+ /**
44
+ * TP-187 (#538): Lane has reached a terminal state. The supervisor process
45
+ * uses this to mark the lane terminated and filter any subsequently-arriving
46
+ * (zombie) alerts whose `context.laneNumber`/`context.agentId` matches.
47
+ */
48
+ | { type: "lane-terminated"; info: import("./types.ts").LaneTerminatedInfo }
49
+ /**
50
+ * TP-187 (#538): Lane number has been re-allocated to a fresh task. The
51
+ * supervisor lifts the suppression so subsequent alerts pass through.
52
+ */
53
+ | { type: "lane-respawned"; laneNumber: number; agentId: string; batchId: string }
54
+ | { type: "state-sync"; state: SerializedBatchState }
55
+ | { type: "complete"; state: SerializedBatchState }
56
+ | { type: "error"; message: string; stack?: string; source?: WorkerErrorSource };
57
+
58
+ /**
59
+ * Messages sent FROM the main thread TO the worker.
60
+ */
61
+ export type WorkerInMessage = { type: "pause" } | { type: "resume" } | { type: "abort" };
62
+
63
+ /**
64
+ * Serializable form of OrchBatchRuntimeState fields synced to main thread.
65
+ * Only includes fields the main thread needs for display/state tracking.
66
+ */
67
+ export interface SerializedBatchState {
68
+ phase: OrchBatchPhase;
69
+ batchId: string;
70
+ baseBranch: string;
71
+ orchBranch: string;
72
+ mode: string;
73
+ currentWaveIndex: number;
74
+ totalWaves: number;
75
+ totalTasks: number;
76
+ succeededTasks: number;
77
+ failedTasks: number;
78
+ skippedTasks: number;
79
+ blockedTasks: number;
80
+ startedAt: number;
81
+ endedAt: number | null;
82
+ errors: string[];
83
+ /** Active lanes for the current wave (synced so /orch-sessions works). */
84
+ currentLanes: AllocatedLane[];
85
+ }
86
+
87
+ /**
88
+ * Serializable form of WorkspaceConfig (Map → array of entries).
89
+ */
90
+ export interface SerializedWorkspaceConfig {
91
+ mode: string;
92
+ repos: Array<[string, WorkspaceRepoConfig]>;
93
+ routing: WorkspaceConfig["routing"];
94
+ configPath: string;
95
+ }
96
+
97
+ /**
98
+ * workerData shape passed from the main thread.
99
+ */
100
+ export interface EngineWorkerData {
101
+ /** Sentinel flag — distinguishes engine worker from test-runner worker threads */
102
+ engineWorker: true;
103
+ /** "execute" for new batch, "resume" for resume */
104
+ mode: "execute" | "resume";
105
+ /** User arguments (target string) — only for "execute" mode */
106
+ args?: string;
107
+ /** Orchestrator configuration */
108
+ orchConfig: OrchestratorConfig;
109
+ /** Task runner configuration */
110
+ runnerConfig: TaskRunnerConfig;
111
+ /** Repository root (cwd) */
112
+ cwd: string;
113
+ /** Workspace configuration (serialized) — null for repo mode */
114
+ workspaceConfig?: SerializedWorkspaceConfig | null;
115
+ /** Workspace root directory */
116
+ workspaceRoot?: string;
117
+ /** Agent root directory */
118
+ agentRoot?: string;
119
+ /** Force flag for resume */
120
+ force?: boolean;
121
+ /** Supervisor autonomy mode propagated to worker bridge tools. */
122
+ supervisorAutonomy?: "interactive" | "supervised" | "autonomous";
123
+ }
124
+
125
+ // ── Serialization helpers (used by both main thread and worker) ──────
126
+
127
+ /**
128
+ * Serialize WorkspaceConfig for cross-thread transfer.
129
+ * Converts the Map to an array of entries.
130
+ */
131
+ export function serializeWorkspaceConfig(
132
+ config: WorkspaceConfig | null | undefined,
133
+ ): SerializedWorkspaceConfig | null {
134
+ if (!config) return null;
135
+ return {
136
+ mode: config.mode,
137
+ repos: [...config.repos.entries()],
138
+ routing: config.routing,
139
+ configPath: config.configPath,
140
+ };
141
+ }
142
+
143
+ /**
144
+ * Reconstruct WorkspaceConfig from serialized form.
145
+ */
146
+ export function deserializeWorkspaceConfig(
147
+ serialized: SerializedWorkspaceConfig | null | undefined,
148
+ ): WorkspaceConfig | null {
149
+ if (!serialized) return null;
150
+ return {
151
+ mode: serialized.mode as WorkspaceConfig["mode"],
152
+ repos: new Map(serialized.repos),
153
+ routing: serialized.routing,
154
+ configPath: serialized.configPath,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Extract serializable batch state for sync back to main thread.
160
+ */
161
+ function serializeBatchState(state: OrchBatchRuntimeState): SerializedBatchState {
162
+ return {
163
+ phase: state.phase,
164
+ batchId: state.batchId,
165
+ baseBranch: state.baseBranch,
166
+ orchBranch: state.orchBranch,
167
+ mode: state.mode,
168
+ currentWaveIndex: state.currentWaveIndex,
169
+ totalWaves: state.totalWaves,
170
+ totalTasks: state.totalTasks,
171
+ succeededTasks: state.succeededTasks,
172
+ failedTasks: state.failedTasks,
173
+ skippedTasks: state.skippedTasks,
174
+ blockedTasks: state.blockedTasks,
175
+ startedAt: state.startedAt,
176
+ endedAt: state.endedAt,
177
+ errors: [...state.errors],
178
+ currentLanes: state.currentLanes,
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Apply serialized batch state from worker to main-thread batch state.
184
+ *
185
+ * Updates only the fields that the worker thread tracks — preserves
186
+ * main-thread-only fields like pauseSignal, dependencyGraph, etc.
187
+ */
188
+ export function applySerializedState(
189
+ batchState: OrchBatchRuntimeState,
190
+ serialized: SerializedBatchState,
191
+ ): void {
192
+ batchState.phase = serialized.phase;
193
+ batchState.batchId = serialized.batchId;
194
+ batchState.baseBranch = serialized.baseBranch;
195
+ batchState.orchBranch = serialized.orchBranch;
196
+ batchState.mode = serialized.mode as OrchBatchRuntimeState["mode"];
197
+ batchState.currentWaveIndex = serialized.currentWaveIndex;
198
+ batchState.totalWaves = serialized.totalWaves;
199
+ batchState.totalTasks = serialized.totalTasks;
200
+ batchState.succeededTasks = serialized.succeededTasks;
201
+ batchState.failedTasks = serialized.failedTasks;
202
+ batchState.skippedTasks = serialized.skippedTasks;
203
+ batchState.blockedTasks = serialized.blockedTasks;
204
+ batchState.startedAt = serialized.startedAt;
205
+ batchState.endedAt = serialized.endedAt;
206
+ batchState.errors = [...serialized.errors];
207
+ batchState.currentLanes = serialized.currentLanes ?? [];
208
+ }
209
+
210
+ // ── Engine main (runs when launched as a forked child process) ───────
211
+
212
+ // Guard: only run engine main when launched via fork() with the sentinel env var.
213
+ if (process.env.TASKPLANE_ENGINE_FORK === "1" && typeof process.send === "function") {
214
+ const send = (msg: WorkerToMainMessage) => {
215
+ try {
216
+ process.send?.(msg);
217
+ } catch {
218
+ // best effort only
219
+ }
220
+ };
221
+
222
+ const sendWithAck = (msg: WorkerToMainMessage, onFlushed: () => void) => {
223
+ if (typeof process.send !== "function" || !process.connected) {
224
+ onFlushed();
225
+ return;
226
+ }
227
+
228
+ let flushed = false;
229
+ const done = () => {
230
+ if (flushed) return;
231
+ flushed = true;
232
+ onFlushed();
233
+ };
234
+
235
+ try {
236
+ (
237
+ process.send as (
238
+ message: WorkerToMainMessage,
239
+ sendHandle?: unknown,
240
+ options?: unknown,
241
+ callback?: (error: Error | null) => void,
242
+ ) => boolean
243
+ )(msg, undefined, undefined, () => done());
244
+ setTimeout(done, 75).unref();
245
+ } catch {
246
+ done();
247
+ }
248
+ };
249
+
250
+ const normalizeError = (err: unknown): { message: string; stack?: string } => {
251
+ if (err instanceof Error) return { message: err.message, stack: err.stack };
252
+ return { message: String(err) };
253
+ };
254
+
255
+ // Wait for the init message carrying workerData, then start the engine.
256
+ process.once("message", async (initMsg: { type: string; data: EngineWorkerData }) => {
257
+ if (initMsg?.type !== "init") return;
258
+
259
+ let batchState: OrchBatchRuntimeState | null = null;
260
+ let fatalHandled = false;
261
+ const reportFatalAndExit = (source: WorkerErrorSource, err: unknown) => {
262
+ if (fatalHandled) return;
263
+ fatalHandled = true;
264
+
265
+ const normalized = normalizeError(err);
266
+ if (batchState && batchState.phase !== "completed" && batchState.phase !== "failed") {
267
+ batchState.phase = "failed";
268
+ batchState.endedAt = Date.now();
269
+ batchState.errors.push(`[${source}] ${normalized.message}`);
270
+ }
271
+
272
+ if (batchState) send({ type: "state-sync", state: serializeBatchState(batchState) });
273
+ sendWithAck(
274
+ { type: "error", source, message: normalized.message, stack: normalized.stack },
275
+ () => process.exit(1),
276
+ );
277
+ setTimeout(() => process.exit(1), 200).unref();
278
+ };
279
+
280
+ process.once("uncaughtException", (err: unknown) => reportFatalAndExit("uncaughtException", err));
281
+ process.once("unhandledRejection", (reason: unknown) =>
282
+ reportFatalAndExit("unhandledRejection", reason),
283
+ );
284
+
285
+ // Dynamic imports — only loaded in engine context to avoid circular
286
+ // dependencies when this module is imported from extension.ts
287
+ const { executeOrchBatch } = await import("./engine.ts");
288
+ const { resumeOrchBatch } = await import("./resume.ts");
289
+ const { freshOrchBatchState } = await import("./types.ts");
290
+
291
+ const data = initMsg.data;
292
+
293
+ // Create a fresh batch state for this process
294
+ batchState = freshOrchBatchState();
295
+ batchState.phase = "launching";
296
+ batchState.startedAt = Date.now();
297
+
298
+ // Deserialize workspace config
299
+ const wsConfig = deserializeWorkspaceConfig(data.workspaceConfig);
300
+
301
+ // ── Control signal listener ──────────────────────────────────
302
+ // Main process sends pause/resume/abort signals via IPC.
303
+ // We apply them to the in-process batchState.pauseSignal.
304
+ process.on("message", (msg: WorkerInMessage) => {
305
+ if (!batchState) return;
306
+ switch (msg.type) {
307
+ case "pause":
308
+ batchState.pauseSignal.paused = true;
309
+ break;
310
+ case "resume":
311
+ batchState.pauseSignal.paused = false;
312
+ break;
313
+ case "abort":
314
+ batchState.pauseSignal.paused = true;
315
+ break;
316
+ }
317
+ });
318
+
319
+ // ── Callback factories (replace ctx-dependent callbacks) ─────
320
+ const onNotify = (message: string, level: "info" | "warning" | "error") => {
321
+ send({ type: "notify", msg: message, level });
322
+ if (!batchState) return;
323
+ // Sync batch state on every notify (lightweight — just the summary fields)
324
+ send({ type: "state-sync", state: serializeBatchState(batchState) });
325
+ };
326
+
327
+ const onMonitorUpdate = (state: MonitorState) => {
328
+ send({ type: "monitor-update", state });
329
+ };
330
+
331
+ const onEngineEvent = (event: EngineEvent) => {
332
+ send({ type: "engine-event", event });
333
+ };
334
+
335
+ // TP-076: Supervisor alert callback — sends structured alerts to main thread
336
+ const onSupervisorAlert = (alert: import("./types.ts").SupervisorAlert) => {
337
+ send({ type: "supervisor-alert", alert });
338
+ };
339
+
340
+ // TP-187 (#538): Lane termination callback — forwards lane-terminated to
341
+ // the supervisor process so it can suppress in-flight zombie alerts.
342
+ const onLaneTerminated = (info: import("./types.ts").LaneTerminatedInfo) => {
343
+ send({ type: "lane-terminated", info });
344
+ };
345
+
346
+ // TP-187 (#538): Lane respawn callback — forwards lane-respawned to
347
+ // the supervisor process so it can lift suppression for re-allocated lanes.
348
+ const onLaneRespawned = (laneNumber: number, agentId: string, batchId: string) => {
349
+ send({ type: "lane-respawned", laneNumber, agentId, batchId });
350
+ };
351
+
352
+ // ── Execute engine ───────────────────────────────────────────
353
+ const enginePromise =
354
+ data.mode === "resume"
355
+ ? resumeOrchBatch(
356
+ data.orchConfig,
357
+ data.runnerConfig,
358
+ data.cwd,
359
+ batchState,
360
+ onNotify,
361
+ onMonitorUpdate,
362
+ wsConfig,
363
+ data.workspaceRoot,
364
+ data.agentRoot,
365
+ data.force ?? false,
366
+ onSupervisorAlert,
367
+ data.supervisorAutonomy ?? "autonomous",
368
+ onLaneTerminated,
369
+ onLaneRespawned,
370
+ )
371
+ : executeOrchBatch(
372
+ data.args ?? "",
373
+ data.orchConfig,
374
+ data.runnerConfig,
375
+ data.cwd,
376
+ batchState,
377
+ onNotify,
378
+ onMonitorUpdate,
379
+ wsConfig,
380
+ data.workspaceRoot,
381
+ data.agentRoot,
382
+ onEngineEvent,
383
+ onSupervisorAlert,
384
+ data.supervisorAutonomy ?? "autonomous",
385
+ onLaneTerminated,
386
+ onLaneRespawned,
387
+ );
388
+
389
+ enginePromise
390
+ .then(() => {
391
+ // Final state sync + completion signal
392
+ const finalState = serializeBatchState(batchState);
393
+ send({ type: "complete", state: finalState });
394
+ // Disconnect IPC so the child process can exit cleanly
395
+ process.disconnect?.();
396
+ })
397
+ .catch((err: unknown) => {
398
+ const normalized = normalizeError(err);
399
+ // Ensure batch state reflects the failure
400
+ if (batchState.phase !== "completed" && batchState.phase !== "failed") {
401
+ batchState.phase = "failed";
402
+ batchState.endedAt = Date.now();
403
+ batchState.errors.push(`Unhandled engine error: ${normalized.message}`);
404
+ }
405
+ send({ type: "state-sync", state: serializeBatchState(batchState) });
406
+ send({
407
+ type: "error",
408
+ source: "enginePromise",
409
+ message: normalized.message,
410
+ stack: normalized.stack,
411
+ });
412
+ process.disconnect?.();
413
+ });
414
+ });
415
+ }