From a0e988fbbe9e00e4a62b1669df2bad6ad0c6f245 Mon Sep 17 00:00:00 2001 From: Alex Southwell <4596216+saphid@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:41:16 +1100 Subject: [PATCH 1/2] fix(server): dev servers a Codex agent starts show under its thread Co-Authored-By: Claude Opus 5.5 --- .../Adapters/CodexAdapterV2.test.ts | 45 +++++++++++++++++++ .../Adapters/CodexAdapterV2.ts | 9 +++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index 2d14fa9518de..c3528ec94297 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,50 @@ 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(NodeServices.layer), + ); + return Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-codex-cli-" }); + const binaryPath = `${dir}/codex`; + yield* fs.writeFileString(binaryPath, "#!/bin/sh\ncat > /dev/null\n"); + yield* fs.chmod(binaryPath, 0o755); + 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, binaryPath }, + environment: {}, + }) + .pipe(Effect.tap(() => Effect.sync(() => assert.equal(runningAgents, 1)))), + ); + assert.equal(runningAgents, 0); + }).pipe(Effect.scoped, Effect.provide(Layer.merge(factoryLayer, NodeServices.layer))); + }); }); 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, From b5a9aae76d981abc4d6d62f566b7e68070e27aef Mon Sep 17 00:00:00 2001 From: Alex Southwell <4596216+saphid@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:27:58 +1100 Subject: [PATCH 2/2] test(codex): port-discovery test runs on every platform Co-Authored-By: Claude Opus 5.5 --- .../Adapters/CodexAdapterV2.test.ts | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts index c3528ec94297..11e479c185ac 100644 --- a/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts +++ b/apps/server/src/orchestration-v2/Adapters/CodexAdapterV2.test.ts @@ -900,14 +900,30 @@ describe("CodexAdapterV2 process exit", () => { canonical: undefined, }), ), - Layer.provide(NodeServices.layer), + 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 fs = yield* FileSystem.FileSystem; - const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-codex-cli-" }); - const binaryPath = `${dir}/codex`; - yield* fs.writeFileString(binaryPath, "#!/bin/sh\ncat > /dev/null\n"); - yield* fs.chmod(binaryPath, 0o755); const factory = yield* CodexAdapterV2.CodexAppServerClientFactory; yield* Effect.scoped( factory @@ -916,13 +932,13 @@ describe("CodexAdapterV2 process exit", () => { threadId: ThreadId.make("thread-codex-agent"), providerSessionId: ProviderSessionId.make("session-codex-agent"), runtimePolicy: CODEX_TEST_RUNTIME_POLICY, - settings: { ...DEFAULT_CODEX_SETTINGS, binaryPath }, + settings: DEFAULT_CODEX_SETTINGS, environment: {}, }) .pipe(Effect.tap(() => Effect.sync(() => assert.equal(runningAgents, 1)))), ); assert.equal(runningAgents, 0); - }).pipe(Effect.scoped, Effect.provide(Layer.merge(factoryLayer, NodeServices.layer))); + }).pipe(Effect.provide(factoryLayer)); }); });