(
remote: RemoteExecHandle,
known: { id?: string; backend?: string } = {},
)
| 68 | // local handle. Both paths wait for command post-exit synchronization before |
| 69 | // completing; result() also returns its synchronization counts. |
| 70 | async function rebuildExecHandle<E extends ExecEncoding>( |
| 71 | remote: RemoteExecHandle, |
| 72 | known: { id?: string; backend?: string } = {}, |
| 73 | ): Promise<WorkspaceRuntimeExecHandle<E>> { |
| 74 | const [id, backend] = await Promise.all([known.id ?? remote.id, known.backend ?? remote.backend]); |
| 75 | let claimed: "result" | "stream" | undefined; |
| 76 | let resultPromise: Promise<WorkspaceRuntimeResult<E>> | undefined; |
| 77 | let reader: ReadableStreamDefaultReader<WorkspaceRuntimeEvent<E>> | undefined; |
| 78 | const stream = new ReadableStream<WorkspaceRuntimeEvent<E>>( |
| 79 | { |
| 80 | // Lazy: don't call remote.stream() until the consumer actually |
| 81 | // pulls. A result()-only caller never starts the stream, so the |
| 82 | // stub's single handle is free for its run-and-wait path. |
| 83 | pull: async (controller) => { |
| 84 | if (claimed === "result") { |
| 85 | controller.error( |
| 86 | new Error( |
| 87 | "exec handle already consumed by result(): result() and streaming are exclusive", |
| 88 | ), |
| 89 | ); |
| 90 | return; |
| 91 | } |
| 92 | if (reader === undefined) { |
| 93 | claimed = "stream"; |
| 94 | const encoded = await remote.stream(); |
| 95 | reader = decodeRuntimeEvents(encoded).getReader() as ReadableStreamDefaultReader< |
| 96 | WorkspaceRuntimeEvent<E> |
| 97 | >; |
| 98 | } |
| 99 | try { |
| 100 | const activeReader = reader; |
| 101 | if (!activeReader) throw new Error("runtime stream reader was not initialized"); |
| 102 | const { value, done } = await activeReader.read(); |
| 103 | if (done) { |
| 104 | activeReader.releaseLock(); |
| 105 | reader = undefined; |
| 106 | controller.close(); |
| 107 | return; |
| 108 | } |
| 109 | controller.enqueue(value); |
| 110 | } catch (error) { |
| 111 | reader?.releaseLock(); |
| 112 | reader = undefined; |
| 113 | controller.error(error); |
| 114 | } |
| 115 | }, |
| 116 | cancel: async (reason) => { |
| 117 | const activeReader = reader; |
| 118 | reader = undefined; |
| 119 | if (!activeReader) { |
| 120 | dispose(); |
| 121 | return; |
| 122 | } |
| 123 | try { |
| 124 | await activeReader.cancel(reason); |
| 125 | } finally { |
| 126 | activeReader.releaseLock(); |
| 127 | dispose(); |
no test coverage detected