Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions apps/server/src/codexAppServerManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,42 @@ describe("classifyCodexStderrLine", () => {
});
});

describe("process stderr events", () => {
it("emits classified stderr lines as notifications", () => {
const manager = new CodexAppServerManager();
const emitEvent = vi
.spyOn(manager as unknown as { emitEvent: (...args: unknown[]) => void }, "emitEvent")
.mockImplementation(() => {});

(
manager as unknown as {
emitNotificationEvent: (
context: { session: { threadId: ThreadId } },
method: string,
message: string,
) => void;
}
).emitNotificationEvent(
{
session: {
threadId: asThreadId("thread-1"),
},
},
"process/stderr",
"fatal: permission denied",
);

expect(emitEvent).toHaveBeenCalledWith(
expect.objectContaining({
kind: "notification",
method: "process/stderr",
threadId: "thread-1",
message: "fatal: permission denied",
}),
);
});
});

describe("normalizeCodexModelSlug", () => {
it("maps 5.3 aliases to gpt-5.3-codex", () => {
expect(normalizeCodexModelSlug("5.3")).toBe("gpt-5.3-codex");
Expand Down
18 changes: 17 additions & 1 deletion apps/server/src/codexAppServerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1046,7 +1046,7 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
continue;
}

this.emitErrorEvent(context, "process/stderr", classified.message);
this.emitNotificationEvent(context, "process/stderr", classified.message);
}
});

Expand Down Expand Up @@ -1354,6 +1354,22 @@ export class CodexAppServerManager extends EventEmitter<CodexAppServerManagerEve
});
}

private emitNotificationEvent(
context: CodexSessionContext,
method: string,
message: string,
): void {
this.emitEvent({
id: EventId.makeUnsafe(randomUUID()),
kind: "notification",
provider: "codex",
threadId: context.session.threadId,
createdAt: new Date().toISOString(),
method,
message,
});
}

private emitEvent(event: ProviderEvent): void {
this.emit("event", event);
}
Expand Down
34 changes: 34 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,40 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => {
}),
);

it.effect("maps process stderr notifications to runtime.warning", () =>
Effect.gen(function* () {
const adapter = yield* CodexAdapter;
const firstEventFiber = yield* Stream.runHead(adapter.streamEvents).pipe(Effect.forkChild);

lifecycleManager.emit("event", {
id: asEventId("evt-process-stderr"),
kind: "notification",
provider: "codex",
threadId: asThreadId("thread-1"),
createdAt: new Date().toISOString(),
method: "process/stderr",
turnId: asTurnId("turn-1"),
message: "The filename or extension is too long. (os error 206)",
} satisfies ProviderEvent);

const firstEvent = yield* Fiber.join(firstEventFiber);

assert.equal(firstEvent._tag, "Some");
if (firstEvent._tag !== "Some") {
return;
}
assert.equal(firstEvent.value.type, "runtime.warning");
if (firstEvent.value.type !== "runtime.warning") {
return;
}
assert.equal(firstEvent.value.turnId, "turn-1");
assert.equal(
firstEvent.value.payload.message,
"The filename or extension is too long. (os error 206)",
);
}),
);

it.effect("preserves request type when mapping serverRequest/resolved", () =>
Effect.gen(function* () {
const adapter = yield* CodexAdapter;
Expand Down
13 changes: 13 additions & 0 deletions apps/server/src/provider/Layers/CodexAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1267,6 +1267,19 @@ function mapToRuntimeEvents(
];
}

if (event.method === "process/stderr") {
return [
{
type: "runtime.warning",
...runtimeEventBase(event, canonicalThreadId),
payload: {
message: event.message ?? "Codex process stderr",
...(event.payload !== undefined ? { detail: event.payload } : {}),
},
},
];
}

if (event.method === "windows/worldWritableWarning") {
return [
{
Expand Down