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
19 changes: 15 additions & 4 deletions src/CodexAcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import {sanitizeMcpServerName} from "./McpServerName";
import type {
AccountLoginCompletedNotification,
AccountUpdatedNotification,
ApprovalsReviewer,
AskForApproval,
GetAccountRateLimitsResponse,
GetAccountResponse,
ListMcpServerStatusResponse,
Expand Down Expand Up @@ -549,6 +551,9 @@ export class CodexAcpClient {
modelProvider: response.modelProvider,
currentServiceTier: response.serviceTier as ServiceTier ?? null,
additionalDirectories,
approvalPolicy: response.approvalPolicy,
approvalsReviewer: response.approvalsReviewer,
sandboxPolicy: response.sandbox,
}
}

Expand Down Expand Up @@ -600,6 +605,9 @@ export class CodexAcpClient {
currentServiceTier: response.serviceTier as ServiceTier ?? null,
thread,
additionalDirectories,
approvalPolicy: response.approvalPolicy,
approvalsReviewer: response.approvalsReviewer,
sandboxPolicy: response.sandbox,
};
}

Expand Down Expand Up @@ -630,6 +638,9 @@ export class CodexAcpClient {
modelProvider: response.modelProvider,
currentServiceTier: response.serviceTier as ServiceTier ?? null,
additionalDirectories,
approvalPolicy: response.approvalPolicy,
approvalsReviewer: response.approvalsReviewer,
sandboxPolicy: response.sandbox,
};
}

Expand Down Expand Up @@ -935,7 +946,7 @@ export class CodexAcpClient {

async sendPrompt(
request: acp.PromptRequest,
agentMode: AgentMode,
turnPermissionSettings: {approvalPolicy: AskForApproval, approvalsReviewer: ApprovalsReviewer, sandboxPolicy: SandboxPolicy},
modelId: ModelId,
serviceTier: ServiceTier | null,
disableSummary: boolean,
Expand All @@ -953,9 +964,9 @@ export class CodexAcpClient {
return await this.codexClient.runTurn({
threadId: request.sessionId,
input: input,
approvalPolicy: agentMode.approvalPolicy,
approvalsReviewer: agentMode.approvalsReviewer,
sandboxPolicy: addAdditionalDirectoriesToSandboxPolicy(agentMode.sandboxPolicy, additionalDirectories),
approvalPolicy: turnPermissionSettings.approvalPolicy,
approvalsReviewer: turnPermissionSettings.approvalsReviewer,
sandboxPolicy: addAdditionalDirectoriesToSandboxPolicy(turnPermissionSettings.sandboxPolicy, additionalDirectories),
summary: disableSummary ? "none" : "auto",
effort: effort,
model: modelId.model,
Expand Down
33 changes: 32 additions & 1 deletion src/CodexAcpServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ import type {InputModality, ReasoningEffort, ServerNotification} from "./app-ser
import type {
Account,
AccountUpdatedNotification,
ApprovalsReviewer,
AskForApproval,
Model,
ReasoningEffortOption,
SandboxPolicy,
Thread,
ThreadGoal,
ThreadItem,
Expand Down Expand Up @@ -160,6 +163,9 @@ export interface SessionState {
supportedReasoningEfforts: Array<ReasoningEffortOption>,
supportedInputModalities: Array<InputModality>,
agentMode: AgentMode,
resolvedApprovalPolicy: AskForApproval,
resolvedApprovalsReviewer: ApprovalsReviewer,
resolvedSandboxPolicy: SandboxPolicy,
collaborationMode: ModeKind,
currentTurnId: string | null;
lastTokenUsage: TokenCount | null;
Expand Down Expand Up @@ -666,6 +672,9 @@ export class CodexAcpServer {
supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
agentMode: AgentMode.getInitialAgentMode(),
resolvedApprovalPolicy: sessionMetadata.approvalPolicy ?? AgentMode.Agent.approvalPolicy,
resolvedApprovalsReviewer: sessionMetadata.approvalsReviewer ?? AgentMode.Agent.approvalsReviewer,
resolvedSandboxPolicy: sessionMetadata.sandboxPolicy ?? AgentMode.Agent.sandboxPolicy,
collaborationMode: sessionMetadata.collaborationMode,
currentTurnId: null,
lastTokenUsage: null,
Expand Down Expand Up @@ -1923,6 +1932,9 @@ export class CodexAcpServer {
supportedReasoningEfforts: currentModel?.supportedReasoningEfforts ?? [],
supportedInputModalities: currentModel?.inputModalities ?? ["text", "image"],
agentMode: AgentMode.getInitialAgentMode(),
resolvedApprovalPolicy: sessionMetadata.approvalPolicy ?? AgentMode.Agent.approvalPolicy,
resolvedApprovalsReviewer: sessionMetadata.approvalsReviewer ?? AgentMode.Agent.approvalsReviewer,
resolvedSandboxPolicy: sessionMetadata.sandboxPolicy ?? AgentMode.Agent.sandboxPolicy,
collaborationMode: sessionMetadata.collaborationMode,
currentTurnId: null,
lastTokenUsage: null,
Expand Down Expand Up @@ -2942,6 +2954,7 @@ export class CodexAcpServer {
throw RequestError.invalidRequest("The current model does not support image input");
}
const agentMode = sessionState.agentMode;
const turnPermissionSettings = this.createTurnPermissionSettings(sessionState, agentMode);
const serviceTier = resolveFastServiceTier(
sessionState.fastModeEnabled,
sessionState.currentModelSupportsFast,
Expand All @@ -2951,7 +2964,7 @@ export class CodexAcpServer {
const sendPromptPromise = this.runWithProcessCheck(
() => this.codexAcpClient.sendPrompt(
effectiveParams,
agentMode,
turnPermissionSettings,
modelId,
serviceTier,
disableSummary,
Expand Down Expand Up @@ -3245,6 +3258,24 @@ export class CodexAcpServer {
}
}

private createTurnPermissionSettings(
sessionState: SessionState,
agentMode: AgentMode,
): {approvalPolicy: AskForApproval, approvalsReviewer: ApprovalsReviewer, sandboxPolicy: SandboxPolicy} {
if (agentMode.id !== AgentMode.DEFAULT_AGENT_MODE.id) {
return {
approvalPolicy: agentMode.approvalPolicy,
approvalsReviewer: agentMode.approvalsReviewer,
sandboxPolicy: agentMode.sandboxPolicy,
};
}
return {
approvalPolicy: sessionState.resolvedApprovalPolicy,
approvalsReviewer: sessionState.resolvedApprovalsReviewer,
sandboxPolicy: sessionState.resolvedSandboxPolicy,
};
}

private cancelledPromptResponse(sessionState: SessionState): acp.PromptResponse {
return {
stopReason: "cancelled",
Expand Down
3 changes: 3 additions & 0 deletions src/SessionFork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export async function forkSession(
modelProvider: response.modelProvider,
currentServiceTier: response.serviceTier as ServiceTier ?? null,
additionalDirectories,
approvalPolicy: response.approvalPolicy,
approvalsReviewer: response.approvalsReviewer,
sandboxPolicy: response.sandbox,
};
}

Expand Down
5 changes: 4 additions & 1 deletion src/SessionMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {ModeKind} from "./app-server/ModeKind";
import type {ServiceTier} from "./app-server/ServiceTier";
import type {Model, Thread} from "./app-server/v2";
import type {ApprovalsReviewer, AskForApproval, Model, SandboxPolicy, Thread} from "./app-server/v2";

export type SessionMetadata = {
sessionId: string,
Expand All @@ -10,6 +10,9 @@ export type SessionMetadata = {
modelProvider?: string | null,
currentServiceTier?: ServiceTier | null,
additionalDirectories: string[],
approvalPolicy?: AskForApproval,
approvalsReviewer?: ApprovalsReviewer,
sandboxPolicy?: SandboxPolicy,
}

export type SessionMetadataWithThread = SessionMetadata & {
Expand Down
62 changes: 62 additions & 0 deletions src/__tests__/CodexACPAgent/CodexAcpClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
createTestFixture,
createTestModel,
createTestSessionState,
setupPromptTestSession,
type TestFixture
} from "../acp-test-utils";
import type {ServerNotification} from "../../app-server";
Expand Down Expand Up @@ -1064,6 +1065,67 @@ describe('ACP server test', { timeout: 40_000 }, () => {
expect(listSkillsSpy.mock.invocationCallOrder[0]!).toBeLessThan(turnStartSpy.mock.invocationCallOrder[0]!);
});

it('preserves resolved default prompt permissions while adding ACP directories', async () => {
const {mockFixture, turnStartSpy} = setupPromptTestSession({
sessionId: "session-id",
cwd: "/workspace",
additionalDirectories: ["/workspace/extra"],
agentMode: AgentMode.DEFAULT_AGENT_MODE,
resolvedApprovalPolicy: "on-request",
resolvedApprovalsReviewer: "user",
resolvedSandboxPolicy: {
type: "workspaceWrite",
writableRoots: ["/shared-cache"],
networkAccess: true,
excludeTmpdirEnvVar: false,
excludeSlashTmp: false,
},
});

await mockFixture.getCodexAcpAgent().prompt({
sessionId: "session-id",
prompt: [{type: "text", text: "Hello"}],
});

expect(turnStartSpy.mock.calls[0]![0]).toMatchObject({
approvalPolicy: "on-request",
approvalsReviewer: "user",
sandboxPolicy: {
type: "workspaceWrite",
writableRoots: ["/shared-cache", "/workspace/extra"],
networkAccess: true,
},
});
});

it('uses explicit non-default mode prompt permissions instead of resolved defaults', async () => {
const {mockFixture, turnStartSpy} = setupPromptTestSession({
sessionId: "session-id",
cwd: "/workspace",
agentMode: AgentMode.AgentFullAccess,
resolvedApprovalPolicy: "on-request",
resolvedApprovalsReviewer: "user",
resolvedSandboxPolicy: {
type: "workspaceWrite",
writableRoots: ["/shared-cache"],
networkAccess: true,
excludeTmpdirEnvVar: false,
excludeSlashTmp: false,
},
});

await mockFixture.getCodexAcpAgent().prompt({
sessionId: "session-id",
prompt: [{type: "text", text: "Hello"}],
});

expect(turnStartSpy.mock.calls[0]![0]).toMatchObject({
approvalPolicy: "never",
approvalsReviewer: "user",
sandboxPolicy: {type: "dangerFullAccess"},
});
});

it('applies ACP additional directories to turn skill discovery and sandbox policy', async () => {
const mockFixture = createCodexMockTestFixture();
const codexAcpAgent = mockFixture.getCodexAcpAgent();
Expand Down
3 changes: 3 additions & 0 deletions src/__tests__/acp-test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,9 @@ export function createTestSessionState(overrides?: Partial<SessionState>): Sessi
supportedReasoningEfforts: [],
supportedInputModalities: ["text", "image"],
agentMode: AgentMode.DEFAULT_AGENT_MODE,
resolvedApprovalPolicy: "on-request",
resolvedApprovalsReviewer: "auto_review",
resolvedSandboxPolicy: AgentMode.DEFAULT_AGENT_MODE.sandboxPolicy,
collaborationMode: DEFAULT_COLLABORATION_MODE,
fastModeEnabled: false,
currentModelSupportsFast: false,
Expand Down