From 4e175527a2b241a68afd5f1509a8bebc21a44dfe Mon Sep 17 00:00:00 2001 From: Sri Pasumarthi <111310667+sripasg@users.noreply.github.com> Date: Fri, 1 May 2026 16:00:10 -0700 Subject: [PATCH] fix(acp): resolve agent mode disconnect and improve mode awareness (#26332) --- packages/cli/src/acp/acpRpcDispatcher.ts | 4 ++ packages/cli/src/acp/acpSession.test.ts | 22 +++++++++++ packages/cli/src/acp/acpSession.ts | 29 +++++++++++++- packages/cli/src/acp/acpSessionManager.ts | 13 +++++++ packages/core/src/config/config.test.ts | 1 + packages/core/src/config/config.ts | 34 +++++++++-------- .../core/__snapshots__/prompts.test.ts.snap | 38 +++++++++---------- packages/core/src/core/client.test.ts | 23 +++++++++++ packages/core/src/core/client.ts | 20 +++++++++- packages/core/src/prompts/promptProvider.ts | 1 + packages/core/src/prompts/snippets.ts | 16 ++++++-- packages/core/src/utils/events.ts | 25 ++++++++++++ 12 files changed, 186 insertions(+), 40 deletions(-) diff --git a/packages/cli/src/acp/acpRpcDispatcher.ts b/packages/cli/src/acp/acpRpcDispatcher.ts index 97fb0d4011..a7d7d26e61 100644 --- a/packages/cli/src/acp/acpRpcDispatcher.ts +++ b/packages/cli/src/acp/acpRpcDispatcher.ts @@ -33,6 +33,10 @@ export class GeminiAgent { this.sessionManager = new AcpSessionManager(settings, argv, connection); } + dispose(): void { + this.sessionManager.dispose(); + } + async initialize( args: acp.InitializeRequest, ): Promise { diff --git a/packages/cli/src/acp/acpSession.test.ts b/packages/cli/src/acp/acpSession.test.ts index c87c1cc4b4..14f04ba7c5 100644 --- a/packages/cli/src/acp/acpSession.test.ts +++ b/packages/cli/src/acp/acpSession.test.ts @@ -564,4 +564,26 @@ describe('Session', () => { expect(result.stopReason).toBe('max_turn_requests'); }); + + it('should send sessionUpdate when approval mode changes', async () => { + const { coreEvents, CoreEvent, ApprovalMode } = await import( + '@google/gemini-cli-core' + ); + + coreEvents.emit(CoreEvent.ApprovalModeChanged, { + sessionId: 'session-1', + mode: ApprovalMode.PLAN, + }); + + expect(mockConnection.sessionUpdate).toHaveBeenCalledWith({ + sessionId: 'session-1', + update: { + sessionUpdate: 'agent_message_chunk', + content: { + type: 'text', + text: `[MODE_UPDATE] ${ApprovalMode.PLAN}`, + }, + }, + }); + }); }); diff --git a/packages/cli/src/acp/acpSession.ts b/packages/cli/src/acp/acpSession.ts index bcc8a86248..da7401cba1 100644 --- a/packages/cli/src/acp/acpSession.ts +++ b/packages/cli/src/acp/acpSession.ts @@ -8,6 +8,9 @@ import { type ApprovalMode, type ConversationRecord, CoreToolCallStatus, + coreEvents, + CoreEvent, + type ApprovalModeChangedPayload, logToolCall, convertToFunctionResponse, ToolConfirmationOutcome, @@ -69,7 +72,31 @@ export class Session { private readonly context: AgentLoopContext, private readonly connection: acp.AgentSideConnection, private readonly settings: LoadedSettings, - ) {} + ) { + coreEvents.on( + CoreEvent.ApprovalModeChanged, + this.handleApprovalModeChanged, + ); + } + + private handleApprovalModeChanged = (payload: ApprovalModeChangedPayload) => { + if (payload.sessionId === this.id) { + void this.sendUpdate({ + sessionUpdate: 'agent_message_chunk', + content: { + type: 'text', + text: `[MODE_UPDATE] ${payload.mode}`, + }, + }); + } + }; + + dispose(): void { + coreEvents.off( + CoreEvent.ApprovalModeChanged, + this.handleApprovalModeChanged, + ); + } async cancelPendingPrompt(): Promise { if (!this.pendingPrompt) { diff --git a/packages/cli/src/acp/acpSessionManager.ts b/packages/cli/src/acp/acpSessionManager.ts index 828dae9b14..2109257317 100644 --- a/packages/cli/src/acp/acpSessionManager.ts +++ b/packages/cli/src/acp/acpSessionManager.ts @@ -48,6 +48,13 @@ export class AcpSessionManager { return this.sessions.get(sessionId); } + dispose(): void { + for (const session of this.sessions.values()) { + session.dispose(); + } + this.sessions.clear(); + } + async newSession( { cwd, mcpServers }: acp.NewSessionRequest, authDetails: AuthDetails, @@ -183,6 +190,12 @@ export class AcpSessionManager { this.connection, this.settings, ); + + const existingSession = this.sessions.get(sessionId); + if (existingSession) { + existingSession.dispose(); + } + this.sessions.set(sessionId, session); // Stream history back to client diff --git a/packages/core/src/config/config.test.ts b/packages/core/src/config/config.test.ts index 982516aade..843acda12f 100644 --- a/packages/core/src/config/config.test.ts +++ b/packages/core/src/config/config.test.ts @@ -203,6 +203,7 @@ const mockCoreEvents = vi.hoisted(() => ({ emitConsoleLog: vi.fn(), emitQuotaChanged: vi.fn(), on: vi.fn(), + emit: vi.fn(), })); const mockSetGlobalProxy = vi.hoisted(() => vi.fn()); diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 704eb0f1db..9d52450d03 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -2697,26 +2697,28 @@ export class Config implements McpContext, AgentLoopContext { this, new ApprovalModeSwitchEvent(currentMode, mode), ); - } - this.policyEngine.setApprovalMode(mode); - this.refreshSandboxManager(); + this.policyEngine.setApprovalMode(mode); + this.refreshSandboxManager(); + coreEvents.emit(CoreEvent.ApprovalModeChanged, { + sessionId: this.getSessionId(), + mode, + }); - const isPlanModeTransition = - currentMode !== mode && - (currentMode === ApprovalMode.PLAN || mode === ApprovalMode.PLAN); - const isYoloModeTransition = - currentMode !== mode && - (currentMode === ApprovalMode.YOLO || mode === ApprovalMode.YOLO); + const isPlanModeTransition = + currentMode === ApprovalMode.PLAN || mode === ApprovalMode.PLAN; + const isYoloModeTransition = + currentMode === ApprovalMode.YOLO || mode === ApprovalMode.YOLO; - if (isPlanModeTransition || isYoloModeTransition) { - if (this._geminiClient?.isInitialized()) { - this._geminiClient.clearCurrentSequenceModel(); - this._geminiClient.setTools().catch((err) => { - debugLogger.error('Failed to update tools', err); - }); + if (isPlanModeTransition || isYoloModeTransition) { + if (this._geminiClient?.isInitialized()) { + this._geminiClient.clearCurrentSequenceModel(); + this._geminiClient.setTools().catch((err) => { + debugLogger.error('Failed to update tools', err); + }); + } + this.updateSystemInstructionIfInitialized(); } - this.updateSystemInstructionIfInitialized(); } } diff --git a/packages/core/src/core/__snapshots__/prompts.test.ts.snap b/packages/core/src/core/__snapshots__/prompts.test.ts.snap index 2116b0cfd3..785ce7c0ee 100644 --- a/packages/core/src/core/__snapshots__/prompts.test.ts.snap +++ b/packages/core/src/core/__snapshots__/prompts.test.ts.snap @@ -1,7 +1,7 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > Approved Plan in Plan Mode > should NOT include approved plan section if no plan is set in config 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Plan** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -181,7 +181,7 @@ ONLY use the built-in \`exit_plan_mode\` tool to present the plan for formal app `; exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > Approved Plan in Plan Mode > should include approved plan path when set in config 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Plan** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -482,7 +482,7 @@ Your core function is efficient and safe assistance. Balance extreme conciseness `; exports[`Core System Prompt (prompts.ts) > ApprovalMode in System Prompt > should include PLAN mode instructions 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Plan** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -662,7 +662,7 @@ ONLY use the built-in \`exit_plan_mode\` tool to present the plan for formal app `; exports[`Core System Prompt (prompts.ts) > should append userMemory with separator when provided 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -843,7 +843,7 @@ Be extra polite. `; exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator (enabled=false) 1`] = ` -"You are Gemini CLI, an autonomous CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an autonomous CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -976,7 +976,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should handle CodebaseInvestigator (enabled=true) 1`] = ` -"You are Gemini CLI, an autonomous CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an autonomous CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -1591,7 +1591,7 @@ Your core function is efficient and safe assistance. Balance extreme conciseness `; exports[`Core System Prompt (prompts.ts) > should include available_skills with updated verbiage for preview models 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -1768,7 +1768,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should include correct sandbox instructions for SANDBOX=sandbox-exec 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -1936,7 +1936,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should include correct sandbox instructions for SANDBOX=true 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -2104,7 +2104,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should include correct sandbox instructions for SANDBOX=undefined 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -2268,7 +2268,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should include mandate to distinguish between Directives and Inquiries 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -2432,7 +2432,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should include modern approved plan instructions with completion in DEFAULT mode when approvedPlanPath is set 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -2590,7 +2590,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should include planning phase suggestion when enter_plan_mode tool is enabled 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -2722,7 +2722,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should include sub-agents in XML for preview models when invoke_agent tool is enabled 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -3014,7 +3014,7 @@ Your core function is efficient and safe assistance. Balance extreme conciseness `; exports[`Core System Prompt (prompts.ts) > should include the TASK MANAGEMENT PROTOCOL when task tracker is enabled 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -3436,7 +3436,7 @@ project context `; exports[`Core System Prompt (prompts.ts) > should return the base prompt when userMemory is empty string 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -3600,7 +3600,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should return the base prompt when userMemory is whitespace only 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -3878,7 +3878,7 @@ Your core function is efficient and safe assistance. Balance extreme conciseness `; exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for preview flash model 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates @@ -4042,7 +4042,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi `; exports[`Core System Prompt (prompts.ts) > should use chatty system prompt for preview model 1`] = ` -"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively. +"You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. You are currently operating in **Default** mode. Your primary goal is to help users safely and effectively. # Core Mandates diff --git a/packages/core/src/core/client.test.ts b/packages/core/src/core/client.test.ts index d9d49379e4..ece8353b29 100644 --- a/packages/core/src/core/client.test.ts +++ b/packages/core/src/core/client.test.ts @@ -2055,6 +2055,29 @@ ${JSON.stringify( ); }); + it('should update system instruction when ApprovalModeChanged event is emitted', async () => { + const { ApprovalMode } = await import('../policy/types.js'); + + vi.mocked(mockConfig.getSessionId).mockReturnValue('session-1'); + vi.mocked(mockConfig.getSystemInstructionMemory).mockReturnValue( + 'Current Memory', + ); + + const { getCoreSystemPrompt } = await import('./prompts.js'); + const mockGetCoreSystemPrompt = vi.mocked(getCoreSystemPrompt); + mockGetCoreSystemPrompt.mockClear(); + + coreEvents.emit(CoreEvent.ApprovalModeChanged, { + sessionId: 'session-1', + mode: ApprovalMode.YOLO, + }); + + expect(mockGetCoreSystemPrompt).toHaveBeenCalledWith( + mockConfig, + 'Current Memory', + ); + }); + it('should propagate InvalidStream events without injecting "Please continue." or recursing', async () => { // Arrange: a single turn that yields an InvalidStream event. const mockStream = (async function* () { diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index cc7b49366e..ce544a0e30 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -67,7 +67,11 @@ import { } from '../availability/policyHelpers.js'; import { getDisplayString, resolveModel } from '../config/models.js'; import { partToString } from '../utils/partUtils.js'; -import { coreEvents, CoreEvent } from '../utils/events.js'; +import { + coreEvents, + CoreEvent, + type ApprovalModeChangedPayload, +} from '../utils/events.js'; import { initializeContextManager } from '../context/initializer.js'; const MAX_TURNS = 100; @@ -116,6 +120,10 @@ export class GeminiClient { coreEvents.on(CoreEvent.ModelChanged, this.handleModelChanged); coreEvents.on(CoreEvent.MemoryChanged, this.handleMemoryChanged); + coreEvents.on( + CoreEvent.ApprovalModeChanged, + this.handleApprovalModeChanged, + ); } private get config(): Config { @@ -130,6 +138,12 @@ export class GeminiClient { this.updateSystemInstruction(); }; + private handleApprovalModeChanged = (payload: ApprovalModeChangedPayload) => { + if (payload.sessionId === this.config.getSessionId()) { + this.updateSystemInstruction(); + } + }; + clearCurrentSequenceModel(): void { this.currentSequenceModel = null; } @@ -314,6 +328,10 @@ export class GeminiClient { dispose() { coreEvents.off(CoreEvent.ModelChanged, this.handleModelChanged); coreEvents.off(CoreEvent.MemoryChanged, this.handleMemoryChanged); + coreEvents.off( + CoreEvent.ApprovalModeChanged, + this.handleApprovalModeChanged, + ); } async resumeChat( diff --git a/packages/core/src/prompts/promptProvider.ts b/packages/core/src/prompts/promptProvider.ts index fac9085392..2c1f9e8652 100644 --- a/packages/core/src/prompts/promptProvider.ts +++ b/packages/core/src/prompts/promptProvider.ts @@ -142,6 +142,7 @@ export class PromptProvider { const options: snippets.SystemPromptOptions = { preamble: this.withSection('preamble', () => ({ interactive: interactiveMode, + approvalMode, })), coreMandates: this.withSection('coreMandates', () => ({ interactive: interactiveMode, diff --git a/packages/core/src/prompts/snippets.ts b/packages/core/src/prompts/snippets.ts index 5bd472fde5..d84c0cce90 100644 --- a/packages/core/src/prompts/snippets.ts +++ b/packages/core/src/prompts/snippets.ts @@ -37,6 +37,7 @@ import { } from '../tools/tool-names.js'; import type { HierarchicalMemory } from '../config/memory.js'; import { DEFAULT_CONTEXT_FILENAME } from '../tools/memoryTool.js'; +import type { ApprovalMode } from '../policy/types.js'; // --- Options Structs --- @@ -57,6 +58,7 @@ export interface SystemPromptOptions { export interface PreambleOptions { interactive: boolean; + approvalMode: ApprovalMode; } export interface CoreMandatesOptions { @@ -188,9 +190,17 @@ ${renderUserMemory(userMemory, contextFilenames)} export function renderPreamble(options?: PreambleOptions): string { if (!options) return ''; - return options.interactive - ? 'You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively.' - : 'You are Gemini CLI, an autonomous CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and effectively.'; + + let modeStr = 'Default'; + if (options.approvalMode === 'plan') modeStr = 'Plan'; + if (options.approvalMode === 'yolo') modeStr = 'YOLO'; + if (options.approvalMode === 'autoEdit') modeStr = 'Auto-Edit'; + + const base = options.interactive + ? 'You are Gemini CLI, an interactive CLI agent specializing in software engineering tasks.' + : 'You are Gemini CLI, an autonomous CLI agent specializing in software engineering tasks.'; + + return `${base} You are currently operating in **${modeStr}** mode. Your primary goal is to help users safely and effectively.`; } export function renderCoreMandates(options?: CoreMandatesOptions): string { diff --git a/packages/core/src/utils/events.ts b/packages/core/src/utils/events.ts index 0a85d466e4..d6a516a2a0 100644 --- a/packages/core/src/utils/events.ts +++ b/packages/core/src/utils/events.ts @@ -14,6 +14,7 @@ import type { KeychainAvailabilityEvent, } from '../telemetry/types.js'; import { debugLogger } from './debugLogger.js'; +import type { ApprovalMode } from '../policy/types.js'; /** * Defines the severity level for user-facing feedback. @@ -52,6 +53,20 @@ export interface ModelChangedPayload { model: string; } +/** + * Payload for the 'approval-mode-changed' event. + */ +export interface ApprovalModeChangedPayload { + /** + * The session ID associated with the mode change. + */ + sessionId: string; + /** + * The new approval mode. + */ + mode: ApprovalMode; +} + /** * Payload for the 'console-log' event. */ @@ -181,6 +196,7 @@ export interface QuotaChangedPayload { export enum CoreEvent { UserFeedback = 'user-feedback', ModelChanged = 'model-changed', + ApprovalModeChanged = 'approval-mode-changed', ConsoleLog = 'console-log', Output = 'output', MemoryChanged = 'memory-changed', @@ -215,6 +231,7 @@ export interface EditorSelectedPayload { export interface CoreEvents extends ExtensionEvents { [CoreEvent.UserFeedback]: [UserFeedbackPayload]; [CoreEvent.ModelChanged]: [ModelChangedPayload]; + [CoreEvent.ApprovalModeChanged]: [ApprovalModeChangedPayload]; [CoreEvent.ConsoleLog]: [ConsoleLogPayload]; [CoreEvent.Output]: [OutputPayload]; [CoreEvent.MemoryChanged]: [MemoryChangedPayload]; @@ -327,6 +344,14 @@ export class CoreEventEmitter extends EventEmitter { this.emit(CoreEvent.ModelChanged, payload); } + /** + * Notifies subscribers that the approval mode has changed. + */ + emitApprovalModeChanged(sessionId: string, mode: ApprovalMode): void { + const payload: ApprovalModeChangedPayload = { sessionId, mode }; + this.emit(CoreEvent.ApprovalModeChanged, payload); + } + /** * Notifies subscribers that settings have been modified. */