import type { ContextOperationRequest } from '../core/context.ts'; import { type WorkerLoggerReplayState } from '../core/context/workflow-logger.ts'; import type { OperationOutcome, WorkerOutboundMessage, WorkflowContext, WorkflowLogger, WorkflowLogRecord, WorkflowStateNamespace } from '../core/types.ts'; import { type WorkerReplayState } from './worker-replay-state.ts'; /** * Subset of {@link WorkflowContext} that the worker-side runner can build * locally from the `run` message. Engine-side fields (`executionTimeRemaining` * in particular) are stub values because the worker has no clock authority — * any user code reading them will see static numbers, not live deadlines. */ export type WorkerWorkflowContext = Pick & { readonly state: WorkflowStateNamespace; readonly log: WorkflowLogger; getVersion(changeId: string, minSupported: number, maxSupported: number): Generator; }; interface RunMessageShape { workflowId: string; workflowExecutionToken?: string; workflowType: string; input: unknown; executionStateOwnerId?: string; deadline?: number; headers?: [string, string][]; } /** * Construct the worker-side `ctx` argument that gets passed as the first * positional parameter to a registered workflow handler. Engine-side fields * not represented in the `run` message are intentionally omitted — only the * `Pick`-ed subset above is populated. `forwardLog`, when provided by the worker * entry (host has an `onLog` sink), routes `ctx.log` records to the host (#529). */ export declare function createWorkerWorkflowContext(message: RunMessageShape, controller: AbortController, getReplayState: () => WorkerLoggerReplayState | undefined, forwardLog?: (record: WorkflowLogRecord) => void, getVersionReplayState?: () => WorkerReplayState | undefined): WorkerWorkflowContext; /** * Posts a forwarded `ctx.log` message to the host, size-checking against the current * `maxProtocolMessageBytes`. Throwing on oversize is intentional — the shared logger * factory catches it and falls the record back to the worker console (#529). */ export type WorkerLogPoster = (message: Extract, maxProtocolMessageBytes: number | undefined) => void; export interface WorkflowRunnerContext { generators: Map; abortControllers: Map; replayStates: Map; /** * The revision (WFT-20) captured from the `run` message that started this * workflow, keyed by workflow id — a `resume` inbound message never * re-sends it, so `workflow-worker-entry.ts`'s `attachWorkerProtocol` reads * it from here to stamp every outbound message for the workflow's * remaining turns. */ workflowRevisions: Map; } export declare function createWorkflowRunnerContext(): WorkflowRunnerContext; export declare function handleRunMessage(context: WorkflowRunnerContext, message: { workflowId: string; workflowType: string; input: unknown; checkpoint?: ArrayBuffer; maxProtocolMessageBytes?: number; executionStateOwnerId?: string; deadline?: number; headers?: [string, string][]; workflowRevision?: string; }, getWorkflowHandler: (type: string) => ((ctx: WorkerWorkflowContext, input: unknown) => AsyncGenerator) | undefined, postLog?: WorkerLogPoster): Promise; export declare function handleResumeMessage(context: WorkflowRunnerContext, message: { workflowId: string; result: unknown; operationResult?: OperationOutcome; maxProtocolMessageBytes?: number; }): Promise; /** * Handle a `cancel` message: abort the workflow's {@link AbortController}, * run the generator's `finally` blocks by calling `generator.return()`, and * tear down the runner's in-memory state. The `return()` call is wrapped in a * try/catch because a well-behaved workflow's `finally` block may still throw * on cancellation (e.g. a `using` disposer), and we must never let that * prevent the rest of cleanup from running. * * The function is async (it awaits `generator.return()` so the workflow's * `finally` blocks actually complete before cleanup), which opens a narrow * race window: while awaiting, the worker message loop can process another * message for the same workflow ID — most dangerously a `run` that installs * a brand-new generator and controller into the context maps. We must not * clobber that state when this cancel handler resumes. The cleanup below * therefore only deletes the cached entries if they still point at the * *same* generator/controller we captured before the await. */ export declare function handleCancelMessage(context: WorkflowRunnerContext, message: { workflowId: string; }): Promise; export declare function cleanupWorkflowRunnerState(context: WorkflowRunnerContext, workflowId: string): void; export {};