| 465 | }; |
| 466 | |
| 467 | export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecutionError>( |
| 468 | config: ExecutionEngineConfig<E>, |
| 469 | ): ExecutionEngine<E> => { |
| 470 | const { executor, codeExecutor, toolDiscoveryProvider = defaultToolDiscoveryProvider } = config; |
| 471 | const pausedExecutions = new Map<string, InternalPausedExecution<E>>(); |
| 472 | // Outcomes of executions that already settled (resumed to completion, hit a |
| 473 | // new pause, or died while paused). MCP clients retry `resume` when a |
| 474 | // response gets lost in transit; without this cache the retry of an |
| 475 | // already-delivered resume answers "no paused execution" (observed in |
| 476 | // production seconds after a successful resume). Bounded FIFO — pause |
| 477 | // volume is tiny (human approvals), so a small window is plenty. |
| 478 | const settledOutcomes = new Map<string, Exit.Exit<ExecutionResult, E>>(); |
| 479 | const SETTLED_OUTCOME_LIMIT = 64; |
| 480 | const settledExecutionIds = new Set<string>(); |
| 481 | const SETTLED_EXECUTION_ID_LIMIT = 1024; |
| 482 | // Resumes whose outcome is still being computed, so a concurrent duplicate |
| 483 | // awaits the same result instead of missing the (already-consumed) pause. |
| 484 | const pendingResumes = new Map<string, Deferred.Deferred<ExecutionResult, E>>(); |
| 485 | |
| 486 | // Exits (not just successes) so a replayed failure re-fails through the |
| 487 | // typed channel — hosts render engine failures opaquely, and a replay must |
| 488 | // not bypass that by flattening the cause into result text. |
| 489 | const recordSettledOutcome = (executionId: string, exit: Exit.Exit<ExecutionResult, E>): void => { |
| 490 | settledExecutionIds.add(executionId); |
| 491 | while (settledExecutionIds.size > SETTLED_EXECUTION_ID_LIMIT) { |
| 492 | const oldest = settledExecutionIds.keys().next().value; |
| 493 | if (oldest === undefined) break; |
| 494 | settledExecutionIds.delete(oldest); |
| 495 | } |
| 496 | settledOutcomes.set(executionId, exit); |
| 497 | while (settledOutcomes.size > SETTLED_OUTCOME_LIMIT) { |
| 498 | const oldest = settledOutcomes.keys().next().value; |
| 499 | if (oldest === undefined) break; |
| 500 | settledOutcomes.delete(oldest); |
| 501 | } |
| 502 | }; |
| 503 | |
| 504 | /** |
| 505 | * Race a running fiber against the pause queue. Returns when either |
| 506 | * the fiber completes or an elicitation handler fires (whichever |
| 507 | * comes first). Re-used by both executeWithPause and resume. |
| 508 | * |
| 509 | * `Effect.raceFirst` (not `Effect.race`) — `race` has prefer-success |
| 510 | * semantics in Effect v4 ("first successful result"), which means a |
| 511 | * fiber failure waits indefinitely for the pause Deferred to succeed. |
| 512 | * For a fast `codeExecutor.execute` failure (e.g. a syntax error |
| 513 | * inside the dynamic worker) the pause signal never fires, so the |
| 514 | * outer Effect hangs until the upstream client gives up. `raceFirst` |
| 515 | * settles on whichever side completes first, success or failure. |
| 516 | */ |
| 517 | const awaitCompletionOrPause = ( |
| 518 | fiber: Fiber.Fiber<ExecuteResult, E>, |
| 519 | pauseQueue: Queue.Queue<InternalPausedExecution<E>>, |
| 520 | ): Effect.Effect<ExecutionResult, E> => |
| 521 | Effect.raceFirst( |
| 522 | Fiber.join(fiber).pipe( |
| 523 | Effect.map((result): ExecutionResult => ({ status: "completed", result })), |
| 524 | ), |