diff --git a/AGENTS.md b/AGENTS.md index 1f0eb082d..88c68684f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,6 +41,10 @@ - Codex reasoning summaries can echo trailing empty HTML comments from model instructions. Keep that provider-specific cleanup in `src/ReasoningText.ts` across live deltas and history replay; do not filter assistant text, raw reasoning, or HTML globally in the client renderer. +- App-server `warning` and `configWarning` notifications are structured ACP session info updates: + put `{ message, source }` under `_meta.codex.warning`. Never emit them as agent message text; + Lody persists that metadata as an `agent_warning` system notice, and text chunks would pollute + assistant history, generated titles, and replayed prompts. - Account rate-limit windows are dynamic. Read the full `account/rateLimits/read` snapshot when a session opens, merge sparse `account/rateLimits/updated` values into that snapshot, and preserve each window's `windowDurationMins` in the ACP extension. Never infer 5-hour/7-day meaning from diff --git a/src/CodexEventHandler.ts b/src/CodexEventHandler.ts index 6034fa6bd..43f8ae8db 100644 --- a/src/CodexEventHandler.ts +++ b/src/CodexEventHandler.ts @@ -437,11 +437,21 @@ export class CodexEventHandler { private async createConfigWarningEvent(event: ConfigWarningNotification): Promise { const detailsText = event.details ? `\n\n${event.details}` : ""; - return createAgentTextMessageChunk(`Config warning: ${event.summary}${detailsText}\n\n`); + return this.createCodexSessionInfoUpdate({ + warning: { + message: `${event.summary}${detailsText}`, + source: "configWarning", + }, + }); } private createWarningEvent(event: WarningNotification): UpdateSessionEvent { - return createAgentTextMessageChunk(`Warning: ${event.message}\n\n`); + return this.createCodexSessionInfoUpdate({ + warning: { + message: event.message, + source: "warning", + }, + }); } private createModelReroutedEvent(event: ModelReroutedNotification): UpdateSessionEvent { diff --git a/src/__tests__/CodexACPAgent/data/warning-events.json b/src/__tests__/CodexACPAgent/data/warning-events.json new file mode 100644 index 000000000..766f0b9e1 --- /dev/null +++ b/src/__tests__/CodexACPAgent/data/warning-events.json @@ -0,0 +1,38 @@ +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "session_info_update", + "_meta": { + "codex": { + "warning": { + "message": "Falling back from WebSockets to HTTPS transport. unexpected status 401", + "source": "warning" + } + } + } + } + } + ] +} +{ + "method": "sessionUpdate", + "args": [ + { + "sessionId": "test-session-id", + "update": { + "sessionUpdate": "session_info_update", + "_meta": { + "codex": { + "warning": { + "message": "Project configuration could not be loaded\n\nUsing the default configuration instead.", + "source": "configWarning" + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/src/__tests__/CodexACPAgent/warning-events.test.ts b/src/__tests__/CodexACPAgent/warning-events.test.ts new file mode 100644 index 000000000..9fc89188d --- /dev/null +++ b/src/__tests__/CodexACPAgent/warning-events.test.ts @@ -0,0 +1,48 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; +import type { ServerNotification } from "../../app-server"; +import { + createCodexMockTestFixture, + createTestSessionState, + setupPromptAndSendNotifications, + type CodexMockTestFixture, +} from "../acp-test-utils"; + +describe("CodexEventHandler - warning events", () => { + let mockFixture: CodexMockTestFixture; + const sessionId = "test-session-id"; + + beforeEach(() => { + mockFixture = createCodexMockTestFixture(); + vi.clearAllMocks(); + }); + + it("sends runtime and configuration warnings as structured session metadata", async () => { + const notifications: ServerNotification[] = [ + { + method: "warning", + params: { + threadId: sessionId, + message: "Falling back from WebSockets to HTTPS transport. unexpected status 401", + }, + }, + { + method: "configWarning", + params: { + summary: "Project configuration could not be loaded", + details: "Using the default configuration instead.", + }, + }, + ]; + + await setupPromptAndSendNotifications( + mockFixture, + sessionId, + createTestSessionState({ sessionId }), + notifications, + ); + + await expect(mockFixture.getAcpConnectionDump([])).toMatchFileSnapshot( + "data/warning-events.json", + ); + }); +});