Wire Copilot Allow All mode to ACP permissions (#935)

This commit is contained in:
Mohamed Boudra
2026-05-12 12:00:20 +08:00
committed by GitHub
parent d32462e9ee
commit a5c2b97e1d
5 changed files with 478 additions and 34 deletions
@@ -11,6 +11,7 @@ export interface AgentModeVisuals {
export type AgentProviderModeDefinition = Omit<AgentMode, "icon" | "colorTier"> &
AgentModeVisuals & {
// Marks the provider's most-permissioned no-prompt mode. Selecting it means tools run without approval; the runtime mechanism is provider-specific.
isUnattended?: boolean;
};
@@ -96,9 +97,9 @@ const COPILOT_MODES: AgentProviderModeDefinition[] = [
colorTier: "planning",
},
{
id: "https://agentclientprotocol.com/protocol/session-modes#autopilot",
label: "Autopilot",
description: "Autonomous mode that runs until task completion without user interaction",
id: "allow-all",
label: "Allow All",
description: "Automatically approves all Copilot tool, path, and URL requests.",
icon: "ShieldOff",
colorTier: "dangerous",
isUnattended: true,
@@ -21,6 +21,15 @@ import {
resolveACPModeSelection,
resolveACPModelSelection,
} from "./acp-agent.js";
import {
COPILOT_ALLOW_ALL_MODE_ID,
COPILOT_MODES,
beforeCopilotModeWriter,
transformCopilotConfigOptions,
transformCopilotModeId,
transformCopilotSessionResponse,
writeCopilotProviderMode,
} from "./copilot-acp-agent.js";
import { transformPiModels } from "./pi-direct-agent.js";
import type { AgentStreamEvent } from "../agent-sdk-types.js";
import { createTestLogger } from "../../../test-utils/test-logger.js";
@@ -140,6 +149,73 @@ function selectConfigOption(
};
}
function createCopilotSessionWithConfig(modeId?: string | null): ACPAgentSession {
return new ACPAgentSession(
{
provider: "copilot",
cwd: "/tmp/paseo-acp-test",
modeId: modeId ?? undefined,
},
{
provider: "copilot",
logger: createTestLogger(),
defaultCommand: ["copilot", "--acp"],
defaultModes: COPILOT_MODES,
sessionResponseTransformer: transformCopilotSessionResponse,
configOptionsTransformer: transformCopilotConfigOptions,
modeIdTransformer: transformCopilotModeId,
providerModeWriter: writeCopilotProviderMode,
beforeModeWriter: beforeCopilotModeWriter,
capabilities: {
supportsStreaming: true,
supportsSessionPersistence: true,
supportsDynamicModes: true,
supportsMcpServers: true,
supportsReasoningStream: true,
supportsToolInvocations: true,
},
},
);
}
function copilotModeConfigOption(currentValue: string): SessionConfigOption {
return {
id: "mode",
name: "Mode",
category: "mode",
type: "select",
currentValue,
options: [
{
value: "https://agentclientprotocol.com/protocol/session-modes#agent",
name: "Agent",
},
{
value: "https://agentclientprotocol.com/protocol/session-modes#plan",
name: "Plan",
},
{
value: "https://agentclientprotocol.com/protocol/session-modes#autopilot",
name: "Autopilot",
},
],
};
}
function copilotAllowAllConfigOption(currentValue: "on" | "off"): SessionConfigOption {
return {
id: "allow_all",
name: "Allow All",
category: "permissions",
type: "select",
currentValue,
options: [
{ value: "on", name: "On" },
{ value: "off", name: "Off" },
],
};
}
function selectConfigOptionName(category: "mode" | "model" | "thought_level"): string {
if (category === "mode") {
return "Mode";
@@ -796,12 +872,10 @@ describe("ACPAgentSession Zed parity", () => {
});
});
test("passes Copilot Autopilot ACP permission requests through to the user", async () => {
// Zed parity: Copilot Autopilot no longer auto-approves ACP permission requests,
// so the accepted UX regression is that every tool request reaches the user UI.
test("passes generic ACP permission requests through to the user", async () => {
const session = createSessionWithConfig({
provider: "copilot",
modeId: "https://agentclientprotocol.com/protocol/session-modes#autopilot",
provider: "cursor-acp",
modeId: "https://agentclientprotocol.com/protocol/session-modes#agent",
});
const events: Array<{ type: string; request?: { id: string } }> = [];
const permissionOptions: PermissionOption[] = [
@@ -835,6 +909,96 @@ describe("ACPAgentSession Zed parity", () => {
outcome: { outcome: "selected", optionId: "allow-once" },
});
});
test("maps Copilot Allow All mode to allow_all ACP config on session start", async () => {
const setSessionConfigOption = vi.fn(async () => ({
configOptions: [
copilotModeConfigOption("https://agentclientprotocol.com/protocol/session-modes#agent"),
copilotAllowAllConfigOption("on"),
],
}));
const setSessionMode = vi.fn(async () => undefined);
const session = createCopilotSessionWithConfig(COPILOT_ALLOW_ALL_MODE_ID);
const { internals } = prepareConfiguredOverrideSession(session, {
currentMode: "https://agentclientprotocol.com/protocol/session-modes#agent",
availableModes: COPILOT_MODES,
configOptions: [
copilotModeConfigOption("https://agentclientprotocol.com/protocol/session-modes#agent"),
copilotAllowAllConfigOption("off"),
],
connection: { setSessionConfigOption, setSessionMode },
});
const events: AgentStreamEvent[] = [];
const unsubscribe = session.subscribe((event) => events.push(event));
await internals.applyConfiguredOverrides();
unsubscribe();
expect(setSessionConfigOption).toHaveBeenCalledWith({
sessionId: "session-1",
configId: "allow_all",
value: "on",
});
expect(setSessionMode).not.toHaveBeenCalled();
await expect(session.getCurrentMode()).resolves.toBe(COPILOT_ALLOW_ALL_MODE_ID);
expect(events.some((event) => event.type === "permission_requested")).toBe(false);
});
test("switching Copilot away from Allow All turns allow_all off before setting the ACP mode", async () => {
const setSessionConfigOption = vi.fn(async (input: { value: string }) => ({
configOptions: [
copilotModeConfigOption("https://agentclientprotocol.com/protocol/session-modes#agent"),
copilotAllowAllConfigOption(input.value === "on" ? "on" : "off"),
],
}));
const setSessionMode = vi.fn(async () => undefined);
const session = createCopilotSessionWithConfig(COPILOT_ALLOW_ALL_MODE_ID);
prepareConfiguredOverrideSession(session, {
currentMode: COPILOT_ALLOW_ALL_MODE_ID,
availableModes: COPILOT_MODES,
configOptions: [
copilotModeConfigOption(COPILOT_ALLOW_ALL_MODE_ID),
copilotAllowAllConfigOption("on"),
],
connection: { setSessionConfigOption, setSessionMode },
});
await session.setMode("https://agentclientprotocol.com/protocol/session-modes#agent");
expect(setSessionConfigOption).toHaveBeenCalledWith({
sessionId: "session-1",
configId: "allow_all",
value: "off",
});
expect(setSessionMode).toHaveBeenCalledWith({
sessionId: "session-1",
modeId: "https://agentclientprotocol.com/protocol/session-modes#agent",
});
});
test("trusts Copilot allow_all config updates as the current mode source", async () => {
const session = createCopilotSessionWithConfig();
const internals = asInternals<ACPSessionInternals>(session);
const events = internals.translateSessionUpdate({
sessionUpdate: "config_option_update",
configOptions: [
copilotModeConfigOption("https://agentclientprotocol.com/protocol/session-modes#agent"),
copilotAllowAllConfigOption("on"),
],
});
expect(events).toMatchObject([
{
type: "mode_changed",
provider: "copilot",
currentModeId: COPILOT_ALLOW_ALL_MODE_ID,
availableModes: expect.arrayContaining([
expect.objectContaining({ id: COPILOT_ALLOW_ALL_MODE_ID, label: "Allow All" }),
]),
},
]);
await expect(session.getCurrentMode()).resolves.toBe(COPILOT_ALLOW_ALL_MODE_ID);
});
});
describe("deriveModelDefinitionsFromACP", () => {
@@ -226,7 +226,13 @@ interface ACPAgentClientOptions {
defaultModes?: AgentMode[];
modelTransformer?: (models: AgentModelDefinition[]) => AgentModelDefinition[];
sessionResponseTransformer?: (response: SessionStateResponse) => SessionStateResponse;
configOptionsTransformer?: (configOptions: SessionConfigOption[]) => SessionConfigOption[];
modeIdTransformer?: (modeId: string) => string | null;
toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
providerModeWriter?: (
context: ACPProviderModeWriterContext,
) => Promise<ACPProviderModeWriteResult>;
beforeModeWriter?: (context: ACPProviderModeWriterContext) => Promise<ACPBeforeModeWriteResult>;
thinkingOptionWriter?: (
connection: ClientSideConnection,
sessionId: string,
@@ -245,7 +251,13 @@ interface ACPAgentSessionOptions {
defaultModes: AgentMode[];
modelTransformer?: (models: AgentModelDefinition[]) => AgentModelDefinition[];
sessionResponseTransformer?: (response: SessionStateResponse) => SessionStateResponse;
configOptionsTransformer?: (configOptions: SessionConfigOption[]) => SessionConfigOption[];
modeIdTransformer?: (modeId: string) => string | null;
toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
providerModeWriter?: (
context: ACPProviderModeWriterContext,
) => Promise<ACPProviderModeWriteResult>;
beforeModeWriter?: (context: ACPProviderModeWriterContext) => Promise<ACPBeforeModeWriteResult>;
thinkingOptionWriter?: (
connection: ClientSideConnection,
sessionId: string,
@@ -337,6 +349,26 @@ interface ACPModelSelection {
hasAvailableModels: boolean;
}
export interface ACPProviderModeWriterContext {
connection: ClientSideConnection;
sessionId: string;
requestedModeId: string;
currentModeId: string | null;
selection: ACPModeSelection;
configOptions: SessionConfigOption[];
logger: Logger;
}
export interface ACPProviderModeWriteResult {
handled: boolean;
currentModeId?: string;
configOptions?: SessionConfigOption[];
}
export interface ACPBeforeModeWriteResult {
configOptions?: SessionConfigOption[];
}
export function mapACPUsage(usage: Usage | null | undefined): AgentUsage | undefined {
if (!usage) {
return undefined;
@@ -465,7 +497,17 @@ export class ACPAgentClient implements AgentClient {
private readonly sessionResponseTransformer?: (
response: SessionStateResponse,
) => SessionStateResponse;
private readonly configOptionsTransformer?: (
configOptions: SessionConfigOption[],
) => SessionConfigOption[];
private readonly modeIdTransformer?: (modeId: string) => string | null;
private readonly toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
private readonly providerModeWriter?: (
context: ACPProviderModeWriterContext,
) => Promise<ACPProviderModeWriteResult>;
private readonly beforeModeWriter?: (
context: ACPProviderModeWriterContext,
) => Promise<ACPBeforeModeWriteResult>;
private readonly thinkingOptionWriter?: (
connection: ClientSideConnection,
sessionId: string,
@@ -483,7 +525,11 @@ export class ACPAgentClient implements AgentClient {
this.defaultModes = options.defaultModes ?? [];
this.modelTransformer = options.modelTransformer;
this.sessionResponseTransformer = options.sessionResponseTransformer;
this.configOptionsTransformer = options.configOptionsTransformer;
this.modeIdTransformer = options.modeIdTransformer;
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
this.providerModeWriter = options.providerModeWriter;
this.beforeModeWriter = options.beforeModeWriter;
this.thinkingOptionWriter = options.thinkingOptionWriter;
this.waitForInitialCommands = options.waitForInitialCommands ?? false;
this.initialCommandsWaitTimeoutMs = options.initialCommandsWaitTimeoutMs ?? 1500;
@@ -504,7 +550,11 @@ export class ACPAgentClient implements AgentClient {
defaultModes: this.defaultModes,
modelTransformer: this.modelTransformer,
sessionResponseTransformer: this.sessionResponseTransformer,
configOptionsTransformer: this.configOptionsTransformer,
modeIdTransformer: this.modeIdTransformer,
toolSnapshotTransformer: this.toolSnapshotTransformer,
providerModeWriter: this.providerModeWriter,
beforeModeWriter: this.beforeModeWriter,
thinkingOptionWriter: this.thinkingOptionWriter,
capabilities: this.capabilities,
launchEnv: launchContext?.env,
@@ -545,7 +595,11 @@ export class ACPAgentClient implements AgentClient {
defaultModes: this.defaultModes,
modelTransformer: this.modelTransformer,
sessionResponseTransformer: this.sessionResponseTransformer,
configOptionsTransformer: this.configOptionsTransformer,
modeIdTransformer: this.modeIdTransformer,
toolSnapshotTransformer: this.toolSnapshotTransformer,
providerModeWriter: this.providerModeWriter,
beforeModeWriter: this.beforeModeWriter,
thinkingOptionWriter: this.thinkingOptionWriter,
capabilities: this.capabilities,
handle,
@@ -747,7 +801,16 @@ export class ACPAgentClient implements AgentClient {
}
protected transformSessionResponse(response: SessionStateResponse): SessionStateResponse {
return this.sessionResponseTransformer ? this.sessionResponseTransformer(response) : response;
const transformed = this.sessionResponseTransformer
? this.sessionResponseTransformer(response)
: response;
if (!this.configOptionsTransformer || !transformed.configOptions) {
return transformed;
}
return {
...transformed,
configOptions: this.configOptionsTransformer(transformed.configOptions),
};
}
}
@@ -763,7 +826,17 @@ export class ACPAgentSession implements AgentSession, ACPClient {
private readonly sessionResponseTransformer?: (
response: SessionStateResponse,
) => SessionStateResponse;
private readonly configOptionsTransformer?: (
configOptions: SessionConfigOption[],
) => SessionConfigOption[];
private readonly modeIdTransformer?: (modeId: string) => string | null;
private readonly toolSnapshotTransformer?: (snapshot: ACPToolSnapshot) => ACPToolSnapshot;
private readonly providerModeWriter?: (
context: ACPProviderModeWriterContext,
) => Promise<ACPProviderModeWriteResult>;
private readonly beforeModeWriter?: (
context: ACPProviderModeWriterContext,
) => Promise<ACPBeforeModeWriteResult>;
private readonly thinkingOptionWriter?: (
connection: ClientSideConnection,
sessionId: string,
@@ -814,7 +887,11 @@ export class ACPAgentSession implements AgentSession, ACPClient {
this.defaultModes = options.defaultModes;
this.modelTransformer = options.modelTransformer;
this.sessionResponseTransformer = options.sessionResponseTransformer;
this.configOptionsTransformer = options.configOptionsTransformer;
this.modeIdTransformer = options.modeIdTransformer;
this.toolSnapshotTransformer = options.toolSnapshotTransformer;
this.providerModeWriter = options.providerModeWriter;
this.beforeModeWriter = options.beforeModeWriter;
this.thinkingOptionWriter = options.thinkingOptionWriter;
this.availableModes = options.defaultModes;
this.launchEnv = options.launchEnv;
@@ -1068,6 +1145,25 @@ export class ACPAgentSession implements AgentSession, ACPClient {
throw new Error("ACP session not initialized");
}
const context = this.createProviderModeWriterContext(modeId, selection);
const providerResult = this.providerModeWriter
? await this.providerModeWriter(context)
: { handled: false };
if (providerResult.handled) {
this.currentMode = providerResult.currentModeId ?? modeId;
if (providerResult.configOptions) {
this.configOptions = this.transformConfigOptions(providerResult.configOptions);
}
this.availableModes = deriveModesFromACP(this.defaultModes, null, this.configOptions).modes;
this.pushEvent({
type: "mode_changed",
provider: this.provider,
currentModeId: this.currentMode,
availableModes: [...this.availableModes],
});
return;
}
if (selection.hasAvailableModes) {
if (!selection.availableMode) {
this.warnInvalidSelection(
@@ -1078,7 +1174,32 @@ export class ACPAgentSession implements AgentSession, ACPClient {
);
return;
}
} else {
const modeOption = selection.configOption;
if (!modeOption) {
throw new Error(`${this.provider} does not expose ACP mode switching`);
}
if (!selection.configChoice) {
this.warnInvalidSelection(
modeId,
`is not valid ${this.provider} mode config option. Available options: ${flattenSelectOptions(
modeOption.options,
)
.map((option) => option.value)
.join(", ")}`,
);
return;
}
}
if (this.beforeModeWriter) {
const beforeResult = await this.beforeModeWriter(context);
if (beforeResult?.configOptions) {
this.configOptions = this.transformConfigOptions(beforeResult.configOptions);
}
}
if (selection.hasAvailableModes) {
await this.connection.setSessionMode({ sessionId: this.sessionId, modeId });
this.currentMode = modeId;
this.pushEvent({
@@ -1094,17 +1215,6 @@ export class ACPAgentSession implements AgentSession, ACPClient {
if (!modeOption) {
throw new Error(`${this.provider} does not expose ACP mode switching`);
}
if (!selection.configChoice) {
this.warnInvalidSelection(
modeId,
`is not valid ${this.provider} mode config option. Available options: ${flattenSelectOptions(
modeOption.options,
)
.map((option) => option.value)
.join(", ")}`,
);
return;
}
const response = await this.connection.setSessionConfigOption({
sessionId: this.sessionId,
@@ -1127,6 +1237,24 @@ export class ACPAgentSession implements AgentSession, ACPClient {
});
}
private createProviderModeWriterContext(
requestedModeId: string,
selection: ACPModeSelection,
): ACPProviderModeWriterContext {
if (!this.connection || !this.sessionId) {
throw new Error("ACP session not initialized");
}
return {
connection: this.connection,
sessionId: this.sessionId,
requestedModeId,
currentModeId: this.currentMode,
selection,
configOptions: this.configOptions,
logger: this.logger,
};
}
async setModel(modelId: string | null): Promise<void> {
if (!this.connection || !this.sessionId) {
throw new Error("ACP session not initialized");
@@ -1281,9 +1409,9 @@ export class ACPAgentSession implements AgentSession, ACPClient {
requestedValue: string;
label: string;
}): string {
this.configOptions = response.configOptions;
this.configOptions = this.transformConfigOptions(response.configOptions);
const responseOption = findSelectConfigOption({
configOptions: response.configOptions,
configOptions: this.configOptions,
category,
id: configId,
});
@@ -1409,7 +1537,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
}
async requestPermission(params: RequestPermissionRequest): Promise<RequestPermissionResponse> {
// Match Zed acp.rs:3189-3220 — pure pass-through. Accepted UX regression: Copilot Autopilot will now prompt the user for every tool request.
// Match Zed acp.rs:3189-3220: generic ACP permission requests stay pure pass-through.
const requestId = randomUUID();
let toolSnapshot =
this.toolCalls.get(params.toolCall.toolCallId) ??
@@ -1622,7 +1750,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
? this.sessionResponseTransformer(response)
: response;
this.configOptions = transformed.configOptions ?? [];
this.configOptions = this.transformConfigOptions(transformed.configOptions ?? []);
const modeInfo = deriveModesFromACP(this.defaultModes, transformed.modes, this.configOptions);
this.availableModes = modeInfo.modes;
@@ -1635,6 +1763,16 @@ export class ACPAgentSession implements AgentSession, ACPClient {
deriveCurrentConfigValue(this.configOptions, "thought_level") ?? this.thinkingOptionId;
}
private transformConfigOptions(configOptions: SessionConfigOption[]): SessionConfigOption[] {
return this.configOptionsTransformer
? this.configOptionsTransformer(configOptions)
: configOptions;
}
private transformModeId(modeId: string): string | null {
return this.modeIdTransformer ? this.modeIdTransformer(modeId) : modeId;
}
private async applyConfiguredOverrides(): Promise<void> {
const configuredModeId = this.config.modeId;
if (configuredModeId && configuredModeId !== this.currentMode) {
@@ -1772,11 +1910,11 @@ export class ACPAgentSession implements AgentSession, ACPClient {
}
private handleCurrentModeUpdate(update: CurrentModeUpdate): void {
this.currentMode = update.currentModeId;
this.currentMode = this.transformModeId(update.currentModeId);
}
private handleConfigOptionUpdate(update: ConfigOptionUpdate): AgentStreamEvent[] {
this.configOptions = update.configOptions;
this.configOptions = this.transformConfigOptions(update.configOptions);
const modeInfo = deriveModesFromACP(this.defaultModes, null, this.configOptions);
const nextMode = modeInfo.currentModeId;
const nextModel = deriveCurrentConfigValue(this.configOptions, "model");
@@ -1,10 +1,17 @@
import type { Logger } from "pino";
import { homedir } from "node:os";
import type { SessionConfigOption } from "@agentclientprotocol/sdk";
import type { AgentCapabilityFlags, AgentMode } from "../agent-sdk-types.js";
import type { ProviderRuntimeSettings } from "../provider-launch-config.js";
import { findExecutable } from "../../../utils/executable.js";
import { ACPAgentClient } from "./acp-agent.js";
import {
ACPAgentClient,
type ACPBeforeModeWriteResult,
type ACPProviderModeWriteResult,
type ACPProviderModeWriterContext,
type SessionStateResponse,
} from "./acp-agent.js";
import {
formatDiagnosticStatus,
formatProviderDiagnostic,
@@ -22,21 +29,31 @@ const COPILOT_CAPABILITIES: AgentCapabilityFlags = {
supportsToolInvocations: true,
};
const COPILOT_MODES: AgentMode[] = [
const COPILOT_AGENT_MODE_ID = "https://agentclientprotocol.com/protocol/session-modes#agent";
const COPILOT_PLAN_MODE_ID = "https://agentclientprotocol.com/protocol/session-modes#plan";
const COPILOT_AUTOPILOT_MODE_ID =
"https://agentclientprotocol.com/protocol/session-modes#autopilot";
export const COPILOT_ALLOW_ALL_MODE_ID = "allow-all";
const COPILOT_ALLOW_ALL_CONFIG_ID = "allow_all";
const COPILOT_ALLOW_ALL_ON = "on";
const COPILOT_ALLOW_ALL_OFF = "off";
type SelectConfigOption = Extract<SessionConfigOption, { type: "select" }>;
export const COPILOT_MODES: AgentMode[] = [
{
id: "https://agentclientprotocol.com/protocol/session-modes#agent",
id: COPILOT_AGENT_MODE_ID,
label: "Agent",
description: "Default agent mode for conversational interactions",
},
{
id: "https://agentclientprotocol.com/protocol/session-modes#plan",
id: COPILOT_PLAN_MODE_ID,
label: "Plan",
description: "Plan mode for creating and executing multi-step plans",
},
{
id: "https://agentclientprotocol.com/protocol/session-modes#autopilot",
label: "Autopilot",
description: "Autonomous mode that runs until task completion without user interaction",
id: COPILOT_ALLOW_ALL_MODE_ID,
label: "Allow All",
description: "Automatically approves all Copilot tool, path, and URL requests.",
},
];
@@ -53,6 +70,11 @@ export class CopilotACPAgentClient extends ACPAgentClient {
runtimeSettings: options.runtimeSettings,
defaultCommand: ["copilot", "--acp"],
defaultModes: COPILOT_MODES,
sessionResponseTransformer: transformCopilotSessionResponse,
configOptionsTransformer: transformCopilotConfigOptions,
modeIdTransformer: transformCopilotModeId,
providerModeWriter: writeCopilotProviderMode,
beforeModeWriter: beforeCopilotModeWriter,
capabilities: COPILOT_CAPABILITIES,
});
}
@@ -113,3 +135,122 @@ export class CopilotACPAgentClient extends ACPAgentClient {
}
}
}
export function transformCopilotSessionResponse(
response: SessionStateResponse,
): SessionStateResponse {
if (!response.modes) {
return response;
}
const allowAllEnabled = isCopilotAllowAllEnabled(response.configOptions ?? []);
return {
...response,
modes: {
...response.modes,
availableModes: response.modes.availableModes
?.filter(
(mode) => mode.id !== COPILOT_AUTOPILOT_MODE_ID && mode.id !== COPILOT_ALLOW_ALL_MODE_ID,
)
.concat({
id: COPILOT_ALLOW_ALL_MODE_ID,
name: "Allow All",
description: "Automatically approves all Copilot tool, path, and URL requests.",
}),
currentModeId: allowAllEnabled
? COPILOT_ALLOW_ALL_MODE_ID
: (transformCopilotModeId(response.modes.currentModeId ?? COPILOT_AGENT_MODE_ID) ??
COPILOT_AGENT_MODE_ID),
},
};
}
export function transformCopilotConfigOptions(
configOptions: SessionConfigOption[],
): SessionConfigOption[] {
const allowAllEnabled = isCopilotAllowAllEnabled(configOptions);
return configOptions.map((option) => {
if (option.type !== "select" || option.category !== "mode") {
return option;
}
// Trust Copilot's allow_all config value as the source of truth when it changes in-process.
const options = flattenCopilotModeOptions(option.options)
.filter(
(choice) =>
choice.value !== COPILOT_AUTOPILOT_MODE_ID && choice.value !== COPILOT_ALLOW_ALL_MODE_ID,
)
.concat({
value: COPILOT_ALLOW_ALL_MODE_ID,
name: "Allow All",
description: "Automatically approves all Copilot tool, path, and URL requests.",
});
return {
...option,
currentValue: allowAllEnabled
? COPILOT_ALLOW_ALL_MODE_ID
: (transformCopilotModeId(option.currentValue) ?? COPILOT_AGENT_MODE_ID),
options,
};
});
}
function flattenCopilotModeOptions(
options: SelectConfigOption["options"],
): Array<{ value: string; name: string; description?: string | null }> {
const flattened: Array<{ value: string; name: string; description?: string | null }> = [];
for (const option of options) {
if ("value" in option) {
flattened.push(option);
continue;
}
flattened.push(...option.options);
}
return flattened;
}
export function transformCopilotModeId(modeId: string): string | null {
return modeId === COPILOT_AUTOPILOT_MODE_ID ? COPILOT_AGENT_MODE_ID : modeId;
}
export async function writeCopilotProviderMode(
context: ACPProviderModeWriterContext,
): Promise<ACPProviderModeWriteResult> {
if (context.requestedModeId !== COPILOT_ALLOW_ALL_MODE_ID) {
return { handled: false };
}
const response = await context.connection.setSessionConfigOption({
sessionId: context.sessionId,
configId: COPILOT_ALLOW_ALL_CONFIG_ID,
value: COPILOT_ALLOW_ALL_ON,
});
return {
handled: true,
currentModeId: COPILOT_ALLOW_ALL_MODE_ID,
configOptions: response.configOptions,
};
}
export async function beforeCopilotModeWriter(
context: ACPProviderModeWriterContext,
): Promise<ACPBeforeModeWriteResult> {
if (
context.currentModeId !== COPILOT_ALLOW_ALL_MODE_ID ||
context.requestedModeId === COPILOT_ALLOW_ALL_MODE_ID
) {
return {};
}
const response = await context.connection.setSessionConfigOption({
sessionId: context.sessionId,
configId: COPILOT_ALLOW_ALL_CONFIG_ID,
value: COPILOT_ALLOW_ALL_OFF,
});
return { configOptions: response.configOptions };
}
function isCopilotAllowAllEnabled(configOptions: SessionConfigOption[]): boolean {
return configOptions.some(
(option) =>
option.type === "select" &&
option.id === COPILOT_ALLOW_ALL_CONFIG_ID &&
option.currentValue === COPILOT_ALLOW_ALL_ON,
);
}
@@ -44,7 +44,7 @@ export const agentConfigs = {
provider: "copilot",
model: "claude-haiku-4.5",
modes: {
full: "https://agentclientprotocol.com/protocol/session-modes#autopilot",
full: "allow-all",
ask: "https://agentclientprotocol.com/protocol/session-modes#agent",
},
},