Tighten Escape agent interrupt shortcut
This commit is contained in:
@@ -9,6 +9,8 @@ import type { GitHubSearchItem } from "@server/shared/messages";
|
|||||||
import { Composer } from "./composer";
|
import { Composer } from "./composer";
|
||||||
import { splitComposerAttachmentsForSubmit } from "./composer-attachments";
|
import { splitComposerAttachmentsForSubmit } from "./composer-attachments";
|
||||||
|
|
||||||
|
const keyboardActionHandlerMock = vi.hoisted(() => vi.fn());
|
||||||
|
|
||||||
const {
|
const {
|
||||||
theme,
|
theme,
|
||||||
imageMetadata,
|
imageMetadata,
|
||||||
@@ -325,7 +327,9 @@ vi.mock("@/hooks/use-shortcut-keys", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@/hooks/use-keyboard-action-handler", () => ({
|
vi.mock("@/hooks/use-keyboard-action-handler", () => ({
|
||||||
useKeyboardActionHandler: () => {},
|
useKeyboardActionHandler: (input: unknown) => {
|
||||||
|
keyboardActionHandlerMock(input);
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@/hooks/use-keyboard-shift-style", () => ({
|
vi.mock("@/hooks/use-keyboard-shift-style", () => ({
|
||||||
@@ -575,6 +579,10 @@ beforeEach(() => {
|
|||||||
vi.stubGlobal("Node", dom.window.Node);
|
vi.stubGlobal("Node", dom.window.Node);
|
||||||
vi.stubGlobal("navigator", dom.window.navigator);
|
vi.stubGlobal("navigator", dom.window.navigator);
|
||||||
vi.stubGlobal("Blob", dom.window.Blob);
|
vi.stubGlobal("Blob", dom.window.Blob);
|
||||||
|
Object.assign(dom.window.HTMLElement.prototype, {
|
||||||
|
attachEvent: vi.fn(),
|
||||||
|
detachEvent: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
container = document.createElement("div");
|
container = document.createElement("div");
|
||||||
document.body.appendChild(container);
|
document.body.appendChild(container);
|
||||||
@@ -593,6 +601,7 @@ beforeEach(() => {
|
|||||||
setAgentStreamTailMock.mockClear();
|
setAgentStreamTailMock.mockClear();
|
||||||
setAgentStreamHeadMock.mockClear();
|
setAgentStreamHeadMock.mockClear();
|
||||||
setQueuedMessagesMock.mockClear();
|
setQueuedMessagesMock.mockClear();
|
||||||
|
keyboardActionHandlerMock.mockClear();
|
||||||
agentDirectoryStatusMock.mockReset();
|
agentDirectoryStatusMock.mockReset();
|
||||||
agentDirectoryStatusMock.mockReturnValue("ready");
|
agentDirectoryStatusMock.mockReturnValue("ready");
|
||||||
mockSessionState.sessions.server.serverInfo = {
|
mockSessionState.sessions.server.serverInfo = {
|
||||||
@@ -606,6 +615,9 @@ beforeEach(() => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
mockSessionState.sessions.server.agents = new Map([
|
||||||
|
["agent", { status: "idle", lastUsage: null }],
|
||||||
|
]);
|
||||||
mockSessionState.sessions.server.agentStreamHead = new Map();
|
mockSessionState.sessions.server.agentStreamHead = new Map();
|
||||||
mockSessionState.sessions.server.agentStreamTail = new Map();
|
mockSessionState.sessions.server.agentStreamTail = new Map();
|
||||||
mockSessionState.sessions.server.queuedMessages = new Map();
|
mockSessionState.sessions.server.queuedMessages = new Map();
|
||||||
@@ -719,12 +731,61 @@ function queryAllAttachmentMenuItems(): NodeListOf<HTMLElement> {
|
|||||||
return document.querySelectorAll('[data-testid^="message-input-attachment-menu-item-"]');
|
return document.querySelectorAll('[data-testid^="message-input-attachment-menu-item-"]');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dispatchAgentInterrupt() {
|
||||||
|
act(() => {
|
||||||
|
const registeredHandler = keyboardActionHandlerMock.mock.calls.at(-1)?.[0];
|
||||||
|
registeredHandler?.handle({ id: "agent.interrupt", scope: "global" });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function countMessageInputRenders(): number {
|
function countMessageInputRenders(): number {
|
||||||
return markScrollInvestigationRenderMock.mock.calls.filter(
|
return markScrollInvestigationRenderMock.mock.calls.filter(
|
||||||
([componentId]) => componentId === "MessageInput:server:agent",
|
([componentId]) => componentId === "MessageInput:server:agent",
|
||||||
).length;
|
).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
describe("Composer keyboard shortcuts", () => {
|
||||||
|
it("interrupts a running agent without clearing a filled draft", async () => {
|
||||||
|
mockSessionState.sessions.server.agents = new Map([
|
||||||
|
["agent", { status: "running", lastUsage: null }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
renderComposer({ initialText: "keep this prompt" });
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
dispatchAgentInterrupt();
|
||||||
|
|
||||||
|
expect(mockClient.cancelAgent).toHaveBeenCalledWith("agent");
|
||||||
|
expect(document.querySelector('[aria-label="Message agent..."]')).toHaveProperty(
|
||||||
|
"value",
|
||||||
|
"keep this prompt",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("interrupts a running agent when the message input is unfocused", async () => {
|
||||||
|
mockSessionState.sessions.server.agents = new Map([
|
||||||
|
["agent", { status: "running", lastUsage: null }],
|
||||||
|
]);
|
||||||
|
|
||||||
|
renderComposer();
|
||||||
|
await flushAsyncWork();
|
||||||
|
|
||||||
|
const input = document.querySelector('[aria-label="Message agent..."]') as HTMLElement | null;
|
||||||
|
input?.blur();
|
||||||
|
dispatchAgentInterrupt();
|
||||||
|
|
||||||
|
expect(mockClient.cancelAgent).toHaveBeenCalledWith("agent");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not interrupt when the agent is idle", () => {
|
||||||
|
renderComposer();
|
||||||
|
|
||||||
|
dispatchAgentInterrupt();
|
||||||
|
|
||||||
|
expect(mockClient.cancelAgent).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("Composer attachments", () => {
|
describe("Composer attachments", () => {
|
||||||
it("opens a Plus menu with image and GitHub attachment actions", () => {
|
it("opens a Plus menu with image and GitHub attachment actions", () => {
|
||||||
renderComposer();
|
renderComposer();
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export function Composer({
|
|||||||
toastErrorRef.current = toast.error;
|
toastErrorRef.current = toast.error;
|
||||||
const voice = useVoiceOptional();
|
const voice = useVoiceOptional();
|
||||||
const voiceToggleKeys = useShortcutKeys("voice-toggle");
|
const voiceToggleKeys = useShortcutKeys("voice-toggle");
|
||||||
const dictationCancelKeys = useShortcutKeys("dictation-cancel");
|
const agentInterruptKeys = useShortcutKeys("agent-interrupt");
|
||||||
const isDictationReady = useIsDictationReady({
|
const isDictationReady = useIsDictationReady({
|
||||||
serverId,
|
serverId,
|
||||||
isConnected,
|
isConnected,
|
||||||
@@ -525,6 +525,15 @@ export function Composer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (action.id) {
|
switch (action.id) {
|
||||||
|
case "agent.interrupt":
|
||||||
|
if (messageInputRef.current?.runKeyboardAction("dictation-cancel")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!isAgentRunning || isCancellingAgent || !isConnected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
handleCancelAgent();
|
||||||
|
return true;
|
||||||
case "message-input.send":
|
case "message-input.send":
|
||||||
return messageInputRef.current?.runKeyboardAction("send") ?? false;
|
return messageInputRef.current?.runKeyboardAction("send") ?? false;
|
||||||
case "message-input.dictation-confirm":
|
case "message-input.dictation-confirm":
|
||||||
@@ -560,12 +569,13 @@ export function Composer({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isPaneFocused],
|
[handleCancelAgent, isAgentRunning, isCancellingAgent, isConnected, isPaneFocused],
|
||||||
);
|
);
|
||||||
|
|
||||||
useKeyboardActionHandler({
|
useKeyboardActionHandler({
|
||||||
handlerId: keyboardHandlerIdRef.current,
|
handlerId: keyboardHandlerIdRef.current,
|
||||||
actions: [
|
actions: [
|
||||||
|
"agent.interrupt",
|
||||||
"message-input.focus",
|
"message-input.focus",
|
||||||
"message-input.send",
|
"message-input.send",
|
||||||
"message-input.dictation-toggle",
|
"message-input.dictation-toggle",
|
||||||
@@ -640,24 +650,12 @@ export function Composer({
|
|||||||
|
|
||||||
const hasSendableContent = userInput.trim().length > 0 || selectedAttachments.length > 0;
|
const hasSendableContent = userInput.trim().length > 0 || selectedAttachments.length > 0;
|
||||||
|
|
||||||
// Handle keyboard navigation for command autocomplete and stop action.
|
// Handle keyboard navigation for command autocomplete.
|
||||||
const handleCommandKeyPress = useCallback(
|
const handleCommandKeyPress = useCallback(
|
||||||
(event: { key: string; preventDefault: () => void }) => {
|
(event: { key: string; preventDefault: () => void }) => {
|
||||||
if (
|
|
||||||
event.key === "Escape" &&
|
|
||||||
isAgentRunning &&
|
|
||||||
!hasSendableContent &&
|
|
||||||
!isCancellingAgent &&
|
|
||||||
isConnected
|
|
||||||
) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleCancelAgent();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return autocompleteOnKeyPressRef.current(event);
|
return autocompleteOnKeyPressRef.current(event);
|
||||||
},
|
},
|
||||||
[hasSendableContent, isAgentRunning, isCancellingAgent, isConnected, handleCancelAgent],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const cancelButton = useMemo(
|
const cancelButton = useMemo(
|
||||||
@@ -683,16 +681,16 @@ export function Composer({
|
|||||||
<TooltipContent side="top" align="center" offset={8}>
|
<TooltipContent side="top" align="center" offset={8}>
|
||||||
<View style={styles.tooltipRow}>
|
<View style={styles.tooltipRow}>
|
||||||
<Text style={styles.tooltipText}>Interrupt</Text>
|
<Text style={styles.tooltipText}>Interrupt</Text>
|
||||||
{dictationCancelKeys ? (
|
{agentInterruptKeys ? (
|
||||||
<Shortcut chord={dictationCancelKeys} style={styles.tooltipShortcut} />
|
<Shortcut chord={agentInterruptKeys} style={styles.tooltipShortcut} />
|
||||||
) : null}
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : null,
|
) : null,
|
||||||
[
|
[
|
||||||
|
agentInterruptKeys,
|
||||||
buttonIconSize,
|
buttonIconSize,
|
||||||
dictationCancelKeys,
|
|
||||||
handleCancelAgent,
|
handleCancelAgent,
|
||||||
hasSendableContent,
|
hasSendableContent,
|
||||||
isAgentRunning,
|
isAgentRunning,
|
||||||
|
|||||||
@@ -307,8 +307,9 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
|||||||
if (action === "dictation-cancel") {
|
if (action === "dictation-cancel") {
|
||||||
if (isDictatingRef.current) {
|
if (isDictatingRef.current) {
|
||||||
cancelDictation();
|
cancelDictation();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === "dictation-toggle") {
|
if (action === "dictation-toggle") {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const { navigateToWorkspaceMock, routerMock, pathState } = vi.hoisted(() => ({
|
|||||||
routerMock: {
|
routerMock: {
|
||||||
back: vi.fn(),
|
back: vi.fn(),
|
||||||
push: vi.fn(),
|
push: vi.fn(),
|
||||||
|
replace: vi.fn(),
|
||||||
},
|
},
|
||||||
pathState: {
|
pathState: {
|
||||||
pathname: "/h/srv/workspace/ws-2",
|
pathname: "/h/srv/workspace/ws-2",
|
||||||
@@ -56,6 +57,7 @@ vi.mock("@/hooks/use-workspace-navigation", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
import { useKeyboardShortcuts } from "./use-keyboard-shortcuts";
|
import { useKeyboardShortcuts } from "./use-keyboard-shortcuts";
|
||||||
|
import { keyboardActionDispatcher } from "@/keyboard/keyboard-action-dispatcher";
|
||||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||||
import {
|
import {
|
||||||
activateNavigationWorkspaceSelection,
|
activateNavigationWorkspaceSelection,
|
||||||
@@ -80,6 +82,7 @@ describe("useKeyboardShortcuts", () => {
|
|||||||
navigateToWorkspaceMock.mockReset();
|
navigateToWorkspaceMock.mockReset();
|
||||||
routerMock.back.mockReset();
|
routerMock.back.mockReset();
|
||||||
routerMock.push.mockReset();
|
routerMock.push.mockReset();
|
||||||
|
routerMock.replace.mockReset();
|
||||||
pathState.pathname = "/h/srv/workspace/ws-2";
|
pathState.pathname = "/h/srv/workspace/ws-2";
|
||||||
syncNavigationActiveWorkspace({ current: null });
|
syncNavigationActiveWorkspace({ current: null });
|
||||||
useKeyboardShortcutsStore.setState({
|
useKeyboardShortcutsStore.setState({
|
||||||
@@ -132,4 +135,57 @@ describe("useKeyboardShortcuts", () => {
|
|||||||
});
|
});
|
||||||
expect(event.defaultPrevented).toBe(true);
|
expect(event.defaultPrevented).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("dispatches Escape as an agent interrupt action", async () => {
|
||||||
|
const handleInterrupt = vi.fn(() => true);
|
||||||
|
const unregister = keyboardActionDispatcher.registerHandler({
|
||||||
|
handlerId: "test-agent-interrupt",
|
||||||
|
actions: ["agent.interrupt"],
|
||||||
|
enabled: true,
|
||||||
|
priority: 100,
|
||||||
|
isActive: () => true,
|
||||||
|
handle: handleInterrupt,
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root?.render(<Probe />);
|
||||||
|
});
|
||||||
|
|
||||||
|
const event = new KeyboardEvent("keydown", {
|
||||||
|
key: "Escape",
|
||||||
|
code: "Escape",
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
expect(handleInterrupt).toHaveBeenCalledTimes(1);
|
||||||
|
unregister();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns from desktop settings to the retained workspace without browser history back", async () => {
|
||||||
|
syncNavigationActiveWorkspace({
|
||||||
|
current: {
|
||||||
|
getCurrentRoute: () => ({ path: "/h/srv/workspace/ws-2" }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
pathState.pathname = "/settings/general";
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root?.render(<Probe />);
|
||||||
|
});
|
||||||
|
|
||||||
|
const event = new KeyboardEvent("keydown", {
|
||||||
|
key: ",",
|
||||||
|
code: "Comma",
|
||||||
|
metaKey: true,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
});
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
expect(routerMock.replace).toHaveBeenCalledWith("/h/srv/workspace/ws-2");
|
||||||
|
expect(routerMock.back).not.toHaveBeenCalled();
|
||||||
|
expect(event.defaultPrevented).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { getIsElectronRuntime } from "@/constants/layout";
|
|||||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||||
import { setCommandCenterFocusRestoreElement } from "@/utils/command-center-focus-restore";
|
import { setCommandCenterFocusRestoreElement } from "@/utils/command-center-focus-restore";
|
||||||
import {
|
import {
|
||||||
|
buildHostWorkspaceRoute,
|
||||||
buildSettingsRoute,
|
buildSettingsRoute,
|
||||||
parseHostAgentRouteFromPathname,
|
parseHostAgentRouteFromPathname,
|
||||||
parseHostWorkspaceRouteFromPathname,
|
parseHostWorkspaceRouteFromPathname,
|
||||||
@@ -27,7 +28,10 @@ import { isNative } from "@/constants/platform";
|
|||||||
import { isImeComposingKeyboardEvent } from "@/utils/keyboard-ime";
|
import { isImeComposingKeyboardEvent } from "@/utils/keyboard-ime";
|
||||||
import { getRelativeSidebarShortcutTarget } from "@/utils/sidebar-shortcuts";
|
import { getRelativeSidebarShortcutTarget } from "@/utils/sidebar-shortcuts";
|
||||||
import { useActiveServerId } from "@/hooks/use-active-server-id";
|
import { useActiveServerId } from "@/hooks/use-active-server-id";
|
||||||
import { getNavigationActiveWorkspaceSelection } from "@/stores/navigation-active-workspace-store";
|
import {
|
||||||
|
getLastNavigationWorkspaceRouteSelection,
|
||||||
|
getNavigationActiveWorkspaceSelection,
|
||||||
|
} from "@/stores/navigation-active-workspace-store";
|
||||||
|
|
||||||
export function useKeyboardShortcuts({
|
export function useKeyboardShortcuts({
|
||||||
enabled,
|
enabled,
|
||||||
@@ -159,6 +163,11 @@ export function useKeyboardShortcuts({
|
|||||||
event: KeyboardEvent;
|
event: KeyboardEvent;
|
||||||
}): boolean => {
|
}): boolean => {
|
||||||
switch (input.action) {
|
switch (input.action) {
|
||||||
|
case "agent.interrupt":
|
||||||
|
return keyboardActionDispatcher.dispatch({
|
||||||
|
id: "agent.interrupt",
|
||||||
|
scope: "global",
|
||||||
|
});
|
||||||
case "agent.new":
|
case "agent.new":
|
||||||
return openProjectPicker();
|
return openProjectPicker();
|
||||||
case "workspace.tab.new":
|
case "workspace.tab.new":
|
||||||
@@ -234,6 +243,15 @@ export function useKeyboardShortcuts({
|
|||||||
return true;
|
return true;
|
||||||
case "settings.toggle":
|
case "settings.toggle":
|
||||||
if (pathname.startsWith("/settings")) {
|
if (pathname.startsWith("/settings")) {
|
||||||
|
if (!isMobile) {
|
||||||
|
const lastWorkspaceRoute = getLastNavigationWorkspaceRouteSelection();
|
||||||
|
if (lastWorkspaceRoute) {
|
||||||
|
router.replace(
|
||||||
|
buildHostWorkspaceRoute(lastWorkspaceRoute.serverId, lastWorkspaceRoute.workspaceId),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
router.back();
|
router.back();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type MessageInputKeyboardActionKind =
|
|||||||
| "voice-mute-toggle";
|
| "voice-mute-toggle";
|
||||||
|
|
||||||
export type KeyboardActionId =
|
export type KeyboardActionId =
|
||||||
|
| "agent.interrupt"
|
||||||
| "agent.new"
|
| "agent.new"
|
||||||
| "workspace.tab.new"
|
| "workspace.tab.new"
|
||||||
| "workspace.tab.close.current"
|
| "workspace.tab.close.current"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export type KeyboardActionScope = "global" | "message-input" | "sidebar" | "workspace";
|
export type KeyboardActionScope = "global" | "message-input" | "sidebar" | "workspace";
|
||||||
|
|
||||||
export type KeyboardActionId =
|
export type KeyboardActionId =
|
||||||
|
| "agent.interrupt"
|
||||||
| "message-input.focus"
|
| "message-input.focus"
|
||||||
| "message-input.send"
|
| "message-input.send"
|
||||||
| "message-input.dictation-toggle"
|
| "message-input.dictation-toggle"
|
||||||
@@ -29,6 +30,7 @@ export type KeyboardActionId =
|
|||||||
| "worktree.archive";
|
| "worktree.archive";
|
||||||
|
|
||||||
export type KeyboardActionDefinition =
|
export type KeyboardActionDefinition =
|
||||||
|
| { id: "agent.interrupt"; scope: KeyboardActionScope }
|
||||||
| { id: "message-input.focus"; scope: KeyboardActionScope }
|
| { id: "message-input.focus"; scope: KeyboardActionScope }
|
||||||
| { id: "message-input.send"; scope: KeyboardActionScope }
|
| { id: "message-input.send"; scope: KeyboardActionScope }
|
||||||
| { id: "message-input.dictation-toggle"; scope: KeyboardActionScope }
|
| { id: "message-input.dictation-toggle"; scope: KeyboardActionScope }
|
||||||
|
|||||||
@@ -280,11 +280,10 @@ describe("keyboard-shortcuts", () => {
|
|||||||
payload: { kind: "voice-mute-toggle" },
|
payload: { kind: "voice-mute-toggle" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "lets Escape continue to local handlers while routing dictation cancel",
|
name: "routes Escape to agent interrupt outside terminal focus",
|
||||||
event: { key: "Escape", code: "Escape" },
|
event: { key: "Escape", code: "Escape" },
|
||||||
context: { focusScope: "message-input" },
|
context: { focusScope: "message-input" },
|
||||||
action: "message-input.action",
|
action: "agent.interrupt",
|
||||||
payload: { kind: "dictation-cancel" },
|
|
||||||
preventDefault: false,
|
preventDefault: false,
|
||||||
stopPropagation: false,
|
stopPropagation: false,
|
||||||
},
|
},
|
||||||
@@ -353,6 +352,16 @@ describe("keyboard-shortcuts", () => {
|
|||||||
event: { key: "d", code: "KeyD", metaKey: true },
|
event: { key: "d", code: "KeyD", metaKey: true },
|
||||||
context: { isMac: true, focusScope: "terminal" },
|
context: { isMac: true, focusScope: "terminal" },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "does not interrupt agent when terminal is focused",
|
||||||
|
event: { key: "Escape", code: "Escape" },
|
||||||
|
context: { focusScope: "terminal" },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "does not interrupt agent when command center is open",
|
||||||
|
event: { key: "Escape", code: "Escape" },
|
||||||
|
context: { commandCenterOpen: true },
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "does not bind pane shortcuts on non-mac platforms",
|
name: "does not bind pane shortcuts on non-mac platforms",
|
||||||
event: { key: "\\", code: "Backslash", ctrlKey: true },
|
event: { key: "\\", code: "Backslash", ctrlKey: true },
|
||||||
|
|||||||
@@ -869,17 +869,16 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "message-input-dictation-cancel",
|
id: "agent-interrupt",
|
||||||
action: "message-input.action",
|
action: "agent.interrupt",
|
||||||
combo: "Escape",
|
combo: "Escape",
|
||||||
when: { commandCenter: false, terminal: false },
|
when: { commandCenter: false, terminal: false },
|
||||||
payload: { type: "message-input", kind: "dictation-cancel" },
|
|
||||||
preventDefault: false,
|
preventDefault: false,
|
||||||
stopPropagation: false,
|
stopPropagation: false,
|
||||||
help: {
|
help: {
|
||||||
id: "dictation-cancel",
|
id: "agent-interrupt",
|
||||||
section: "agent-input",
|
section: "agent-input",
|
||||||
label: "Cancel dictation",
|
label: "Interrupt agent",
|
||||||
keys: ["Esc"],
|
keys: ["Esc"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user