| 414 | }; |
| 415 | |
| 416 | export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecutionError>( |
| 417 | config: ExecutionEngineConfig<E>, |
| 418 | ): ExecutionEngine<E> => { |
| 419 | const { executor, codeExecutor, toolDiscoveryProvider = defaultToolDiscoveryProvider } = config; |
| 420 | const pausedExecutions = new Map<string, InternalPausedExecution<E>>(); |
| 421 | // Outcomes of executions that already settled (resumed to completion, hit a |
| 422 | // new pause, or died while paused). MCP clients retry `resume` when a |
| 423 | // response gets lost in transit; without this cache the retry of an |
| 424 | // already-delivered resume answers "no paused execution" (observed in |
| 425 | // production seconds after a successful resume). Bounded FIFO — pause |
| 426 | // volume is tiny (human approvals), so a small window is plenty. |
| 427 | const settledOutcomes = new Map<string, Exit.Exit<ExecutionResult, E>>(); |
| 428 | const SETTLED_OUTCOME_LIMIT = 64; |
| 429 | // Resumes whose outcome is still being computed, so a concurrent duplicate |
| 430 | // awaits the same result instead of missing the (already-consumed) pause. |
| 431 | const pendingResumes = new Map<string, Deferred.Deferred<ExecutionResult, E>>(); |
| 432 | |
| 433 | // Exits (not just successes) so a replayed failure re-fails through the |
| 434 | // typed channel — hosts render engine failures opaquely, and a replay must |
| 435 | // not bypass that by flattening the cause into result text. |
| 436 | const recordSettledOutcome = (executionId: string, exit: Exit.Exit<ExecutionResult, E>): void => { |
| 437 | settledOutcomes.set(executionId, exit); |
| 438 | while (settledOutcomes.size > SETTLED_OUTCOME_LIMIT) { |
| 439 | const oldest = settledOutcomes.keys().next().value; |
| 440 | if (oldest === undefined) break; |
| 441 | settledOutcomes.delete(oldest); |
| 442 | } |
| 443 | }; |
| 444 | |
| 445 | /** |
| 446 | * Race a running fiber against the pause queue. Returns when either |
| 447 | * the fiber completes or an elicitation handler fires (whichever |
| 448 | * comes first). Re-used by both executeWithPause and resume. |
| 449 | * |
| 450 | * `Effect.raceFirst` (not `Effect.race`) — `race` has prefer-success |
| 451 | * semantics in Effect v4 ("first successful result"), which means a |
| 452 | * fiber failure waits indefinitely for the pause Deferred to succeed. |
| 453 | * For a fast `codeExecutor.execute` failure (e.g. a syntax error |
| 454 | * inside the dynamic worker) the pause signal never fires, so the |
| 455 | * outer Effect hangs until the upstream client gives up. `raceFirst` |
| 456 | * settles on whichever side completes first, success or failure. |
| 457 | */ |
| 458 | const awaitCompletionOrPause = ( |
| 459 | fiber: Fiber.Fiber<ExecuteResult, E>, |
| 460 | pauseQueue: Queue.Queue<InternalPausedExecution<E>>, |
| 461 | ): Effect.Effect<ExecutionResult, E> => |
| 462 | Effect.raceFirst( |
| 463 | Fiber.join(fiber).pipe( |
| 464 | Effect.map((result): ExecutionResult => ({ status: "completed", result })), |
| 465 | ), |
| 466 | Queue.take(pauseQueue).pipe( |
| 467 | Effect.map((paused): ExecutionResult => ({ status: "paused", execution: paused })), |
| 468 | ), |
| 469 | ); |
| 470 | |
| 471 | /** |
| 472 | * Start an execution in pause/resume mode. |
| 473 | * |