@@ -73,7 +73,7 @@ export interface EventSinkV2Shape {
7373 OrchestrationV2ProviderThread ,
7474 "driver" | "providerInstanceId" | "appThreadId" | "nativeThreadRef"
7575 > ;
76- readonly guardPendingUserInputCancellations ?: boolean ;
76+ readonly guardPendingRuntimeRequestCancellations ?: boolean ;
7777 readonly commandId ?: CommandId ;
7878 readonly events : ReadonlyArray < OrchestrationV2DomainEvent > ;
7979 } ) => Effect . Effect < ReadonlyArray < OrchestrationV2StoredEvent > , EventSinkV2Error > ;
@@ -87,13 +87,13 @@ export interface EventSinkV2Shape {
8787 OrchestrationV2ProviderThread ,
8888 "driver" | "providerInstanceId" | "appThreadId" | "nativeThreadRef"
8989 > ;
90- readonly guardPendingUserInputCancellations ?: boolean ;
90+ readonly guardPendingRuntimeRequestCancellations ?: boolean ;
9191 readonly commandId ?: CommandId ;
9292 readonly events : ReadonlyArray < OrchestrationV2DomainEvent > ;
9393 readonly effects : ReadonlyArray < EffectOutbox . PendingOrchestrationEffectV2 > ;
9494 } ) => Effect . Effect < ReadonlyArray < OrchestrationV2StoredEvent > , EventSinkV2Error > ;
9595 readonly writeIfRunCurrent : ( input : {
96- readonly guardPendingUserInputCancellations ?: boolean ;
96+ readonly guardPendingRuntimeRequestCancellations ?: boolean ;
9797 readonly commandId ?: CommandId ;
9898 readonly threadId : ThreadId ;
9999 readonly runId : RunId ;
@@ -110,7 +110,7 @@ export interface EventSinkV2Shape {
110110 > ;
111111 /** Commits only if the expected attempt and ordinal still own the provider thread, protecting newer attempts. */
112112 readonly writeIfProviderThreadOwner : ( input : {
113- readonly guardPendingUserInputCancellations ?: boolean ;
113+ readonly guardPendingRuntimeRequestCancellations ?: boolean ;
114114 readonly commandId ?: CommandId ;
115115 readonly providerThreadId : ProviderThreadId ;
116116 readonly runId : RunId ;
@@ -278,29 +278,53 @@ const layerBase: Layer.Layer<
278278 ) ;
279279 } ) ;
280280
281- // A user can answer after terminal normalization reads the pending request.
281+ // A user can answer after terminal normalization reads the pending request, and an approval
282+ // answer commits before its outbox delivery reaches a provider that may withdraw the request.
282283 // Recheck inside the write transaction so stale cleanup cannot erase answers.
283- const guardUserInputCancellations =
284+ const guardRuntimeRequestCancellations =
284285 /* This helper runs for individual orchestration events; the enclosing operation owns the span. */
285286 Effect . fnUntraced ( function * ( events : ReadonlyArray < OrchestrationV2DomainEvent > ) {
286287 const staleRequests = new Set < RuntimeRequestId > ( ) ;
287288 const staleNodes = new Set < NodeId > ( ) ;
288289 for ( const event of events ) {
290+ // Providers can withdraw the node and item in writes of their own, so check each against its request.
289291 if (
290- event . type !== "runtime-request.updated" ||
291- event . payload . kind !== "user_input" ||
292- event . payload . status !== "cancelled"
293- )
292+ event . type === "node.updated" &&
293+ event . payload . status === "cancelled" &&
294+ event . payload . runtimeRequestId !== null
295+ ) {
296+ const current = yield * projectionStore . getRuntimeRequest (
297+ event . threadId ,
298+ event . payload . runtimeRequestId ,
299+ ) ;
300+ if ( current ?. status === "resolved" ) staleNodes . add ( event . payload . id ) ;
301+ continue ;
302+ }
303+ if (
304+ event . type === "turn-item.updated" &&
305+ ( event . payload . type === "user_input_request" ||
306+ event . payload . type === "approval_request" ) &&
307+ event . payload . status === "cancelled"
308+ ) {
309+ const current = yield * projectionStore . getRuntimeRequest (
310+ event . threadId ,
311+ event . payload . requestId ,
312+ ) ;
313+ if ( current ?. status === "resolved" ) staleRequests . add ( event . payload . requestId ) ;
314+ continue ;
315+ }
316+ if ( event . type !== "runtime-request.updated" || event . payload . status !== "cancelled" )
294317 continue ;
295318 const current = yield * projectionStore . getRuntimeRequest (
296319 event . threadId ,
297320 event . payload . id ,
298321 ) ;
299322 if (
300323 current ?. status !== "pending" ||
301- current . kind !== "user_input" ||
302- current . providerTurnId !== event . payload . providerTurnId ||
303- current . responseCapability . type === "message"
324+ ( event . payload . kind === "user_input" &&
325+ ( current . kind !== "user_input" ||
326+ current . providerTurnId !== event . payload . providerTurnId ||
327+ current . responseCapability . type === "message" ) )
304328 ) {
305329 staleRequests . add ( event . payload . id ) ;
306330 staleNodes . add ( event . payload . nodeId ) ;
@@ -314,7 +338,8 @@ const layerBase: Layer.Layer<
314338 return event . payload . status !== "cancelled" || ! staleNodes . has ( event . payload . id ) ;
315339 case "turn-item.updated" :
316340 return (
317- event . payload . type !== "user_input_request" ||
341+ ( event . payload . type !== "user_input_request" &&
342+ event . payload . type !== "approval_request" ) ||
318343 event . payload . status !== "cancelled" ||
319344 ! staleRequests . has ( event . payload . requestId )
320345 ) ;
@@ -430,8 +455,8 @@ const layerBase: Layer.Layer<
430455 )
431456 : input . events ;
432457 const normalized = yield * normalizeEvents (
433- input . guardPendingUserInputCancellations === true
434- ? yield * guardUserInputCancellations ( events )
458+ input . guardPendingRuntimeRequestCancellations === true
459+ ? yield * guardRuntimeRequestCancellations ( events )
435460 : events ,
436461 ) ;
437462 const committed = yield * eventStore . append ( {
@@ -489,8 +514,8 @@ const layerBase: Layer.Layer<
489514 }
490515
491516 const normalized = yield * normalizeEvents (
492- input . guardPendingUserInputCancellations === true
493- ? yield * guardUserInputCancellations ( input . events )
517+ input . guardPendingRuntimeRequestCancellations === true
518+ ? yield * guardRuntimeRequestCancellations ( input . events )
494519 : input . events ,
495520 ) ;
496521 const storedEvents = yield * eventStore . append ( {
@@ -554,8 +579,8 @@ const layerBase: Layer.Layer<
554579 }
555580
556581 const normalized = yield * normalizeEvents (
557- input . guardPendingUserInputCancellations === true
558- ? yield * guardUserInputCancellations ( input . events )
582+ input . guardPendingRuntimeRequestCancellations === true
583+ ? yield * guardRuntimeRequestCancellations ( input . events )
559584 : input . events ,
560585 ) ;
561586 const storedEvents = yield * eventStore . append ( {
0 commit comments