File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -1902,13 +1902,22 @@ export class SessionService {
19021902 }
19031903
19041904 // Steer: the user sent a message mid-turn and asked to fold it into the
1905- // running turn rather than queue it. Native (Claude) injects at the next
1906- // tool boundary; everything else interrupts the turn and resends below as a
1907- // fresh prompt. Compaction always falls through to the queue.
1908- if ( options ?. steer && session . isPromptPending && ! session . isCompacting ) {
1909- const supportsNativeSteer =
1910- ! session . isCloud && session . adapter === "claude" ;
1911- if ( supportsNativeSteer ) {
1905+ // running turn rather than queue it. Native (Claude, local) injects at the
1906+ // next tool boundary; local Codex interrupts the turn and resends below as
1907+ // a fresh prompt.
1908+ //
1909+ // Cloud has no real mid-turn steer: the backend only delivers user messages
1910+ // between turns, so a cloud "steer" would cancel the running turn for no
1911+ // gain (the message lands next turn either way) while surfacing a jarring
1912+ // interruption. Until the backend supports true steering, cloud steer falls
1913+ // through to the queue like a normal message. Compaction also falls through.
1914+ if (
1915+ options ?. steer &&
1916+ ! session . isCloud &&
1917+ session . isPromptPending &&
1918+ ! session . isCompacting
1919+ ) {
1920+ if ( session . adapter === "claude" ) {
19121921 return this . sendSteerPrompt ( session , prompt ) ;
19131922 }
19141923 await this . cancelPrompt ( taskId ) ;
Original file line number Diff line number Diff line change @@ -28,8 +28,12 @@ export function QueuedMessagesDock({ taskId }: QueuedMessagesDockProps) {
2828 const sessionService = useService < SessionService > ( SESSION_SERVICE ) ;
2929 const supportsNativeSteer = useSupportsNativeSteer ( taskId ) ;
3030 const returnToEditor = useReturnQueuedMessageToEditor ( taskId ) ;
31+ const session = useSessionForTask ( taskId ) ;
3132 // Steer can't inject mid-compaction, so it would be a silent no-op; hide it.
32- const isCompacting = useSessionForTask ( taskId ) ?. isCompacting ?? false ;
33+ // Cloud has no real mid-turn steer either (it would just interrupt the turn),
34+ // so hide it there too — the message stays queued and lands next turn.
35+ const canSteer =
36+ ! ( session ?. isCompacting ?? false ) && ! ( session ?. isCloud ?? false ) ;
3337
3438 if ( queued . length === 0 ) return null ;
3539
@@ -41,9 +45,8 @@ export function QueuedMessagesDock({ taskId }: QueuedMessagesDockProps) {
4145 message = { message }
4246 supportsNativeSteer = { supportsNativeSteer }
4347 onSteer = {
44- isCompacting
45- ? undefined
46- : ( ) => {
48+ canSteer
49+ ? ( ) => {
4750 void sessionService
4851 . steerQueuedMessage ( taskId , message . id )
4952 . catch ( ( ) => {
@@ -52,6 +55,7 @@ export function QueuedMessagesDock({ taskId }: QueuedMessagesDockProps) {
5255 ) ;
5356 } ) ;
5457 }
58+ : undefined
5559 }
5660 onReturnToEditor = { ( ) => returnToEditor ( message ) }
5761 onRemove = { ( ) =>
Original file line number Diff line number Diff line change @@ -650,11 +650,13 @@ export function SessionView({
650650 ) : null
651651 }
652652 messagingModeToggle = {
653- taskId ? (
653+ taskId && ! isCloudRun ? (
654654 < SteerQueueToggle taskId = { taskId } />
655655 ) : undefined
656656 }
657- onToggleMessagingMode = { toggleMessagingMode }
657+ onToggleMessagingMode = {
658+ isCloudRun ? undefined : toggleMessagingMode
659+ }
658660 onBeforeSubmit = { handleBeforeSubmit }
659661 onSubmit = { handleSubmit }
660662 onBashCommand = { onBashCommand }
Original file line number Diff line number Diff line change @@ -3644,6 +3644,35 @@ describe("SessionService", () => {
36443644 expect ( mockTrpcCloudTask . sendCommand . mutate ) . not . toHaveBeenCalled ( ) ;
36453645 } ) ;
36463646
3647+ it ( "queues a cloud steer instead of interrupting the running turn" , async ( ) => {
3648+ // Regression: cloud has no native mid-turn steer, so steering used to
3649+ // fall back to cancel-then-resend — which surfaced as a jarring user
3650+ // interruption. Cloud steer must now queue like a normal message and
3651+ // never cancel the running turn.
3652+ const service = getSessionService ( ) ;
3653+ mockSessionStoreSetters . getSessionByTaskId . mockReturnValue (
3654+ createMockSession ( {
3655+ isCloud : true ,
3656+ cloudStatus : "in_progress" ,
3657+ status : "connected" ,
3658+ isPromptPending : true ,
3659+ } ) ,
3660+ ) ;
3661+
3662+ const prompt : ContentBlock [ ] = [ { type : "text" , text : "steer me" } ] ;
3663+ const result = await service . sendPrompt ( "task-123" , prompt , {
3664+ steer : true ,
3665+ } ) ;
3666+
3667+ expect ( result . stopReason ) . toBe ( "queued" ) ;
3668+ expect ( mockSessionStoreSetters . enqueueMessage ) . toHaveBeenCalledWith (
3669+ "task-123" ,
3670+ "steer me" ,
3671+ prompt ,
3672+ ) ;
3673+ expect ( mockTrpcCloudTask . sendCommand . mutate ) . not . toHaveBeenCalled ( ) ;
3674+ } ) ;
3675+
36473676 it ( "kicks an SSE retry when queueing on a disconnected cloud session" , async ( ) => {
36483677 const service = getSessionService ( ) ;
36493678 mockSessionStoreSetters . getSessionByTaskId . mockReturnValue (
You can’t perform that action at this time.
0 commit comments