@@ -24,6 +24,7 @@ import { AgentScope } from "@t3tools/shared/AgentScope";
2424import { ProviderEventLoggers } from "@t3tools/provider-core/server/ProviderEventLoggers" ;
2525import { makeProviderFailure } from "@t3tools/provider-core/server/failure" ;
2626import { signalProcessGroup } from "@t3tools/provider-core/server/processGroup" ;
27+ import * as PortScanner from "../../preview/PortScanner.ts" ;
2728import {
2829 ClaudeAgentSdkQueryRunner ,
2930 type ClaudeAgentSdkQueryOpenInput ,
@@ -66,6 +67,7 @@ const writeClaudeSession = Effect.fnUntraced(function* (configDir: string) {
6667} ) ;
6768
6869const forkRunnerLayer = layerQueryRunner . pipe (
70+ Layer . provide ( PortScanner . layerTest ) ,
6971 Layer . provide ( Layer . succeed ( ProviderEventLoggers , { native : undefined , canonical : undefined } ) ) ,
7072) ;
7173
@@ -110,6 +112,7 @@ it.effect("forks a session stored under the instance's own CLAUDE_CONFIG_DIR", (
110112 ) . pipe ( Effect . provide ( NodeServices . layer ) ) ,
111113) ;
112114const realQueryRunner = layerQueryRunner . pipe (
115+ Layer . provide ( PortScanner . layerTest ) ,
113116 Layer . provide (
114117 Layer . succeed ( ProviderEventLoggers , {
115118 native : undefined ,
@@ -487,4 +490,54 @@ describe.skipIf(HostProcess.Platform.defaultValue() === "win32")("Claude CLI pro
487490 yield * Effect . promise ( ( ) => turnStopped ) ;
488491 } ) ,
489492 ) ;
493+
494+ it . effect ( "marks the CLI's jobs with its thread and counts it as an agent until it exits" , ( ) => {
495+ let runningAgents = 0 ;
496+ let releaseAgent ! : ( ) => void ;
497+ const agentReleased = new Promise < void > ( ( resolve ) => ( releaseAgent = resolve ) ) ;
498+ const runner = layerQueryRunner . pipe (
499+ Layer . provide (
500+ Layer . mock ( PortScanner . PortDiscovery ) ( {
501+ trackAgent : ( ) => {
502+ runningAgents += 1 ;
503+ return ( ) => {
504+ runningAgents -= 1 ;
505+ releaseAgent ( ) ;
506+ } ;
507+ } ,
508+ } ) ,
509+ ) ,
510+ Layer . provide (
511+ Layer . succeed ( ProviderEventLoggers , { native : undefined , canonical : undefined } ) ,
512+ ) ,
513+ Layer . provide ( NodeServices . layer ) ,
514+ ) ;
515+ return Effect . gen ( function * ( ) {
516+ const cli = fakeClaudeCli (
517+ [
518+ 'dir=$(dirname "$0")' ,
519+ `printf '%s' "$${ PortScanner . AGENT_THREAD_ENV } " > "$dir/thread"` ,
520+ "cat > /dev/null" ,
521+ ] . join ( "\n" ) ,
522+ ) ;
523+ const dir = NodePath . dirname ( cli ) ;
524+ NodeChildProcess . execFileSync ( "mkfifo" , [ NodePath . join ( dir , "thread" ) ] ) ;
525+ yield * Effect . addFinalizer ( ( ) =>
526+ Effect . sync ( ( ) => NodeFS . rmSync ( dir , { recursive : true , force : true } ) ) ,
527+ ) ;
528+ const threadRead = new Promise < string > ( ( resolve ) => {
529+ let thread = "" ;
530+ NodeFS . createReadStream ( NodePath . join ( dir , "thread" ) , "utf8" )
531+ . on ( "data" , ( chunk ) => ( thread += chunk ) )
532+ . once ( "end" , ( ) => resolve ( thread ) ) ;
533+ } ) ;
534+
535+ const session = yield * openFakeCli ( cli ) ;
536+ assert . equal ( yield * Effect . promise ( ( ) => threadRead ) , "thread-fake-cli" ) ;
537+ assert . equal ( runningAgents , 1 ) ;
538+ yield * session . close ;
539+ yield * Effect . promise ( ( ) => agentReleased ) ;
540+ assert . equal ( runningAgents , 0 ) ;
541+ } ) . pipe ( Effect . provide ( runner ) ) ;
542+ } ) ;
490543} ) ;
0 commit comments