diff --git a/packages/app/src/components/agent-stream-view.tsx b/packages/app/src/components/agent-stream-view.tsx index b7ec45512..7c45d66ca 100644 --- a/packages/app/src/components/agent-stream-view.tsx +++ b/packages/app/src/components/agent-stream-view.tsx @@ -5,8 +5,8 @@ import { useEffect, useImperativeHandle, useMemo, - useRef, useState, + useRef, } from "react"; import { View, Text, Pressable, Platform, ActivityIndicator } from "react-native"; import Markdown from "react-native-markdown-display"; @@ -42,6 +42,7 @@ import type { AgentPermissionResponse } from "@server/server/agent/agent-sdk-typ import type { Agent } from "@/contexts/session-context"; import { useSessionStore } from "@/stores/session-store"; import { useFileExplorerActions } from "@/hooks/use-file-explorer-actions"; +import { useKeyboardActionHandler } from "@/hooks/use-keyboard-action-handler"; import type { DaemonClient } from "@server/client/daemon-client"; import { ToolCallDetailsContent } from "./tool-call-details"; import { QuestionFormCard } from "./question-form-card"; @@ -85,6 +86,7 @@ export interface AgentStreamViewProps { agent: Agent; streamItems: StreamItem[]; pendingPermissions: Map; + isPaneFocused?: boolean; routeBottomAnchorRequest?: BottomAnchorRouteRequest | null; isAuthoritativeHistoryReady?: boolean; onOpenWorkspaceFile?: (input: { filePath: string }) => void; @@ -98,6 +100,7 @@ const AgentStreamViewComponent = forwardRef Array.from(pendingPermissions.values()).filter((perm) => perm.agentId === agentId), + [pendingPermissions, agentId], + ); const baseRenderModel = useMemo(() => { return buildAgentStreamRenderModel({ @@ -490,18 +497,18 @@ const AgentStreamViewComponent = forwardRef Array.from(pendingPermissions.values()).filter((perm) => perm.agentId === agentId), - [pendingPermissions, agentId], - ); - const showWorkingIndicator = agent.status === "running"; const renderModel = useMemo(() => { const pendingPermissionsNode = pendingPermissionItems.length > 0 ? ( - {pendingPermissionItems.map((permission) => ( - + {pendingPermissionItems.map((permission, index) => ( + ))} ) : null; @@ -525,7 +532,14 @@ const AgentStreamViewComponent = forwardRef { if ( @@ -727,9 +741,11 @@ function WorkingIndicator() { function PermissionRequestCard({ permission, client, + shortcutActive, }: { permission: PendingPermission; client: DaemonClient | null; + shortcutActive: boolean; }) { const { theme } = useUnistyles(); const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; @@ -740,9 +756,6 @@ function PermissionRequestCard({ const description = request.description ?? ""; const planMarkdown = useMemo(() => { - if (!request) { - return undefined; - } const planFromMetadata = typeof request.metadata?.planText === "string" ? request.metadata.planText : undefined; if (planFromMetadata) { @@ -868,6 +881,7 @@ function PermissionRequestCard({ resetPermissionMutation(); setRespondingAction(null); }, [permission.request.id, resetPermissionMutation]); + const handleResponse = useCallback( (response: AgentPermissionResponse) => { respondToPermission({ @@ -881,12 +895,52 @@ function PermissionRequestCard({ [permission.agentId, permission.request.id, respondToPermission], ); + const handleDeny = useCallback(() => { + setRespondingAction("deny"); + handleResponse({ + behavior: "deny", + message: "Denied by user", + }); + }, [handleResponse]); + + const handleAccept = useCallback(() => { + setRespondingAction("accept"); + handleResponse({ behavior: "allow" }); + }, [handleResponse]); + + const handlePromptSelection = useCallback( + (action: { id: string; index?: number }): boolean => { + if (action.id !== "agent.prompt.select" || request.kind === "question" || isResponding) { + return false; + } + if (action.index === 1) { + handleDeny(); + return true; + } + if (action.index === 2) { + handleAccept(); + return true; + } + return false; + }, + [handleAccept, handleDeny, isResponding, request.kind], + ); + + useKeyboardActionHandler({ + handlerId: `agent-prompt-permission:${permission.key}`, + actions: ["agent.prompt.select"], + enabled: shortcutActive && request.kind !== "question" && !isResponding, + priority: 150, + handle: handlePromptSelection, + }); + if (request.kind === "question") { return ( ); } @@ -958,13 +1012,7 @@ function PermissionRequestCard({ }, pressed ? permissionStyles.optionButtonPressed : null, ]} - onPress={() => { - setRespondingAction("deny"); - handleResponse({ - behavior: "deny", - message: "Denied by user", - }); - }} + onPress={handleDeny} disabled={isResponding} > {respondingAction === "deny" ? ( @@ -989,10 +1037,7 @@ function PermissionRequestCard({ }, pressed ? permissionStyles.optionButtonPressed : null, ]} - onPress={() => { - setRespondingAction("accept"); - handleResponse({ behavior: "allow" }); - }} + onPress={handleAccept} disabled={isResponding} > {respondingAction === "accept" ? ( diff --git a/packages/app/src/components/question-form-card.tsx b/packages/app/src/components/question-form-card.tsx index f5d711fdc..84690d6c3 100644 --- a/packages/app/src/components/question-form-card.tsx +++ b/packages/app/src/components/question-form-card.tsx @@ -1,8 +1,9 @@ -import { useState, useCallback } from "react"; +import { useCallback, useState } from "react"; import { View, Text, TextInput, Pressable, ActivityIndicator, Platform } from "react-native"; import { StyleSheet, useUnistyles, UnistylesRuntime } from "react-native-unistyles"; import { Check, CircleHelp, X } from "lucide-react-native"; import type { PendingPermission } from "@/types/shared"; +import { useKeyboardActionHandler } from "@/hooks/use-keyboard-action-handler"; import type { AgentPermissionResponse } from "@server/server/agent/agent-sdk-types"; interface QuestionOption { @@ -17,6 +18,9 @@ interface Question { multiSelect: boolean; } +type QuestionSelections = Record>; +type QuestionOtherTexts = Record; + function parseQuestions(input: unknown): Question[] | null { if ( typeof input !== "object" || @@ -26,128 +30,334 @@ function parseQuestions(input: unknown): Question[] | null { ) { return null; } + const raw = (input as Record).questions as unknown[]; const questions: Question[] = []; + for (const item of raw) { - if (typeof item !== "object" || item === null) return null; - const q = item as Record; - if (typeof q.question !== "string" || typeof q.header !== "string") return null; - if (!Array.isArray(q.options)) return null; + if (typeof item !== "object" || item === null) { + return null; + } + + const question = item as Record; + if (typeof question.question !== "string" || typeof question.header !== "string") { + return null; + } + if (!Array.isArray(question.options)) { + return null; + } + const options: QuestionOption[] = []; - for (const opt of q.options as unknown[]) { - if (typeof opt !== "object" || opt === null) return null; - const o = opt as Record; - if (typeof o.label !== "string") return null; + for (const option of question.options as unknown[]) { + if (typeof option !== "object" || option === null) { + return null; + } + + const candidate = option as Record; + if (typeof candidate.label !== "string") { + return null; + } + options.push({ - label: o.label, - description: typeof o.description === "string" ? o.description : undefined, + label: candidate.label, + description: typeof candidate.description === "string" ? candidate.description : undefined, }); } + questions.push({ - question: q.question, - header: q.header, + question: question.question, + header: question.header, options, - multiSelect: q.multiSelect === true, + multiSelect: question.multiSelect === true, }); } + return questions.length > 0 ? questions : null; } +function cloneSelections(input: QuestionSelections): QuestionSelections { + const next: QuestionSelections = {}; + for (const [key, value] of Object.entries(input)) { + next[Number(key)] = new Set(value); + } + return next; +} + +function isQuestionAnswered(input: { + selections: QuestionSelections; + otherTexts: QuestionOtherTexts; + questionIndex: number; +}): boolean { + const selected = input.selections[input.questionIndex]; + const otherText = input.otherTexts[input.questionIndex]?.trim(); + return (selected && selected.size > 0) || Boolean(otherText && otherText.length > 0); +} + +function findFirstUnansweredQuestionIndex(input: { + questions: readonly Question[]; + selections: QuestionSelections; + otherTexts: QuestionOtherTexts; +}): number | null { + for (let questionIndex = 0; questionIndex < input.questions.length; questionIndex += 1) { + if ( + !isQuestionAnswered({ + selections: input.selections, + otherTexts: input.otherTexts, + questionIndex, + }) + ) { + return questionIndex; + } + } + + return null; +} + +function areAllQuestionsAnswered(input: { + questions: readonly Question[]; + selections: QuestionSelections; + otherTexts: QuestionOtherTexts; +}): boolean { + return input.questions.every((_, questionIndex) => + isQuestionAnswered({ + selections: input.selections, + otherTexts: input.otherTexts, + questionIndex, + }), + ); +} + +function buildQuestionAnswers(input: { + questions: readonly Question[]; + selections: QuestionSelections; + otherTexts: QuestionOtherTexts; +}): Record { + const answers: Record = {}; + + for (let questionIndex = 0; questionIndex < input.questions.length; questionIndex += 1) { + const question = input.questions[questionIndex]; + if (!question) { + continue; + } + + const selected = input.selections[questionIndex]; + const otherText = input.otherTexts[questionIndex]?.trim(); + + if (otherText && otherText.length > 0) { + answers[question.header] = otherText; + continue; + } + + if (!selected || selected.size === 0) { + continue; + } + + const labels = Array.from(selected) + .map((optionIndex) => question.options[optionIndex]?.label) + .filter((label): label is string => typeof label === "string"); + if (labels.length > 0) { + answers[question.header] = labels.join(", "); + } + } + + return answers; +} + +function selectQuestionOption(input: { + questions: readonly Question[]; + selections: QuestionSelections; + otherTexts: QuestionOtherTexts; + questionIndex: number; + optionIndex: number; +}): { + selections: QuestionSelections; + otherTexts: QuestionOtherTexts; +} { + const question = input.questions[input.questionIndex]; + if (!question || !question.options[input.optionIndex]) { + return { + selections: input.selections, + otherTexts: input.otherTexts, + }; + } + + const selections = cloneSelections(input.selections); + const otherTexts = { ...input.otherTexts }; + const current = selections[input.questionIndex] ?? new Set(); + const next = new Set(current); + + if (question.multiSelect) { + if (next.has(input.optionIndex)) { + next.delete(input.optionIndex); + } else { + next.add(input.optionIndex); + } + } else if (next.has(input.optionIndex)) { + next.clear(); + } else { + next.clear(); + next.add(input.optionIndex); + } + + selections[input.questionIndex] = next; + delete otherTexts[input.questionIndex]; + + return { selections, otherTexts }; +} + interface QuestionFormCardProps { permission: PendingPermission; onRespond: (response: AgentPermissionResponse) => void; isResponding: boolean; + shortcutActive: boolean; } const IS_WEB = Platform.OS === "web"; -export function QuestionFormCard({ permission, onRespond, isResponding }: QuestionFormCardProps) { +export function QuestionFormCard({ + permission, + onRespond, + isResponding, + shortcutActive, +}: QuestionFormCardProps) { const { theme } = useUnistyles(); const isMobile = UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm"; const questions = parseQuestions(permission.request.input); - const [selections, setSelections] = useState>>({}); - const [otherTexts, setOtherTexts] = useState>({}); + const [selections, setSelections] = useState({}); + const [otherTexts, setOtherTexts] = useState({}); const [respondingAction, setRespondingAction] = useState<"submit" | "dismiss" | null>(null); - const toggleOption = useCallback((qIndex: number, optIndex: number, multiSelect: boolean) => { - setSelections((prev) => { - const current = prev[qIndex] ?? new Set(); - const next = new Set(current); - if (multiSelect) { - if (next.has(optIndex)) { - next.delete(optIndex); - } else { - next.add(optIndex); - } - } else { - if (next.has(optIndex)) { - next.clear(); - } else { - next.clear(); - next.add(optIndex); - } + const submitResponses = useCallback( + (input: { selections: QuestionSelections; otherTexts: QuestionOtherTexts }) => { + if (!questions) { + return; } - return { ...prev, [qIndex]: next }; - }); - setOtherTexts((prev) => { - if (!prev[qIndex]) return prev; - const next = { ...prev }; - delete next[qIndex]; - return next; - }); - }, []); - const setOtherText = useCallback((qIndex: number, text: string) => { - setOtherTexts((prev) => ({ ...prev, [qIndex]: text })); - if (text.length > 0) { - setSelections((prev) => { - if (!prev[qIndex] || prev[qIndex].size === 0) return prev; - return { ...prev, [qIndex]: new Set() }; + setRespondingAction("submit"); + onRespond({ + behavior: "allow", + updatedInput: { + ...permission.request.input, + answers: buildQuestionAnswers({ + questions, + selections: input.selections, + otherTexts: input.otherTexts, + }), + }, }); - } - }, []); + }, + [onRespond, permission.request.input, questions], + ); - if (!questions) { - return null; - } + const toggleOption = useCallback( + (input: { questionIndex: number; optionIndex: number }) => { + if (!questions) { + return; + } - const allAnswered = questions.every((_, qIndex) => { - const selected = selections[qIndex]; - const otherText = otherTexts[qIndex]?.trim(); - return (selected && selected.size > 0) || (otherText && otherText.length > 0); - }); + const next = selectQuestionOption({ + questions, + selections, + otherTexts, + questionIndex: input.questionIndex, + optionIndex: input.optionIndex, + }); + setSelections(next.selections); + setOtherTexts(next.otherTexts); + }, + [otherTexts, questions, selections], + ); - function handleSubmit() { - setRespondingAction("submit"); - const answers: Record = {}; - for (let i = 0; i < questions!.length; i++) { - const q = questions![i]; - const selected = selections[i]; - const otherText = otherTexts[i]?.trim(); - - if (otherText && otherText.length > 0) { - answers[q.header] = otherText; - } else if (selected && selected.size > 0) { - const labels = Array.from(selected).map((idx) => q.options[idx].label); - answers[q.header] = labels.join(", "); - } + const setOtherText = useCallback((input: { questionIndex: number; text: string }) => { + setOtherTexts((previous) => ({ ...previous, [input.questionIndex]: input.text })); + if (input.text.length > 0) { + setSelections((previous) => { + if (!previous[input.questionIndex] || previous[input.questionIndex]?.size === 0) { + return previous; + } + return { ...previous, [input.questionIndex]: new Set() }; + }); } + }, []); - onRespond({ - behavior: "allow", - updatedInput: { ...permission.request.input, answers }, - }); - } + const handleSubmit = useCallback(() => { + submitResponses({ selections, otherTexts }); + }, [otherTexts, selections, submitResponses]); - function handleDeny() { + const handleDeny = useCallback(() => { setRespondingAction("dismiss"); onRespond({ behavior: "deny", message: "Dismissed by user", }); + }, [onRespond]); + + const handlePromptSelection = useCallback( + (action: { id: string; index?: number }): boolean => { + if (action.id !== "agent.prompt.select" || isResponding || !questions) { + return false; + } + + const questionIndex = findFirstUnansweredQuestionIndex({ + questions, + selections, + otherTexts, + }); + if (questionIndex === null) { + return false; + } + + const optionIndex = (action.index ?? 0) - 1; + const question = questions[questionIndex]; + if (!question?.options[optionIndex]) { + return false; + } + + const next = selectQuestionOption({ + questions, + selections, + otherTexts, + questionIndex, + optionIndex, + }); + setSelections(next.selections); + setOtherTexts(next.otherTexts); + + const shouldAutoSubmit = + areAllQuestionsAnswered({ + questions, + selections: next.selections, + otherTexts: next.otherTexts, + }) && questions.every((candidate) => candidate.multiSelect !== true); + + if (shouldAutoSubmit) { + submitResponses(next); + } + + return true; + }, + [isResponding, otherTexts, questions, selections, submitResponses], + ); + + useKeyboardActionHandler({ + handlerId: `agent-prompt-question:${permission.key}`, + actions: ["agent.prompt.select"], + enabled: shortcutActive && !isResponding && questions !== null, + priority: 150, + handle: handlePromptSelection, + }); + + if (!questions) { + return null; } + const allAnswered = areAllQuestionsAnswered({ + questions, + selections, + otherTexts, + }); + return ( - {questions.map((q, qIndex) => { - const selected = selections[qIndex] ?? new Set(); - const otherText = otherTexts[qIndex] ?? ""; + {questions.map((question, questionIndex) => { + const selected = selections[questionIndex] ?? new Set(); + const otherText = otherTexts[questionIndex] ?? ""; return ( - + - {q.question} + {question.question} - {q.options.map((opt, optIndex) => { - const isSelected = selected.has(optIndex); + {question.options.map((option, optionIndex) => { + const isSelected = selected.has(optionIndex); return ( [ styles.optionItem, (hovered || isSelected) && { @@ -183,22 +393,27 @@ export function QuestionFormCard({ permission, onRespond, isResponding }: Questi }, pressed && styles.optionItemPressed, ]} - onPress={() => toggleOption(qIndex, optIndex, q.multiSelect)} + onPress={() => + toggleOption({ + questionIndex, + optionIndex, + }) + } disabled={isResponding} > - {opt.label} + {option.label} - {opt.description ? ( + {option.description ? ( - {opt.description} + {option.description} ) : null} @@ -222,12 +437,21 @@ export function QuestionFormCard({ permission, onRespond, isResponding }: Questi backgroundColor: theme.colors.surface2, }, // @ts-expect-error - outlineStyle is web-only - IS_WEB && { outlineStyle: "none", outlineWidth: 0, outlineColor: "transparent" }, + IS_WEB && { + outlineStyle: "none", + outlineWidth: 0, + outlineColor: "transparent", + }, ]} placeholder="Other..." placeholderTextColor={theme.colors.foregroundMuted} value={otherText} - onChangeText={(text) => setOtherText(qIndex, text)} + onChangeText={(text) => + setOtherText({ + questionIndex, + text, + }) + } editable={!isResponding} /> diff --git a/packages/app/src/hooks/use-keyboard-shortcuts.ts b/packages/app/src/hooks/use-keyboard-shortcuts.ts index 2ec127b7a..7cedcad08 100644 --- a/packages/app/src/hooks/use-keyboard-shortcuts.ts +++ b/packages/app/src/hooks/use-keyboard-shortcuts.ts @@ -161,6 +161,15 @@ export function useKeyboardShortcuts({ switch (input.action) { case "agent.new": return openProjectPicker(); + case "agent.prompt.select": + if (!input.payload || typeof input.payload !== "object" || !("index" in input.payload)) { + return false; + } + return keyboardActionDispatcher.dispatch({ + id: "agent.prompt.select", + scope: "workspace", + index: input.payload.index, + }); case "workspace.tab.new": return keyboardActionDispatcher.dispatch({ id: "workspace.tab.new", diff --git a/packages/app/src/keyboard/actions.ts b/packages/app/src/keyboard/actions.ts index 2653ac323..b29169795 100644 --- a/packages/app/src/keyboard/actions.ts +++ b/packages/app/src/keyboard/actions.ts @@ -16,6 +16,7 @@ export type MessageInputKeyboardActionKind = export type KeyboardActionId = | "agent.new" + | "agent.prompt.select" | "workspace.tab.new" | "workspace.tab.close.current" | "workspace.tab.navigate.index" diff --git a/packages/app/src/keyboard/keyboard-action-dispatcher.ts b/packages/app/src/keyboard/keyboard-action-dispatcher.ts index 817ac7101..b89f3d861 100644 --- a/packages/app/src/keyboard/keyboard-action-dispatcher.ts +++ b/packages/app/src/keyboard/keyboard-action-dispatcher.ts @@ -1,6 +1,7 @@ export type KeyboardActionScope = "global" | "message-input" | "sidebar" | "workspace"; export type KeyboardActionId = + | "agent.prompt.select" | "message-input.focus" | "message-input.dictation-toggle" | "message-input.dictation-cancel" @@ -26,6 +27,7 @@ export type KeyboardActionId = | "worktree.archive"; export type KeyboardActionDefinition = + | { id: "agent.prompt.select"; scope: KeyboardActionScope; index: number } | { id: "message-input.focus"; scope: KeyboardActionScope } | { id: "message-input.dictation-toggle"; scope: KeyboardActionScope } | { id: "message-input.dictation-cancel"; scope: KeyboardActionScope } diff --git a/packages/app/src/keyboard/keyboard-shortcuts.test.ts b/packages/app/src/keyboard/keyboard-shortcuts.test.ts index 21bc67c72..25fca321a 100644 --- a/packages/app/src/keyboard/keyboard-shortcuts.test.ts +++ b/packages/app/src/keyboard/keyboard-shortcuts.test.ts @@ -261,9 +261,15 @@ describe("keyboard-shortcuts", () => { action: "sidebar.toggle.left", }, { - name: "keeps Mod+. as sidebar toggle fallback", + name: "matches Mod+. to toggle both sidebars on non-mac", event: { key: ".", code: "Period", ctrlKey: true }, context: { isMac: false }, + action: "sidebar.toggle.both", + }, + { + name: "matches Ctrl+B sidebar toggle on non-mac", + event: { key: "b", code: "KeyB", ctrlKey: true }, + context: { isMac: false }, action: "sidebar.toggle.left", }, { @@ -289,6 +295,13 @@ describe("keyboard-shortcuts", () => { preventDefault: false, stopPropagation: false, }, + { + name: "routes number keys to prompt selection outside editable scopes", + event: { key: "2", code: "Digit2" }, + context: { focusScope: "other" }, + action: "agent.prompt.select", + payload: { index: 2 }, + }, ]; it.each(matchingCases)("$name", ({ @@ -344,11 +357,6 @@ describe("keyboard-shortcuts", () => { event: { key: "k", code: "KeyK", ctrlKey: true }, context: { isMac: false, focusScope: "terminal" }, }, - { - name: "does not bind Ctrl+B on non-mac", - event: { key: "b", code: "KeyB", ctrlKey: true }, - context: { isMac: false }, - }, { name: "does not route message-input actions when terminal is focused", event: { key: "d", code: "KeyD", metaKey: true }, @@ -364,6 +372,11 @@ describe("keyboard-shortcuts", () => { event: { key: " ", code: "Space" }, context: { focusScope: "message-input" }, }, + { + name: "does not route prompt selection while typing in the message input", + event: { key: "1", code: "Digit1" }, + context: { focusScope: "message-input" }, + }, ]; it.each(nonMatchingCases)("$name", ({ event, context }) => { @@ -467,6 +480,9 @@ describe("keyboard-shortcut help sections", () => { "workspace-tab-close-current": ["meta", "W"], "workspace-pane-split-right": ["mod", "\\"], "workspace-pane-close": ["mod", "shift", "W"], + "agent-prompt-select-1": ["1"], + "agent-prompt-select-2": ["2"], + "agent-prompt-select-3": ["3"], }, }, { @@ -477,10 +493,11 @@ describe("keyboard-shortcut help sections", () => { }, }, { - name: "uses mod+period as non-mac left sidebar shortcut", + name: "uses mod+b and mod+period for non-mac sidebar shortcuts", context: { isMac: false, isDesktop: false }, expectedKeys: { - "toggle-left-sidebar": ["mod", "."], + "toggle-left-sidebar": ["mod", "B"], + "toggle-both-sidebars": ["mod", "."], }, }, ]; diff --git a/packages/app/src/keyboard/keyboard-shortcuts.ts b/packages/app/src/keyboard/keyboard-shortcuts.ts index 2540e8549..dcfe8a5a2 100644 --- a/packages/app/src/keyboard/keyboard-shortcuts.ts +++ b/packages/app/src/keyboard/keyboard-shortcuts.ts @@ -814,6 +814,48 @@ const SHORTCUT_BINDINGS: readonly ShortcutBinding[] = [ keys: ["Esc"], }, }, + { + id: "agent-prompt-select-1", + action: "agent.prompt.select", + combo: "1", + repeat: false, + when: { focusScope: "other", commandCenter: false }, + payload: { type: "index" }, + help: { + id: "agent-prompt-select-1", + section: "agent-input", + label: "Select prompt option 1", + keys: ["1"], + }, + }, + { + id: "agent-prompt-select-2", + action: "agent.prompt.select", + combo: "2", + repeat: false, + when: { focusScope: "other", commandCenter: false }, + payload: { type: "index" }, + help: { + id: "agent-prompt-select-2", + section: "agent-input", + label: "Select prompt option 2", + keys: ["2"], + }, + }, + { + id: "agent-prompt-select-3", + action: "agent.prompt.select", + combo: "3", + repeat: false, + when: { focusScope: "other", commandCenter: false }, + payload: { type: "index" }, + help: { + id: "agent-prompt-select-3", + section: "agent-input", + label: "Select prompt option 3", + keys: ["3"], + }, + }, { id: "message-input-send-enter", action: "message-input.action", diff --git a/packages/app/src/panels/agent-panel.tsx b/packages/app/src/panels/agent-panel.tsx index c32116953..fc44380ca 100644 --- a/packages/app/src/panels/agent-panel.tsx +++ b/packages/app/src/panels/agent-panel.tsx @@ -322,6 +322,7 @@ function AgentPanelBody({ attentionReason: agent?.attentionReason, isScreenFocused: isPaneFocused, }); + useEffect(() => { clearOnAgentBlurRef.current = attentionController.clearOnAgentBlur; }, [attentionController.clearOnAgentBlur]); @@ -796,6 +797,7 @@ function AgentPanelBody({ agent={effectiveAgent} streamItems={shouldUseOptimisticStream ? mergedStreamItems : streamItems} pendingPermissions={pendingPermissions} + isPaneFocused={isPaneFocused} routeBottomAnchorRequest={routeBottomAnchorRequest} isAuthoritativeHistoryReady={hasAppliedAuthoritativeHistory} onOpenWorkspaceFile={onOpenWorkspaceFile}