Skip to content

[WIP] effect-codex-app-server#1942

Open
juliusmarminge wants to merge 4 commits intot3code/greetingfrom
t3code/ui-polish-thread-view
Open

[WIP] effect-codex-app-server#1942
juliusmarminge wants to merge 4 commits intot3code/greetingfrom
t3code/ui-polish-thread-view

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Apr 12, 2026

Typesafe JSONRPC effect API for Codex App Server. Generated from the protocol's JSON schemas

import * as Console from "effect/Console";
import * as Effect from "effect/Effect";

import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
import * as NodeServices from "@effect/platform-node/NodeServices";

import * as CodexClient from "effect-codex-app-server/client";

const program = Effect.gen(function* () {
  const codexLayer = CodexClient.layerCommand({
    command: process.env.CODEX_BIN ?? "codex",
    args: ["app-server"],
    cwd: process.cwd(),
    logIncoming: true,
    logOutgoing: true,
  });

  yield* Effect.gen(function* () {
    const client = yield* CodexClient.CodexAppServerClient;

    yield* client.handleServerRequest("item/tool/requestUserInput", (payload) =>
      Effect.succeed({
        answers: Object.fromEntries(
          payload.questions.map((question) => [
            question.id,
            {
              answers:
                question.options && question.options.length > 0
                  ? [question.options[0]!.label]
                  : ["ok"],
            },
          ]),
        ),
      }),
    );

    const initialized = yield* client.request("initialize", {
      clientInfo: {
        name: "effect-codex-app-server-probe",
        title: "Effect Codex App Server Probe",
        version: "0.0.0",
      },
      capabilities: {
        experimentalApi: true,
        optOutNotificationMethods: null,
      },
    });
    yield* Console.log("initialize", JSON.stringify(initialized, null, 2));

    yield* client.notify("initialized", undefined);

    const account = yield* client.request("account/read", {});
    yield* Console.log("account/read", JSON.stringify(account, null, 2));

    const skills = yield* client.request("skills/list", {
      cwds: [process.cwd()],
    });
    yield* Console.log("skills/list", JSON.stringify(skills, null, 2));
  }).pipe(Effect.provide(codexLayer));
});

program.pipe(Effect.scoped, Effect.provide(NodeServices.layer), NodeRuntime.runMain);

Note

Medium Risk
Moderate risk because it replaces the Codex provider transport/event plumbing and error mapping with a new runtime layer; regressions could affect session lifecycle, event streaming, and approval/user-input flows.

Overview
Migrates the server’s Codex integration from the in-repo CodexAppServerManager to the new effect-codex-app-server typed JSON-RPC runtime, managing one CodexSessionRuntime per thread and wiring its events stream into the existing canonical runtime event stream.

Updates CodexAdapter to create/stop runtimes on startSession, route sendTurn/interruptTurn/readThread/rollbackThread/approval + user-input responses through the runtime, and map runtime errors into adapter errors (notably SessionNotFound vs process/transport failures).

Refactors Codex event mapping to decode payloads via generated schemas and adjusts a few mappings (e.g., stdout/stderr classification via runtime, output deltas emitted as content.delta, user-input question multiSelect forced to false), updates tests to use a fake runtime + queue-backed event stream, and switches model capability import to provider/codexModels.ts. Also adds the effect-codex-app-server dev dependency and removes the legacy manager implementation + its tests.

Reviewed by Cursor Bugbot for commit 3d9f655. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add effect-codex-app-server package and refactor Codex adapter to use typed session runtimes

  • Introduces a new effect-codex-app-server workspace package with a typed JSON-RPC client, bidirectional stdio protocol, generated schemas from the OpenAI Codex protocol, and structured error types for spawn, transport, parse, and request failures.
  • Rewrites the Codex adapter in CodexAdapter.ts to manage per-thread CodexSessionRuntime instances instead of a single CodexAppServerManager; each session now streams typed events directly from its own runtime fiber.
  • Replaces the manual JSON-RPC-over-stdio discovery probe in codexAppServer.ts with an Effect-based client using the new package; skills and account data are now parsed via generated schemas.
  • Adds snapshot enrichment via enrichCodexSnapshotViaDiscovery in CodexProvider.ts, which merges discovered account auth and skills into the published provider snapshot.
  • Deletes codexAppServerManager.ts and moves model constants to a dedicated codexModels.ts module.
  • Risk: event payload shapes sent to clients have changed (e.g. command/file-change output now emits content.delta instead of tool.progress; turn.completed no longer includes usage/cost fields); multiSelect on user-input questions is hardcoded to false.

Macroscope summarized 3d9f655.

- Generate schema and protocol typings from upstream codex app-server
- Add tests and probe fixtures for protocol/client coverage
- Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 16f3bd56-42de-49b2-910f-2d0ce26d619c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/ui-polish-thread-view

Comment @coderabbitai help to get the list of available commands and usage tips.

@juliusmarminge juliusmarminge changed the base branch from main to t3code/greeting April 12, 2026 02:29
@github-actions github-actions bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Apr 12, 2026
@juliusmarminge juliusmarminge changed the title Polish thread view and ACP provider runtime [WIP] effect-codex-app-server Apr 12, 2026
@juliusmarminge juliusmarminge marked this pull request as draft April 12, 2026 02:31
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp bot commented Apr 12, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. Diff is too large for automated approval analysis. A human reviewer should evaluate this PR.

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge
Copy link
Copy Markdown
Member Author

bugbot run

Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Error recovery yields failure instead of fallback value
    • Piped the Effect.tryPromise through Effect.orElseSucceed(() => "") so that a rejected response.text() recovers to an empty string success value instead of short-circuiting the generator with a bare empty string failure.

Create PR

Or push these changes by commenting:

@cursor push cf803e8469
Preview (cf803e8469)
diff --git a/packages/effect-codex-app-server/scripts/generate.ts b/packages/effect-codex-app-server/scripts/generate.ts
--- a/packages/effect-codex-app-server/scripts/generate.ts
+++ b/packages/effect-codex-app-server/scripts/generate.ts
@@ -160,8 +160,9 @@
   if (!response.ok) {
     const detail = yield* Effect.tryPromise({
       try: () => response.text(),
-      catch: () => "",
-    });
+      catch: (cause) =>
+        new GeneratorError({ detail: `Failed to read response body from ${url}`, cause }),
+    }).pipe(Effect.orElseSucceed(() => ""));
     return yield* Effect.fail(
       new GeneratorError({
         detail: `Failed to download ${url}: ${response.status} ${detail}`,

You can send follow-ups to the cloud agent here.

- Switch discovery probing to the typed Codex client
- Add coverage for initialize, account, and skills parsing
- Preserve account snapshot mapping for typed responses
- Split Codex app-server session logic into dedicated runtime modules
- Update provider and adapter paths to use the new codex session flow
- Refresh tests and developer instructions around Codex behavior
Comment on lines +615 to +618
Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const runtimeScope = yield* Scope.make("sequential");
const events = yield* Queue.unbounded<ProviderEvent>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium codex/CodexSessionRuntime.ts:615

If makeCodexSessionRuntime fails after spawning the child process but before returning (e.g., Layer.build at line 644 throws), runtimeScope is never closed and the child process leaks because it was spawned with Effect.provideService(Scope.Scope, runtimeScope). The scope at line 617 has no automatic finalization handler, so any early exit bypasses cleanup.

  Effect.gen(function* () {
     const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
-    const runtimeScope = yield* Scope.make("sequential");
+    const runtimeScope = yield* Scope.make("sequential");
+    yield* Effect.addFinalizer(() => Scope.close(runtimeScope, Exit.void));
     const events = yield* Queue.unbounded<ProviderEvent>();
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/server/src/provider/codex/CodexSessionRuntime.ts around lines 615-618:

If `makeCodexSessionRuntime` fails after spawning the child process but before returning (e.g., `Layer.build` at line 644 throws), `runtimeScope` is never closed and the child process leaks because it was spawned with `Effect.provideService(Scope.Scope, runtimeScope)`. The scope at line 617 has no automatic finalization handler, so any early exit bypasses cleanup.

Evidence trail:
Line 617 shows runtimeScope creation

@juliusmarminge juliusmarminge marked this pull request as ready for review April 12, 2026 18:37
- Add `packages/effect-codex-app-server/package.json` to the tracked workspace files
- Keep release smoke coverage aligned with the repo package set
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is ON. A cloud agent has been kicked off to fix the reported issue. You can view the agent here.

Reviewed by Cursor Bugbot for commit 3d9f655. Configure here.

}

function normalizeItemType(raw: unknown): string {
const type = asString(raw);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped "cancelled" case maps cancelled turns to "completed"

Medium Severity

The toTurnStatus function's return type includes "cancelled" but the switch statement no longer has a case "cancelled" branch — it was removed in this diff. If the Codex server sends a turn with "cancelled" status, it falls through to default and is incorrectly reported as "completed". The old code explicitly handled "cancelled" as a passthrough case.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3d9f655. Configure here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bugbot Autofix determined this is a false positive.

The upstream Codex schema (V2TurnCompletedNotification__TurnStatus) only defines "completed" | "interrupted" | "failed" | "inProgress" — "cancelled" is not a valid value and adding it causes a TypeScript compile error, so the server can never send it.

You can send follow-ups to the cloud agent here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant