From 40aef6af91e6913c98b29d238effaa32c0e0322d Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Fri, 5 Jun 2026 20:50:30 -0500 Subject: [PATCH] feat(desktop): steer the live run from the composer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The desktop app could only queue while busy — `/steer` was in the palette but had no first-class affordance, so the "nudge the agent mid-turn without interrupting" lane was effectively unreachable. Add a steer action to the composer: while busy with a text-only draft, a steering-wheel button (and Cmd/Ctrl+Enter) injects the text into the live turn via the `session.steer` RPC — the gateway folds it into the next tool result so the model reads it on its next iteration. Plain Enter still queues. steerPrompt returns false when the gateway has no live tool window (or the RPC errors), and the composer re-queues the words so nothing is lost — the same safety net as a plain queue. --- .../src/app/chat/composer/controls.tsx | 23 +++++++- apps/desktop/src/app/chat/composer/index.tsx | 36 +++++++++++ apps/desktop/src/app/chat/composer/types.ts | 1 + apps/desktop/src/app/chat/index.tsx | 3 + apps/desktop/src/app/desktop-controller.tsx | 12 +++- .../session/hooks/use-prompt-actions.test.tsx | 59 ++++++++++++++++++- .../app/session/hooks/use-prompt-actions.ts | 49 ++++++++++++++- apps/desktop/src/app/types.ts | 7 +++ apps/desktop/src/i18n/en.ts | 1 + apps/desktop/src/i18n/types.ts | 1 + apps/desktop/src/i18n/zh.ts | 1 + apps/desktop/src/lib/icons.ts | 2 + 12 files changed, 187 insertions(+), 8 deletions(-) diff --git a/apps/desktop/src/app/chat/composer/controls.tsx b/apps/desktop/src/app/chat/composer/controls.tsx index 5e1e3df6fb0..7fbe9efa4a2 100644 --- a/apps/desktop/src/app/chat/composer/controls.tsx +++ b/apps/desktop/src/app/chat/composer/controls.tsx @@ -3,7 +3,7 @@ import { Codicon } from '@/components/ui/codicon' import { Tip } from '@/components/ui/tooltip' import { useI18n } from '@/i18n' import { triggerHaptic } from '@/lib/haptics' -import { AudioLines, Layers3, Loader2, Square } from '@/lib/icons' +import { AudioLines, Layers3, Loader2, Square, SteeringWheel } from '@/lib/icons' import { cn } from '@/lib/utils' import type { ConversationStatus } from './hooks/use-voice-conversation' @@ -38,16 +38,19 @@ interface ConversationProps { export function ComposerControls({ busy, busyAction, + canSteer, canSubmit, conversation, disabled, hasComposerPayload, state, voiceStatus, - onDictate + onDictate, + onSteer }: { busy: boolean busyAction: 'queue' | 'stop' + canSteer: boolean canSubmit: boolean conversation: ConversationProps disabled: boolean @@ -55,6 +58,7 @@ export function ComposerControls({ state: ChatBarState voiceStatus: VoiceStatus onDictate: () => void + onSteer: () => void }) { const { t } = useI18n() const c = t.composer @@ -68,6 +72,21 @@ export function ComposerControls({ return (
+ {canSteer && ( + + + + )} {showVoicePrimary ? (