fix(core): resolve infinite loop and improve editor selection flow

- Fixes an infinite loop when using 'Modify with Editor' without a configured editor.
- Implements interactive editor selection via a UI dialog.
- Returns to the previous confirmation prompt if selection is cancelled or fails.
- Simplifies editor availability logic and removes deprecated sync functions.

Fixes #7669
This commit is contained in:
ehedlund
2026-02-04 14:00:08 -05:00
parent 37dabd873a
commit 02fc2aaef2
7 changed files with 147 additions and 379 deletions

View File

@@ -524,12 +524,22 @@ export const AppContainer = (props: AppContainerProps) => {
refreshStatic();
}, [refreshStatic, isAlternateBuffer, app, config]);
const [editorError, setEditorError] = useState<string | null>(null);
const {
isEditorDialogOpen,
openEditorDialog,
handleEditorSelect,
exitEditorDialog,
} = useEditorSettings(settings, setEditorError, historyManager.addItem);
useEffect(() => {
coreEvents.on(CoreEvent.ExternalEditorClosed, handleEditorClose);
coreEvents.on(CoreEvent.RequestEditorSelection, openEditorDialog);
return () => {
coreEvents.off(CoreEvent.ExternalEditorClosed, handleEditorClose);
coreEvents.off(CoreEvent.RequestEditorSelection, openEditorDialog);
};
}, [handleEditorClose]);
}, [handleEditorClose, openEditorDialog]);
useEffect(() => {
if (
@@ -543,6 +553,9 @@ export const AppContainer = (props: AppContainerProps) => {
}
}, [bannerVisible, bannerText, settings, config, refreshStatic]);
const { isSettingsDialogOpen, openSettingsDialog, closeSettingsDialog } =
useSettingsCommand();
const {
isThemeDialogOpen,
openThemeDialog,
@@ -738,17 +751,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
onAuthError,
]);
const [editorError, setEditorError] = useState<string | null>(null);
const {
isEditorDialogOpen,
openEditorDialog,
handleEditorSelect,
exitEditorDialog,
} = useEditorSettings(settings, setEditorError, historyManager.addItem);
const { isSettingsDialogOpen, openSettingsDialog, closeSettingsDialog } =
useSettingsCommand();
const { isModelDialogOpen, openModelDialog, closeModelDialog } =
useModelCommand();

View File

@@ -15,6 +15,8 @@ import {
allowEditorTypeInSandbox,
checkHasEditorType,
getEditorDisplayName,
coreEvents,
CoreEvent,
} from '@google/gemini-cli-core';
import type { UseHistoryManagerReturn } from './useHistoryManager.js';
@@ -66,6 +68,7 @@ export const useEditorSettings = (
);
setEditorError(null);
setIsEditorDialogOpen(false);
coreEvents.emit(CoreEvent.EditorSelected, { editor: editorType });
} catch (error) {
setEditorError(`Failed to set editor preference: ${error}`);
}
@@ -75,6 +78,7 @@ export const useEditorSettings = (
const exitEditorDialog = useCallback(() => {
setIsEditorDialogOpen(false);
coreEvents.emit(CoreEvent.EditorSelected, { editor: undefined });
}, []);
return {

View File

@@ -752,18 +752,19 @@ export class CoreToolScheduler {
} else if (outcome === ToolConfirmationOutcome.ModifyWithEditor) {
const waitingToolCall = toolCall as WaitingToolCall;
// Use resolveEditorAsync to check availability and auto-detect if needed
// Using async version to avoid blocking the event loop
const preferredEditor = this.getPreferredEditor();
const resolution = await resolveEditorAsync(preferredEditor);
const editor = await resolveEditorAsync(preferredEditor, signal);
if (!resolution.editor) {
// No editor available - emit error feedback and cancel the operation
// This fixes the infinite loop issue reported in #7669
if (resolution.error) {
coreEvents.emitFeedback('error', resolution.error);
}
this.cancelAll(signal);
if (!editor) {
// No editor available - emit error feedback and return to previous confirmation screen
coreEvents.emitFeedback(
'error',
'No external editor is available. Please run /editor to configure one.',
);
this.setStatusInternal(callId, 'awaiting_approval', signal, {
...waitingToolCall.confirmationDetails,
isModifying: false,
} as ToolCallConfirmationDetails);
return;
}
@@ -774,7 +775,7 @@ export class CoreToolScheduler {
const result = await this.toolModifier.handleModifyWithEditor(
waitingToolCall,
resolution.editor,
editor,
signal,
);

View File

@@ -162,14 +162,11 @@ export async function resolveConfirmation(
signal,
);
if (!modResult.success) {
// Editor is not available - emit error feedback and break the loop
// by cancelling the operation to prevent infinite loop
// Editor is not available - emit error feedback and stay in the loop
// to return to previous confirmation screen.
if (modResult.error) {
coreEvents.emitFeedback('error', modResult.error);
}
// Break the loop by changing outcome to Cancel
// This prevents the infinite loop issue reported in #7669
outcome = ToolConfirmationOutcome.Cancel;
}
} else if (response.payload && 'newContent' in response.payload) {
await handleInlineModification(deps, toolCall, response.payload, signal);
@@ -222,27 +219,21 @@ async function handleExternalModification(
): Promise<ExternalModificationResult> {
const { state, modifier, getPreferredEditor } = deps;
// Use the new resolveEditorAsync function which handles:
// 1. Checking if preferred editor is available
// 2. Auto-detecting an available editor if none is configured
// 3. Providing helpful error messages
// Using async version to avoid blocking the event loop
const preferredEditor = getPreferredEditor();
const resolution = await resolveEditorAsync(preferredEditor);
const editor = await resolveEditorAsync(preferredEditor, signal);
if (!resolution.editor) {
if (!editor) {
// No editor available - return failure with error message
return {
success: false,
error:
resolution.error ||
'No external editor is available. Please run /editor to configure one.',
};
}
const result = await modifier.handleModifyWithEditor(
state.firstActiveCall as WaitingToolCall,
resolution.editor,
editor,
signal,
);
if (result) {

View File

@@ -21,12 +21,10 @@ import {
allowEditorTypeInSandbox,
isEditorAvailable,
isEditorAvailableAsync,
detectFirstAvailableEditor,
detectFirstAvailableEditorAsync,
resolveEditor,
resolveEditorAsync,
type EditorType,
} from './editor.js';
import { coreEvents, CoreEvent } from './events.js';
import { exec, execSync, spawn, spawnSync } from 'node:child_process';
import { debugLogger } from './debugLogger.js';
@@ -550,132 +548,6 @@ describe('editor utils', () => {
});
});
describe('detectFirstAvailableEditor', () => {
it('should return undefined when no editors are installed', () => {
(execSync as Mock).mockImplementation(() => {
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', '');
expect(detectFirstAvailableEditor()).toBeUndefined();
});
it('should prioritize terminal editors over GUI editors', () => {
// Mock vim as available
(execSync as Mock).mockImplementation((cmd: string) => {
if (cmd.includes('vim') && !cmd.includes('nvim')) {
return Buffer.from('/usr/bin/vim');
}
if (cmd.includes('code')) {
return Buffer.from('/usr/bin/code');
}
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', '');
expect(detectFirstAvailableEditor()).toBe('vim');
});
it('should return vim when vim is the only editor available in sandbox mode', () => {
(execSync as Mock).mockImplementation((cmd: string) => {
if (cmd.includes('vim') && !cmd.includes('nvim')) {
return Buffer.from('/usr/bin/vim');
}
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', 'sandbox');
expect(detectFirstAvailableEditor()).toBe('vim');
});
it('should skip GUI editors in sandbox mode', () => {
(execSync as Mock).mockImplementation((cmd: string) => {
if (cmd.includes('code')) {
return Buffer.from('/usr/bin/code');
}
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', 'sandbox');
// vscode is installed but not allowed in sandbox
expect(detectFirstAvailableEditor()).toBeUndefined();
});
it('should return first available terminal editor (neovim)', () => {
(execSync as Mock).mockImplementation((cmd: string) => {
if (cmd.includes('nvim')) {
return Buffer.from('/usr/bin/nvim');
}
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', '');
expect(detectFirstAvailableEditor()).toBe('neovim');
});
});
describe('resolveEditor', () => {
it('should return the preferred editor when available', () => {
(execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/vim'));
vi.stubEnv('SANDBOX', '');
const result = resolveEditor('vim');
expect(result.editor).toBe('vim');
expect(result.error).toBeUndefined();
});
it('should return error when preferred editor is not installed', () => {
(execSync as Mock).mockImplementation(() => {
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', '');
const result = resolveEditor('vim');
expect(result.editor).toBeUndefined();
expect(result.error).toContain('Vim');
expect(result.error).toContain('not installed');
});
it('should return error when preferred GUI editor cannot be used in sandbox mode', () => {
(execSync as Mock).mockReturnValue(Buffer.from('/usr/bin/code'));
vi.stubEnv('SANDBOX', 'sandbox');
const result = resolveEditor('vscode');
expect(result.editor).toBeUndefined();
expect(result.error).toContain('VS Code');
expect(result.error).toContain('sandbox mode');
});
it('should auto-detect editor when no preference is set', () => {
(execSync as Mock).mockImplementation((cmd: string) => {
if (cmd.includes('vim') && !cmd.includes('nvim')) {
return Buffer.from('/usr/bin/vim');
}
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', '');
const result = resolveEditor(undefined);
expect(result.editor).toBe('vim');
expect(result.error).toBeUndefined();
});
it('should return error when no preference is set and no editors are available', () => {
(execSync as Mock).mockImplementation(() => {
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', '');
const result = resolveEditor(undefined);
expect(result.editor).toBeUndefined();
expect(result.error).toContain('No external editor');
expect(result.error).toContain('/editor');
});
it('should work with terminal editors in sandbox mode when no preference is set', () => {
(execSync as Mock).mockImplementation((cmd: string) => {
if (cmd.includes('vim') && !cmd.includes('nvim')) {
return Buffer.from('/usr/bin/vim');
}
throw new Error('Command not found');
});
vi.stubEnv('SANDBOX', 'sandbox');
const result = resolveEditor(undefined);
expect(result.editor).toBe('vim');
expect(result.error).toBeUndefined();
});
});
// Helper to create a mock exec that simulates async behavior
const mockExecAsync = (implementation: (cmd: string) => boolean): void => {
(exec as unknown as Mock).mockImplementation(
@@ -749,86 +621,93 @@ describe('editor utils', () => {
});
});
describe('detectFirstAvailableEditorAsync', () => {
it('should return undefined when no editors are installed', async () => {
mockExecAsync(() => false);
vi.stubEnv('SANDBOX', '');
expect(await detectFirstAvailableEditorAsync()).toBeUndefined();
});
it('should prioritize terminal editors over GUI editors', async () => {
mockExecAsync(
(cmd) =>
(cmd.includes('vim') && !cmd.includes('nvim')) ||
cmd.includes('code'),
);
vi.stubEnv('SANDBOX', '');
expect(await detectFirstAvailableEditorAsync()).toBe('vim');
});
it('should return vim in sandbox mode', async () => {
mockExecAsync((cmd) => cmd.includes('vim') && !cmd.includes('nvim'));
vi.stubEnv('SANDBOX', 'sandbox');
expect(await detectFirstAvailableEditorAsync()).toBe('vim');
});
it('should skip GUI editors in sandbox mode', async () => {
mockExecAsync((cmd) => cmd.includes('code'));
vi.stubEnv('SANDBOX', 'sandbox');
expect(await detectFirstAvailableEditorAsync()).toBeUndefined();
});
});
describe('resolveEditorAsync', () => {
it('should return the preferred editor when available', async () => {
mockExecAsync((cmd) => cmd.includes('vim'));
vi.stubEnv('SANDBOX', '');
const result = await resolveEditorAsync('vim');
expect(result.editor).toBe('vim');
expect(result.error).toBeUndefined();
expect(result).toBe('vim');
});
it('should return error when preferred editor is not installed', async () => {
it('should request editor selection when preferred editor is not installed', async () => {
mockExecAsync(() => false);
vi.stubEnv('SANDBOX', '');
const result = await resolveEditorAsync('vim');
expect(result.editor).toBeUndefined();
expect(result.error).toContain('Vim');
expect(result.error).toContain('not installed');
const resolvePromise = resolveEditorAsync('vim');
setTimeout(
() => coreEvents.emit(CoreEvent.EditorSelected, { editor: 'neovim' }),
0,
);
const result = await resolvePromise;
expect(result).toBe('neovim');
});
it('should return error when preferred GUI editor cannot be used in sandbox mode', async () => {
it('should request editor selection when preferred GUI editor cannot be used in sandbox mode', async () => {
mockExecAsync((cmd) => cmd.includes('code'));
vi.stubEnv('SANDBOX', 'sandbox');
const result = await resolveEditorAsync('vscode');
expect(result.editor).toBeUndefined();
expect(result.error).toContain('VS Code');
expect(result.error).toContain('sandbox mode');
const resolvePromise = resolveEditorAsync('vscode');
setTimeout(
() => coreEvents.emit(CoreEvent.EditorSelected, { editor: 'vim' }),
0,
);
const result = await resolvePromise;
expect(result).toBe('vim');
});
it('should auto-detect editor when no preference is set', async () => {
mockExecAsync((cmd) => cmd.includes('vim') && !cmd.includes('nvim'));
it('should request editor selection when no preference is set', async () => {
const emitSpy = vi.spyOn(coreEvents, 'emit');
vi.stubEnv('SANDBOX', '');
const result = await resolveEditorAsync(undefined);
expect(result.editor).toBe('vim');
expect(result.error).toBeUndefined();
const resolvePromise = resolveEditorAsync(undefined);
// Simulate UI selection
setTimeout(
() => coreEvents.emit(CoreEvent.EditorSelected, { editor: 'vim' }),
0,
);
const result = await resolvePromise;
expect(result).toBe('vim');
expect(emitSpy).toHaveBeenCalledWith(CoreEvent.RequestEditorSelection);
});
it('should return error when no preference is set and no editors are available', async () => {
mockExecAsync(() => false);
vi.stubEnv('SANDBOX', '');
const result = await resolveEditorAsync(undefined);
expect(result.editor).toBeUndefined();
expect(result.error).toContain('No external editor');
expect(result.error).toContain('/editor');
it('should return undefined when editor selection is cancelled', async () => {
const resolvePromise = resolveEditorAsync(undefined);
// Simulate UI cancellation (exit dialog)
setTimeout(
() => coreEvents.emit(CoreEvent.EditorSelected, { editor: undefined }),
0,
);
const result = await resolvePromise;
expect(result).toBeUndefined();
});
it('should work with terminal editors in sandbox mode when no preference is set', async () => {
mockExecAsync((cmd) => cmd.includes('vim') && !cmd.includes('nvim'));
it('should return undefined when abort signal is triggered', async () => {
const controller = new AbortController();
const resolvePromise = resolveEditorAsync(undefined, controller.signal);
setTimeout(() => controller.abort(), 0);
const result = await resolvePromise;
expect(result).toBeUndefined();
});
it('should request editor selection in sandbox mode when no preference is set', async () => {
const emitSpy = vi.spyOn(coreEvents, 'emit');
vi.stubEnv('SANDBOX', 'sandbox');
const result = await resolveEditorAsync(undefined);
expect(result.editor).toBe('vim');
expect(result.error).toBeUndefined();
const resolvePromise = resolveEditorAsync(undefined);
// Simulate UI selection
setTimeout(
() => coreEvents.emit(CoreEvent.EditorSelected, { editor: 'vim' }),
0,
);
const result = await resolvePromise;
expect(result).toBe('vim');
expect(emitSpy).toHaveBeenCalledWith(CoreEvent.RequestEditorSelection);
});
});
});

View File

@@ -6,8 +6,9 @@
import { exec, execSync, spawn, spawnSync } from 'node:child_process';
import { promisify } from 'node:util';
import { once } from 'node:events';
import { debugLogger } from './debugLogger.js';
import { coreEvents, CoreEvent } from './events.js';
import { coreEvents, CoreEvent, type EditorSelectedPayload } from './events.js';
const GUI_EDITORS = [
'vscode',
@@ -123,20 +124,21 @@ const editorCommands: Record<
hx: { win32: ['hx'], default: ['hx'] },
};
export function checkHasEditorType(editor: EditorType): boolean {
function getEditorCommands(editor: EditorType): string[] {
const commandConfig = editorCommands[editor];
const commands =
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
return commands.some((cmd) => commandExists(cmd));
return process.platform === 'win32'
? commandConfig.win32
: commandConfig.default;
}
export function checkHasEditorType(editor: EditorType): boolean {
return getEditorCommands(editor).some((cmd) => commandExists(cmd));
}
export async function checkHasEditorTypeAsync(
editor: EditorType,
): Promise<boolean> {
const commandConfig = editorCommands[editor];
const commands =
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
for (const cmd of commands) {
for (const cmd of getEditorCommands(editor)) {
if (await commandExistsAsync(cmd)) {
return true;
}
@@ -145,9 +147,7 @@ export async function checkHasEditorTypeAsync(
}
export function getEditorCommand(editor: EditorType): string {
const commandConfig = editorCommands[editor];
const commands =
process.platform === 'win32' ? commandConfig.win32 : commandConfig.default;
const commands = getEditorCommands(editor);
return (
commands.slice(0, -1).find((cmd) => commandExists(cmd)) ||
commands[commands.length - 1]
@@ -163,15 +163,20 @@ export function allowEditorTypeInSandbox(editor: EditorType): boolean {
return true;
}
function isEditorTypeAvailable(
editor: string | undefined,
): editor is EditorType {
return (
!!editor && isValidEditorType(editor) && allowEditorTypeInSandbox(editor)
);
}
/**
* Check if the editor is valid and can be used.
* Returns false if preferred editor is not set / invalid / not available / not allowed in sandbox.
*/
export function isEditorAvailable(editor: string | undefined): boolean {
if (editor && isValidEditorType(editor)) {
return checkHasEditorType(editor) && allowEditorTypeInSandbox(editor);
}
return false;
return isEditorTypeAvailable(editor) && checkHasEditorType(editor);
}
/**
@@ -182,155 +187,29 @@ export function isEditorAvailable(editor: string | undefined): boolean {
export async function isEditorAvailableAsync(
editor: string | undefined,
): Promise<boolean> {
if (editor && isValidEditorType(editor)) {
return (
(await checkHasEditorTypeAsync(editor)) &&
allowEditorTypeInSandbox(editor)
);
}
return false;
return (
isEditorTypeAvailable(editor) && (await checkHasEditorTypeAsync(editor))
);
}
/**
* Detects the first available editor from the supported list.
* Prioritizes terminal editors (vim, neovim, emacs, hx) as they work in all environments
* including sandboxed mode, then falls back to GUI editors.
* Returns undefined if no supported editor is found.
*/
export function detectFirstAvailableEditor(): EditorType | undefined {
// Prioritize terminal editors as they work in sandbox mode
for (const editor of TERMINAL_EDITORS) {
if (isEditorAvailable(editor)) {
return editor;
}
}
// Fall back to GUI editors (won't work in sandbox mode but checked above)
for (const editor of GUI_EDITORS) {
if (isEditorAvailable(editor)) {
return editor;
}
}
return undefined;
}
/**
* Async version of detectFirstAvailableEditor.
* Detects the first available editor from the supported list without blocking the event loop.
* Prioritizes terminal editors (vim, neovim, emacs, hx) as they work in all environments
* including sandboxed mode, then falls back to GUI editors.
* Returns undefined if no supported editor is found.
*/
export async function detectFirstAvailableEditorAsync(): Promise<
EditorType | undefined
> {
// Prioritize terminal editors as they work in sandbox mode
for (const editor of TERMINAL_EDITORS) {
if (await isEditorAvailableAsync(editor)) {
return editor;
}
}
// Fall back to GUI editors (won't work in sandbox mode but checked above)
for (const editor of GUI_EDITORS) {
if (await isEditorAvailableAsync(editor)) {
return editor;
}
}
return undefined;
}
/**
* Result of attempting to resolve an editor for use.
*/
export interface EditorResolutionResult {
/** The editor to use, if available */
editor?: EditorType;
/** Error message if no editor is available */
error?: string;
}
/**
* Resolves an editor to use for external editing.
* 1. If a preferred editor is set and available, uses it.
* 2. If a preferred editor is set but not available, returns an error.
* 3. If no preferred editor is set, attempts to auto-detect an available editor.
* 4. If no editor can be found, returns an error with instructions.
*
* @deprecated Use resolveEditorAsync instead to avoid blocking the event loop.
*/
export function resolveEditor(
preferredEditor: EditorType | undefined,
): EditorResolutionResult {
// Case 1: Preferred editor is set
if (preferredEditor) {
if (isEditorAvailable(preferredEditor)) {
return { editor: preferredEditor };
}
// Preferred editor is set but not available
const displayName = getEditorDisplayName(preferredEditor);
if (!checkHasEditorType(preferredEditor)) {
return {
error: `${displayName} is configured as your preferred editor but is not installed. Please install it or run /editor to choose a different editor.`,
};
}
// If the editor is installed but not available, it must be due to sandbox restrictions.
return {
error: `${displayName} cannot be used in sandbox mode. Please run /editor to choose a terminal-based editor (vim, neovim, emacs, or helix).`,
};
}
// Case 2: No preferred editor set, try to auto-detect
const detectedEditor = detectFirstAvailableEditor();
if (detectedEditor) {
return { editor: detectedEditor };
}
// Case 3: No editor available at all
return {
error:
'No external editor is configured or available. Please run /editor to set your preferred editor, or install one of the supported editors: vim, neovim, emacs, helix, VS Code, Cursor, Zed, or Windsurf.',
};
}
/**
* Async version of resolveEditor.
* Resolves an editor to use for external editing without blocking the event loop.
* 1. If a preferred editor is set and available, uses it.
* 2. If a preferred editor is set but not available, returns an error.
* 3. If no preferred editor is set, attempts to auto-detect an available editor.
* 4. If no editor can be found, returns an error with instructions.
* 2. If no preferred editor is set (or preferred is unavailable), requests selection from user and waits for it.
*/
export async function resolveEditorAsync(
preferredEditor: EditorType | undefined,
): Promise<EditorResolutionResult> {
// Case 1: Preferred editor is set
if (preferredEditor) {
if (await isEditorAvailableAsync(preferredEditor)) {
return { editor: preferredEditor };
}
// Preferred editor is set but not available
const displayName = getEditorDisplayName(preferredEditor);
if (!(await checkHasEditorTypeAsync(preferredEditor))) {
return {
error: `${displayName} is configured as your preferred editor but is not installed. Please install it or run /editor to choose a different editor.`,
};
}
// If the editor is installed but not available, it must be due to sandbox restrictions.
return {
error: `${displayName} cannot be used in sandbox mode. Please run /editor to choose a terminal-based editor (vim, neovim, emacs, or helix).`,
};
signal?: AbortSignal,
): Promise<EditorType | undefined> {
if (preferredEditor && (await isEditorAvailableAsync(preferredEditor))) {
return preferredEditor;
}
// Case 2: No preferred editor set, try to auto-detect
const detectedEditor = await detectFirstAvailableEditorAsync();
if (detectedEditor) {
return { editor: detectedEditor };
}
coreEvents.emit(CoreEvent.RequestEditorSelection);
// Case 3: No editor available at all
return {
error:
'No external editor is configured or available. Please run /editor to set your preferred editor, or install one of the supported editors: vim, neovim, emacs, helix, VS Code, Cursor, Zed, or Windsurf.',
};
return once(coreEvents, CoreEvent.EditorSelected, { signal })
.then(([payload]) => (payload as EditorSelectedPayload).editor)
.catch(() => undefined);
}
/**

View File

@@ -8,6 +8,7 @@ import { EventEmitter } from 'node:events';
import type { AgentDefinition } from '../agents/types.js';
import type { McpClient } from '../tools/mcp-client.js';
import type { ExtensionEvents } from './extensionLoader.js';
import type { EditorType } from './editor.js';
/**
* Defines the severity level for user-facing feedback.
@@ -143,6 +144,15 @@ export enum CoreEvent {
RetryAttempt = 'retry-attempt',
ConsentRequest = 'consent-request',
AgentsDiscovered = 'agents-discovered',
RequestEditorSelection = 'request-editor-selection',
EditorSelected = 'editor-selected',
}
/**
* Payload for the 'editor-selected' event.
*/
export interface EditorSelectedPayload {
editor?: EditorType;
}
export interface CoreEvents extends ExtensionEvents {
@@ -162,6 +172,8 @@ export interface CoreEvents extends ExtensionEvents {
[CoreEvent.RetryAttempt]: [RetryAttemptPayload];
[CoreEvent.ConsentRequest]: [ConsentRequestPayload];
[CoreEvent.AgentsDiscovered]: [AgentsDiscoveredPayload];
[CoreEvent.RequestEditorSelection]: never[];
[CoreEvent.EditorSelected]: [EditorSelectedPayload];
}
type EventBacklogItem = {