diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 2d14fa9518de..11e479c185ac 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -69,6 +69,7 @@ import * as ProviderReplayHarness from "../testkit/ProviderReplayHarness.ts"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; import type * as ProviderContinuationRequests from "@t3tools/provider-core/server/ProviderContinuationRequests"; import * as ModelManifest from "../../provider/ModelManifest.ts"; +import * as PortScanner from "../../preview/PortScanner.ts"; import { resolveProviderCompatibility } from "../../provider/providerCompatibility.ts"; import * as CodexAdapterV2 from "./CodexAdapterV2.ts"; import { withCodexReplayChildMetadata } from "./CodexAdapterV2.testkit.ts"; @@ -879,6 +880,66 @@ describe("CodexAdapterV2 process exit", () => { ), ), ); + + it.effect("counts the app-server as a running agent until its session closes", () => { + let runningAgents = 0; + const factoryLayer = CodexAdapterV2.layerAppServerClientFactory.pipe( + Layer.provide( + Layer.mock(PortScanner.PortDiscovery)({ + trackAgent: () => { + runningAgents += 1; + return () => { + runningAgents -= 1; + }; + }, + }), + ), + Layer.provide( + Layer.succeed(ProviderEventLoggers.ProviderEventLoggers, { + native: undefined, + canonical: undefined, + }), + ), + Layer.provide( + Layer.succeed( + ChildProcessSpawner.ChildProcessSpawner, + ChildProcessSpawner.make(() => + Effect.succeed( + ChildProcessSpawner.makeHandle({ + pid: ChildProcessSpawner.ProcessId(999_999_999), + exitCode: Effect.never, + isRunning: Effect.succeed(true), + kill: () => Effect.void, + unref: Effect.succeed(Effect.void), + stdin: Sink.drain, + stdout: Stream.never, + stderr: Stream.empty, + all: Stream.empty, + getInputFd: () => Sink.drain, + getOutputFd: () => Stream.empty, + }), + ), + ), + ), + ), + ); + return Effect.gen(function* () { + const factory = yield* CodexAdapterV2.CodexAppServerClientFactory; + yield* Effect.scoped( + factory + .open({ + instanceId: CodexAdapterV2.CODEX_DEFAULT_INSTANCE_ID, + threadId: ThreadId.make("thread-codex-agent"), + providerSessionId: ProviderSessionId.make("session-codex-agent"), + runtimePolicy: CODEX_TEST_RUNTIME_POLICY, + settings: DEFAULT_CODEX_SETTINGS, + environment: {}, + }) + .pipe(Effect.tap(() => Effect.sync(() => assert.equal(runningAgents, 1)))), + ); + assert.equal(runningAgents, 0); + }).pipe(Effect.provide(factoryLayer)); + }); }); describe("CodexAdapterV2 session initialize", () => { diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts index f81a3809b35f..256bee07ab42 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.ts @@ -1516,13 +1516,16 @@ function isSensitiveCodexProtocolKey(key: string): boolean { export const layerAppServerClientFactory: Layer.Layer< CodexAppServerClientFactory, never, - ChildProcessSpawner.ChildProcessSpawner | ProviderEventLoggers.ProviderEventLoggers + | ChildProcessSpawner.ChildProcessSpawner + | PortScanner.PortDiscovery + | ProviderEventLoggers.ProviderEventLoggers > = Layer.effect( CodexAppServerClientFactory, Effect.gen(function* () { const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; const { native: nativeEventLogger } = yield* ProviderEventLoggers.ProviderEventLoggers; const agentScope = yield* AgentScope; + const portDiscovery = yield* PortScanner.PortDiscovery; return CodexAppServerClientFactory.of({ open: Effect.fn("CodexAdapterV2.open")(function* (input) { @@ -1550,6 +1553,10 @@ export const layerAppServerClientFactory: Layer.Layer< }), ), ); + // Its shells carry each thread's T3CODE_THREAD_ID, so port discovery reads listeners' threads. + yield* Effect.acquireRelease(Effect.sync(portDiscovery.trackAgent), (release) => + Effect.sync(release), + ); const protocolLogger = makeCodexAppServerProtocolLogger({ nativeEventLogger, threadId: input.threadId,