@@ -481,6 +481,7 @@ function layerTest(input: {
481481 readonly armed : Ref . Ref < boolean > ;
482482 readonly paused : Deferred . Deferred < void > ;
483483 } ;
484+ readonly afterMcpSet ?: Ref . Ref < Effect . Effect < void > > ;
484485 readonly hasPendingBackgroundWork ?: Effect . Effect < boolean > ;
485486 readonly hasPendingBackgroundWorkForThread ?: Effect . Effect < boolean > ;
486487 readonly backgroundTasksEndWithProcess ?: boolean ;
@@ -534,6 +535,22 @@ function layerTest(input: {
534535 input . pauseResolve === undefined
535536 ? layerTestMcpRegistry
536537 : layerPausingMcpRegistry ( input . pauseResolve ) ;
538+ const layerMcpSessions =
539+ input . afterMcpSet === undefined
540+ ? McpProviderSessions . layer
541+ : Layer . effect (
542+ McpProviderSessions . McpProviderSessions ,
543+ Effect . gen ( function * ( ) {
544+ const delegate = yield * McpProviderSessions . McpProviderSessions ;
545+ return McpProviderSessions . McpProviderSessions . of ( {
546+ ...delegate ,
547+ set : ( config ) =>
548+ delegate
549+ . set ( config )
550+ . pipe ( Effect . andThen ( Ref . get ( input . afterMcpSet ! ) ) , Effect . flatten ) ,
551+ } ) ;
552+ } ) ,
553+ ) . pipe ( Layer . provide ( McpProviderSessions . layer ) ) ;
537554 const layerProviderEventIngestorTest = ProviderEventIngestor . layer . pipe (
538555 Layer . provide (
539556 Layer . mergeAll (
@@ -556,14 +573,14 @@ function layerTest(input: {
556573 layerConfiguredEventSink ,
557574 IdAllocator . layer ,
558575 layerConfiguredMcpRegistry ,
559- McpProviderSessions . layer ,
576+ layerMcpSessions ,
560577 ProviderSessionManager . layerWithOptions ( {
561578 idleTimeoutMs : input . idleTimeoutMs ,
562579 ...( input . maxIdlePinMs === undefined ? { } : { maxIdlePinMs : input . maxIdlePinMs } ) ,
563580 } ) . pipe (
564581 Layer . provide (
565582 Layer . mergeAll (
566- layerRegistry . pipe ( Layer . provideMerge ( McpProviderSessions . layer ) ) ,
583+ layerRegistry . pipe ( Layer . provideMerge ( layerMcpSessions ) ) ,
567584 CommandOutputHub . layer . pipe (
568585 Layer . provide ( Layer . merge ( layerConfiguredEventSink , layerTestStores ) ) ,
569586 ) ,
@@ -2281,11 +2298,12 @@ it.effect(
22812298 } ) ,
22822299) ;
22832300
2284- it . effect (
2285- "ProviderSessionManagerV2 takes the thread's MCP credential back when a run returns to a still-attached session" ,
2286- ( ) =>
2301+ it . effect . each ( [ false , true ] ) (
2302+ "ProviderSessionManagerV2 takes the thread's MCP credential back when a run returns to a still-attached session, interrupted=%s " ,
2303+ ( interrupted ) =>
22872304 Effect . gen ( function * ( ) {
22882305 const state = yield * Ref . make ( emptyState ) ;
2306+ const afterMcpSet = yield * Ref . make < Effect . Effect < void > > ( Effect . void ) ;
22892307 const effect = Effect . gen ( function * ( ) {
22902308 const eventSink = yield * EventSink . EventSinkV2 ;
22912309 const idAllocator = yield * IdAllocator . IdAllocatorV2 ;
@@ -2304,7 +2322,31 @@ it.effect(
23042322 yield * eventSink . write ( {
23052323 events : [ yield * makeThreadCreatedEvent ( { idAllocator, threadId, now } ) ] ,
23062324 } ) ;
2307- yield * manager . open ( { threadId, providerSessionId, modelSelection, runtimePolicy } ) ;
2325+ const runtime = yield * manager . open ( {
2326+ threadId,
2327+ providerSessionId,
2328+ modelSelection,
2329+ runtimePolicy,
2330+ } ) ;
2331+ const providerThread = makeProviderThread ( {
2332+ idAllocator,
2333+ threadId,
2334+ providerSessionId,
2335+ now,
2336+ } ) ;
2337+ yield * runtime . resumeThread ( { providerThread, modelSelection, runtimePolicy } ) ;
2338+ yield * runtime . resumeThread ( { providerThread, modelSelection, runtimePolicy } ) ;
2339+ assert . equal ( ( yield * Ref . get ( state ) ) . resumeCount , 1 ) ;
2340+ const staleSessionId = yield * idAllocator . allocate . providerSession ( {
2341+ providerInstanceId : modelSelection . instanceId ,
2342+ threadId,
2343+ } ) ;
2344+ yield * manager . open ( {
2345+ threadId,
2346+ providerSessionId : staleSessionId ,
2347+ modelSelection,
2348+ runtimePolicy,
2349+ } ) ;
23082350
23092351 // The thread switches to another provider, whose attach replaces the
23102352 // thread's credential while this session stays attached.
@@ -2315,9 +2357,31 @@ it.effect(
23152357 } ) ;
23162358 yield * mcpSessions . set ( otherProvider . config ) ;
23172359
2360+ if ( interrupted ) {
2361+ const published = yield * Deferred . make < void > ( ) ;
2362+ const resume = yield * Deferred . make < void > ( ) ;
2363+ yield * Ref . set (
2364+ afterMcpSet ,
2365+ Deferred . succeed ( published , undefined ) . pipe ( Effect . andThen ( Deferred . await ( resume ) ) ) ,
2366+ ) ;
2367+ const reclaim = yield * manager
2368+ . open ( { threadId, providerSessionId, modelSelection, runtimePolicy } )
2369+ . pipe ( Effect . forkChild ( { startImmediately : true } ) ) ;
2370+ yield * Deferred . await ( published ) ;
2371+ const issued = yield * mcpSessions . read ( threadId ) ;
2372+ reclaim . interruptUnsafe ( ) ;
2373+ yield * Deferred . succeed ( resume , undefined ) ;
2374+ yield * Fiber . await ( reclaim ) ;
2375+ assert . isUndefined ( yield * registry . resolve ( tokenOf ( issued ) ) ) ;
2376+ assert . isUndefined ( yield * mcpSessions . read ( threadId ) ) ;
2377+ return ;
2378+ }
2379+
23182380 // The thread switches back and its next turn reuses this session.
23192381 yield * manager . open ( { threadId, providerSessionId, modelSelection, runtimePolicy } ) ;
2320- assert . equal ( ( yield * Ref . get ( state ) ) . openCount , 1 ) ;
2382+ assert . equal ( ( yield * Ref . get ( state ) ) . openCount , 2 ) ;
2383+ yield * runtime . resumeThread ( { providerThread, modelSelection, runtimePolicy } ) ;
2384+ assert . equal ( ( yield * Ref . get ( state ) ) . resumeCount , 2 ) ;
23212385
23222386 const reclaimed = yield * mcpSessions . read ( threadId ) ;
23232387 assert . equal ( reclaimed ?. providerInstanceId , modelSelection . instanceId ) ;
@@ -2333,7 +2397,7 @@ it.effect(
23332397 assert . isUndefined ( yield * mcpSessions . read ( threadId ) ) ;
23342398 } ) ;
23352399
2336- yield * effect . pipe ( Effect . provide ( layerTest ( { state, idleTimeoutMs : 1_000 } ) ) ) ;
2400+ yield * effect . pipe ( Effect . provide ( layerTest ( { state, idleTimeoutMs : 1_000 , afterMcpSet } ) ) ) ;
23372401 } ) ,
23382402) ;
23392403
0 commit comments