11import { resolveSpawnCommand } from "@t3tools/shared/shell" ;
2+ import * as Cause from "effect/Cause" ;
23import * as Deferred from "effect/Deferred" ;
34import * as Effect from "effect/Effect" ;
5+ import * as Exit from "effect/Exit" ;
46import * as Option from "effect/Option" ;
57import * as Queue from "effect/Queue" ;
68import * as Ref from "effect/Ref" ;
@@ -35,6 +37,23 @@ export class PiSessionRuntimeError extends Schema.TaggedErrorClass<PiSessionRunt
3537
3638const isPiSessionRuntimeError = Schema . is ( PiSessionRuntimeError ) ;
3739
40+ /** Synthetic raw event emitted when Pi's process or RPC transport becomes unusable. */
41+ export const PI_RPC_TRANSPORT_FAILURE_EVENT_TYPE = "pi_rpc_transport_failure" ;
42+
43+ export interface PiRpcTransportFailureEvent {
44+ readonly type : typeof PI_RPC_TRANSPORT_FAILURE_EVENT_TYPE ;
45+ readonly operation : string ;
46+ readonly detail : string ;
47+ }
48+
49+ export function isPiSessionRuntimeTransportError ( error : PiSessionRuntimeError ) : boolean {
50+ return (
51+ error . operation === "read-stdout" ||
52+ error . operation === "process-exit" ||
53+ error . operation === "write-stdin"
54+ ) ;
55+ }
56+
3857export interface PiSessionRuntimeOptions {
3958 readonly binaryPath : string ;
4059 readonly configDirectory : string ;
@@ -160,6 +179,15 @@ function stringValue(value: unknown): string | undefined {
160179 return typeof value === "string" && value . trim ( ) . length > 0 ? value . trim ( ) : undefined ;
161180}
162181
182+ export function isPiRpcTransportFailureEvent ( value : unknown ) : value is PiRpcTransportFailureEvent {
183+ return (
184+ isRecord ( value ) &&
185+ value . type === PI_RPC_TRANSPORT_FAILURE_EVENT_TYPE &&
186+ stringValue ( value . operation ) !== undefined &&
187+ stringValue ( value . detail ) !== undefined
188+ ) ;
189+ }
190+
163191function parseModel ( value : unknown ) : PiRpcModel | undefined {
164192 if ( ! isRecord ( value ) ) {
165193 return undefined ;
@@ -288,7 +316,7 @@ export const makePiSessionRuntime = (
288316 const pendingRequests = yield * Ref . make ( new Map < string , PendingRequest > ( ) ) ;
289317 const requestNumber = yield * Ref . make ( 0 ) ;
290318 const writeLock = yield * Semaphore . make ( 1 ) ;
291- const rawEvents = yield * Queue . unbounded < unknown > ( ) ;
319+ const rawEvents = yield * Queue . unbounded < unknown , Cause . Done > ( ) ;
292320 const closed = yield * Ref . make ( false ) ;
293321 const decoder = makePiJsonlDecoder ( ) ;
294322
@@ -303,6 +331,26 @@ export const makePiSessionRuntime = (
303331 ) ,
304332 ) ;
305333
334+ const finishUnexpectedTransportFailure = Effect . fn (
335+ "PiSessionRuntime.finishUnexpectedTransportFailure" ,
336+ ) ( function * ( error : PiSessionRuntimeError ) {
337+ if ( yield * Ref . getAndSet ( closed , true ) ) {
338+ return ;
339+ }
340+
341+ yield * Queue . offer ( rawEvents , {
342+ type : PI_RPC_TRANSPORT_FAILURE_EVENT_TYPE ,
343+ operation : error . operation ,
344+ detail : error . detail ,
345+ } satisfies PiRpcTransportFailureEvent ) ;
346+ yield * failPendingRequests ( error ) ;
347+ yield * child . kill ( { forceKillAfter : PI_RPC_FORCE_KILL_AFTER } ) . pipe ( Effect . ignore ) ;
348+ // `end` drains the failure event before completing the event stream.
349+ // `shutdown` would interrupt the adapter before it could mark an active
350+ // T3 turn interrupted and retain the diagnostic payload.
351+ yield * Queue . end ( rawEvents ) ;
352+ } ) ;
353+
306354 const removePendingRequest = ( id : string ) =>
307355 Ref . update ( pendingRequests , ( pending ) => {
308356 if ( ! pending . has ( id ) ) {
@@ -370,12 +418,19 @@ export const makePiSessionRuntime = (
370418 Effect . forEach ( decoder . push ( chunk ) , handleRecord , { discard : true } ) . pipe ( Effect . asVoid ) ,
371419 ) ,
372420 Effect . ensuring ( flushDecoder ( ) ) ,
373- Effect . catch ( ( ) =>
374- failPendingRequests (
375- new PiSessionRuntimeError ( {
376- operation : "read-stdout" ,
377- detail : "Pi RPC stdout closed unexpectedly." ,
378- } ) ,
421+ Effect . exit ,
422+ Effect . flatMap ( ( exit ) =>
423+ finishUnexpectedTransportFailure (
424+ Exit . isSuccess ( exit )
425+ ? new PiSessionRuntimeError ( {
426+ operation : "read-stdout" ,
427+ detail : "Pi RPC stdout closed unexpectedly." ,
428+ } )
429+ : new PiSessionRuntimeError ( {
430+ operation : "read-stdout" ,
431+ detail : "Pi RPC stdout connection failed." ,
432+ cause : exit . cause ,
433+ } ) ,
379434 ) ,
380435 ) ,
381436 Effect . forkIn ( runtimeScope ) ,
@@ -388,15 +443,15 @@ export const makePiSessionRuntime = (
388443 yield * child . exitCode . pipe (
389444 Effect . matchEffect ( {
390445 onFailure : ( cause ) =>
391- failPendingRequests (
446+ finishUnexpectedTransportFailure (
392447 new PiSessionRuntimeError ( {
393448 operation : "process-exit" ,
394449 detail : "Could not read Pi process exit status." ,
395450 cause,
396451 } ) ,
397452 ) ,
398453 onSuccess : ( code ) =>
399- failPendingRequests (
454+ finishUnexpectedTransportFailure (
400455 new PiSessionRuntimeError ( {
401456 operation : "process-exit" ,
402457 detail :
@@ -406,7 +461,6 @@ export const makePiSessionRuntime = (
406461 } ) ,
407462 ) ,
408463 } ) ,
409- Effect . andThen ( Queue . shutdown ( rawEvents ) ) ,
410464 Effect . forkIn ( runtimeScope ) ,
411465 ) ;
412466
@@ -434,6 +488,9 @@ export const makePiSessionRuntime = (
434488 cause,
435489 } ) ,
436490 ) ,
491+ Effect . tapError ( ( error ) =>
492+ error . operation === "write-stdin" ? finishUnexpectedTransportFailure ( error ) : Effect . void ,
493+ ) ,
437494 ) ;
438495
439496 const request = Effect . fn ( "PiSessionRuntime.request" ) ( function * (
@@ -480,7 +537,9 @@ export const makePiSessionRuntime = (
480537 return response ;
481538 } ) ;
482539
483- const getState = ( timeout = PI_RPC_REQUEST_TIMEOUT ) =>
540+ const getState = (
541+ timeout : typeof PI_RPC_REQUEST_TIMEOUT | typeof PI_RPC_START_TIMEOUT = PI_RPC_REQUEST_TIMEOUT ,
542+ ) =>
484543 request ( { type : "get_state" } , timeout ) . pipe (
485544 Effect . flatMap ( ( response ) => {
486545 const state = parseState ( response ) ;
0 commit comments