@@ -22,6 +22,26 @@ import type { AdmissionQueue, AdmissionStatus } from "./admission.js";
2222
2323const log = getLogger ( [ LOG_NAMESPACE_ROOT , "subagent" , "session-store" ] ) ;
2424
25+ async function invokeCloseBounded (
26+ close : ( deadlineMs ?: number ) => Promise < void > ,
27+ deadlineMs : number ,
28+ ) : Promise < void > {
29+ let closeError : unknown ;
30+ await Promise . race ( [
31+ close ( deadlineMs ) . then (
32+ ( ) => undefined ,
33+ ( err : unknown ) => {
34+ closeError = err ;
35+ log . warn ( "session close raced deadline: {error}" , {
36+ error : err instanceof Error ? err . message : String ( err ) ,
37+ } ) ;
38+ } ,
39+ ) ,
40+ new Promise < void > ( ( resolve ) => setTimeout ( resolve , deadlineMs ) ) ,
41+ ] ) ;
42+ if ( closeError !== undefined ) throw closeError ;
43+ }
44+
2545export type SubAgentSessionStatus = "running" | "done" | "failed" | "cancelled" ;
2646
2747/**
@@ -194,8 +214,10 @@ export interface SubAgentSessionStore {
194214 // Abort a running session and mark it cancelled. Returns true when a running
195215 // session was cancelled; false if missing or already terminal.
196216 cancel ( id : string , reason ?: string ) : boolean ;
197- // Cancel every running session. Returns the ids that transitioned.
198- cancelAll ( reason ?: string ) : string [ ] ;
217+ // Cancel every running session. Closes retained workers with the same
218+ // deadline race as closeOne: leftover-child throws reject, hang-forever
219+ // resolves without throwing. Returns the ids that transitioned to cancelled.
220+ cancelAll ( reason ?: string ) : Promise < string [ ] > ;
199221 // CL-6943: flips a "pending_init" session to "running" once its agent
200222 // object actually exists. No-op on an unknown id or one already past init.
201223 markRunning ( id : string ) : void ;
@@ -1219,18 +1241,11 @@ export function createSubAgentSessionStore(
12191241 // close that does not honor its own deadline argument — a wedged
12201242 // descendant must not hang the whole close_agent call.
12211243 let closeError : unknown ;
1222- await Promise . race ( [
1223- close ( deadlineMs ) . then (
1224- ( ) => undefined ,
1225- ( err : unknown ) => {
1226- closeError = err ;
1227- log . warn ( "session close raced deadline: {error}" , {
1228- error : err instanceof Error ? err . message : String ( err ) ,
1229- } ) ;
1230- } ,
1231- ) ,
1232- new Promise < void > ( ( resolve ) => setTimeout ( resolve , deadlineMs ) ) ,
1233- ] ) ;
1244+ try {
1245+ await invokeCloseBounded ( close , deadlineMs ) ;
1246+ } catch ( err : unknown ) {
1247+ closeError = err ;
1248+ }
12341249 if ( keepFailed ) {
12351250 // fail() already stamped failed; invoke leftover teardown without
12361251 // rewriting that to shutdown.
@@ -1465,7 +1480,7 @@ export function createSubAgentSessionStore(
14651480 return cancelSession ( id , reason ) ;
14661481 } ,
14671482
1468- cancelAll ( reason = DEFAULT_CANCEL_REASON ) : string [ ] {
1483+ async cancelAll ( reason = DEFAULT_CANCEL_REASON ) : Promise < string [ ] > {
14691484 // Snapshot before cancelSession: markCancelled clears retained, and a
14701485 // resumed retained worker is strip-live so the first loop would otherwise
14711486 // skip the close-handle pass (CL-7001).
@@ -1477,10 +1492,17 @@ export function createSubAgentSessionStore(
14771492 for ( const session of running ) {
14781493 if ( cancelSession ( session . id , reason ) ) cancelled . push ( session . id ) ;
14791494 }
1495+ const pendingCloses : Promise < void > [ ] = [ ] ;
14801496 for ( const id of retainedIds ) {
14811497 const session = sessions . get ( id ) ;
14821498 if ( session === undefined || session . lifecycle . state === "shutdown" ) continue ;
1483- releaseHandles ( id ) ;
1499+ const close = closeHandles . get ( id ) ;
1500+ cancelAskInternal ( id , "session handles released" ) ;
1501+ closeHandles . delete ( id ) ;
1502+ cancelHandles . delete ( id ) ;
1503+ interruptHandles . delete ( id ) ;
1504+ followupHandles . delete ( id ) ;
1505+ deliverHandles . delete ( id ) ;
14841506 mutate ( id , ( s ) => {
14851507 s . lifecycle = {
14861508 state : "shutdown" ,
@@ -1489,6 +1511,15 @@ export function createSubAgentSessionStore(
14891511 } ;
14901512 s . retained = false ;
14911513 } ) ;
1514+ if ( close !== undefined ) {
1515+ pendingCloses . push ( invokeCloseBounded ( close , DEFAULT_CLOSE_DEADLINE_MS ) ) ;
1516+ }
1517+ }
1518+ const results = await Promise . allSettled ( pendingCloses ) ;
1519+ const failures = results . flatMap ( ( r ) => ( r . status === "rejected" ? [ r . reason ] : [ ] ) ) ;
1520+ if ( failures . length === 1 ) throw failures [ 0 ] ;
1521+ if ( failures . length > 1 ) {
1522+ throw new AggregateError ( failures , "session cancelAll close failed" ) ;
14921523 }
14931524 return cancelled ;
14941525 } ,
0 commit comments