From be8d3251380bf77dd0995ee52cc0ae30b2557689 Mon Sep 17 00:00:00 2001 From: Peter Kirkham Date: Fri, 3 Apr 2026 14:23:50 +0100 Subject: [PATCH] feat: give codex permissions support --- .../agent/src/adapters/codex/codex-agent.ts | 31 ++++++++++- .../agent/src/adapters/codex/codex-client.ts | 52 +++++++++++++++++-- .../agent/src/adapters/codex/session-state.ts | 4 ++ 3 files changed, 81 insertions(+), 6 deletions(-) diff --git a/packages/agent/src/adapters/codex/codex-agent.ts b/packages/agent/src/adapters/codex/codex-agent.ts index 5d02be28e..ed7735c96 100644 --- a/packages/agent/src/adapters/codex/codex-agent.ts +++ b/packages/agent/src/adapters/codex/codex-agent.ts @@ -36,6 +36,10 @@ import { } from "@agentclientprotocol/sdk"; import packageJson from "../../../package.json" with { type: "json" }; import { POSTHOG_NOTIFICATIONS } from "../../acp-extensions"; +import { + CODE_EXECUTION_MODES, + type CodeExecutionMode, +} from "../../execution-mode"; import type { ProcessSpawnedCallback } from "../../types"; import { Logger } from "../../utils/logger"; import { @@ -80,6 +84,20 @@ type CodexSession = BaseSession & { settingsManager: CodexSettingsManager; }; +function toCodeExecutionMode(mode?: string): CodeExecutionMode { + if (mode && (CODE_EXECUTION_MODES as readonly string[]).includes(mode)) { + return mode as CodeExecutionMode; + } + return "default"; +} + +const CODEX_NATIVE_MODE: Record = { + default: "default", + acceptEdits: "default", + plan: "plan", + bypassPermissions: "default", +}; + export class CodexAcpAgent extends BaseAcpAgent { readonly adapterName = "codex"; declare session: CodexSession; @@ -182,6 +200,7 @@ export class CodexAcpAgent extends BaseAcpAgent { taskId: meta?.taskId ?? meta?.persistence?.taskId, modeId: response.modes?.currentModeId ?? "default", modelId: response.models?.currentModelId, + permissionMode: toCodeExecutionMode(meta?.permissionMode), }); this.sessionId = response.sessionId; this.sessionState.configOptions = response.configOptions ?? []; @@ -353,9 +372,17 @@ export class CodexAcpAgent extends BaseAcpAgent { async setSessionMode( params: SetSessionModeRequest, ): Promise { - const response = await this.codexConnection.setSessionMode(params); + const requestedMode = toCodeExecutionMode(params.modeId); + const nativeMode = CODEX_NATIVE_MODE[requestedMode]; + + const response = await this.codexConnection.setSessionMode({ + ...params, + modeId: nativeMode, + }); + if (this.sessionState) { - this.sessionState.modeId = params.modeId; + this.sessionState.modeId = nativeMode; + this.sessionState.permissionMode = requestedMode; } return response ?? {}; } diff --git a/packages/agent/src/adapters/codex/codex-client.ts b/packages/agent/src/adapters/codex/codex-client.ts index 6306cf742..fecf7ab70 100644 --- a/packages/agent/src/adapters/codex/codex-client.ts +++ b/packages/agent/src/adapters/codex/codex-client.ts @@ -23,11 +23,13 @@ import type { TerminalHandle, TerminalOutputRequest, TerminalOutputResponse, + ToolKind, WaitForTerminalExitRequest, WaitForTerminalExitResponse, WriteTextFileRequest, WriteTextFileResponse, } from "@agentclientprotocol/sdk"; +import type { CodeExecutionMode } from "../../execution-mode"; import type { Logger } from "../../utils/logger"; import type { CodexSessionState } from "./session-state"; @@ -36,6 +38,33 @@ export interface CodexClientCallbacks { onUsageUpdate?: (update: Record) => void; } +const AUTO_APPROVED_KINDS: Record> = { + default: new Set(["read", "search", "fetch", "think"]), + acceptEdits: new Set(["read", "edit", "search", "fetch", "think"]), + plan: new Set(["read", "search", "fetch", "think"]), + bypassPermissions: new Set([ + "read", + "edit", + "delete", + "move", + "search", + "execute", + "think", + "fetch", + "switch_mode", + "other", + ]), +}; + +function shouldAutoApprove( + mode: CodeExecutionMode, + kind: ToolKind | null | undefined, +): boolean { + if (mode === "bypassPermissions") return true; + if (!kind) return false; + return AUTO_APPROVED_KINDS[mode]?.has(kind) ?? false; +} + /** * Creates an ACP Client that delegates all requests from codex-acp * to the upstream PostHog Code client (via AgentSideConnection). @@ -46,16 +75,31 @@ export function createCodexClient( sessionState: CodexSessionState, callbacks?: CodexClientCallbacks, ): Client { - // Track terminal handles for delegation const terminalHandles = new Map(); return { async requestPermission( params: RequestPermissionRequest, ): Promise { - logger.debug("Relaying permission request to upstream", { - sessionId: params.sessionId, - }); + const kind = params.toolCall?.kind as ToolKind | null | undefined; + + if (shouldAutoApprove(sessionState.permissionMode, kind)) { + logger.debug("Auto-approving permission", { + mode: sessionState.permissionMode, + kind, + toolCallId: params.toolCall?.toolCallId, + }); + const allowOption = params.options?.find( + (o) => o.kind === "allow_once" || o.kind === "allow_always", + ); + return { + outcome: { + outcome: "selected", + optionId: allowOption?.optionId ?? "allow", + }, + }; + } + return upstreamClient.requestPermission(params); }, diff --git a/packages/agent/src/adapters/codex/session-state.ts b/packages/agent/src/adapters/codex/session-state.ts index a1555df23..6ba83c8fd 100644 --- a/packages/agent/src/adapters/codex/session-state.ts +++ b/packages/agent/src/adapters/codex/session-state.ts @@ -4,6 +4,7 @@ */ import type { SessionConfigOption } from "@agentclientprotocol/sdk"; +import type { CodeExecutionMode } from "../../execution-mode"; export interface CodexUsage { inputTokens: number; @@ -21,6 +22,7 @@ export interface CodexSessionState { accumulatedUsage: CodexUsage; contextSize?: number; contextUsed?: number; + permissionMode: CodeExecutionMode; cancelled: boolean; interruptReason?: string; taskRunId?: string; @@ -35,6 +37,7 @@ export function createSessionState( taskId?: string; modeId?: string; modelId?: string; + permissionMode?: CodeExecutionMode; }, ): CodexSessionState { return { @@ -49,6 +52,7 @@ export function createSessionState( cachedReadTokens: 0, cachedWriteTokens: 0, }, + permissionMode: opts?.permissionMode ?? "default", cancelled: false, taskRunId: opts?.taskRunId, taskId: opts?.taskId,