@@ -392,14 +392,14 @@ function AppShellContent({
392392 reportError : reportTaskEntryError ,
393393 manageProjects : openProjectSettings ,
394394 } ) ;
395- // Named on its own because the rail depends on it: `taskEntry.commands` is a
396- // fresh object every render, so depending on the bag rather than the command
397- // would rebuild the rail's Project rows on every AppShell commit (#4109).
395+ /* Named on its own because the rail depends on it: `taskEntry.commands` is a
396+ * fresh object every render, so depending on the bag rather than the command
397+ * would rebuild the rail's Project rows on every AppShell commit (#4109). */
398398 const { selectLocalProject } = taskEntry . commands ;
399399 const currentNewTaskDraftKey = taskEntry . selectors . draftKey ;
400- // Staged files and quotes do NOT take the target-scoped key: they belong to
401- // the composer the user is looking at, and an in-flight send needs an owner
402- // that cannot move under it. See NEW_TASK_PENDING_KEY.
400+ /* Staged files and quotes do NOT take the target-scoped key: they belong to
401+ * the composer the user is looking at, and an in-flight send needs an owner
402+ * that cannot move under it. See NEW_TASK_PENDING_KEY. */
403403 const attachmentDraftKey = activeId ?? NEW_TASK_PENDING_KEY ;
404404 const directoryHostId = activeId
405405 ? ( activeCatalogSession ?. profileKind === 'local'
@@ -437,19 +437,19 @@ function AppShellContent({
437437 clearQuotes,
438438 restoreQuotes,
439439 } = useAppShellComposerQuotes ( { draftKey : attachmentDraftKey } ) ;
440- // Held for the whole of sendOwningItsTarget; see ChatComposerRegion.
440+ /* Held for the whole of sendOwningItsTarget; see ChatComposerRegion. */
441441 const [ newTaskSendPending , setNewTaskSendPending ] = useState ( false ) ;
442- // What a new chat will start with, held the way the Session holds it: a
443- // Plan toggle and one orchestration value, not one fused choice.
442+ /* What a new chat will start with, held the way the Session holds it: a
443+ * Plan toggle and one orchestration value, not one fused choice. */
444444 const [ newChatPlanModeActive , setNewChatPlanModeActive ] = useState ( false ) ;
445445 const [ newChatOrchestrationMode , setNewChatOrchestrationMode ] = useState < OrchestrationMode > ( 'default' ) ;
446446 const [ newTaskPermissionChoice , setNewTaskPermissionChoice , clearNewTaskPermissionChoice ] =
447447 useNewTaskChoice < ChatDefaultPermissionMode > ( currentNewTaskDraftKey ) ;
448448 const [ historyLoadPendingSessionId , setHistoryLoadPendingSessionId ] = useState < string > ( ) ;
449- // The state above is what the transcript renders; this is what the guard
450- // reads. A scroller can ask twice in one task — two scroll events before
451- // React has re-rendered anything — and a state read is still the old value
452- // for both of them.
449+ /* The state above is what the transcript renders; this is what the guard
450+ * reads. A scroller can ask twice in one task — two scroll events before
451+ * React has re-rendered anything — and a state read is still the old value
452+ * for both of them. */
453453 const historyLoadPendingRef = useRef ( false ) ;
454454 const [ transcriptTurnIndex , setTranscriptTurnIndex ] = useState < {
455455 sessionId : string ;
@@ -536,8 +536,8 @@ function AppShellContent({
536536 unsubscribe ( ) ;
537537 } ;
538538 } , [ setNavSelection ] ) ;
539- // #1985: the shell's complete read of session UI state. See the hook for why
540- // the two token-rate maps are absent.
539+ /* #1985: the shell's complete read of session UI state. See the hook for why
540+ * the two token-rate maps are absent. */
541541 const {
542542 messageLoadErrorBySession,
543543 messageRetryPendingBySession,
@@ -548,8 +548,8 @@ function AppShellContent({
548548 streamingSessionIds,
549549 activeLiveTurnSnapshot,
550550 } = useAppShellSessionUiReads ( sessionUiController , activeId ) ;
551- // The chat surface follows the active Session's Host. Settings and global
552- // commands remain owned by the default Host.
551+ /* The chat surface follows the active Session's Host. Settings and global
552+ * commands remain owned by the default Host. */
553553 const { memoryActive, refreshMemoryActive } = useShellMemoryPill ( {
554554 toastApi,
555555 uiLocale,
@@ -712,10 +712,10 @@ function AppShellContent({
712712 } , [ ] ) ;
713713
714714 const updateReminder = updateReminderFromStatus ( appUpdateStatus ) ;
715- // Dispatches on the task, not on the raw status: the footer is this
716- // callback's only caller and it only renders for the two states above, so
717- // reading the status again here would be the same "who needs the user" list
718- // maintained twice.
715+ /* Dispatches on the task, not on the raw status: the footer is this
716+ * callback's only caller and it only renders for the two states above, so
717+ * reading the status again here would be the same "who needs the user" list
718+ * maintained twice. */
719719 const openUpdateDownload = useCallback ( ( ) => {
720720 if ( updateReminder ?. state === 'downloaded' ) {
721721 if ( updateInstallInFlightRef . current ) return ;
@@ -766,9 +766,9 @@ function AppShellContent({
766766 ) ;
767767 } ) ;
768768 } , [ updateReminder , shellCopy , toastApi , uiLocale ] ) ;
769- // Persisted composer defaults seed the empty-state model, project path, and
770- // recent workspace history so the home view is populated before the async
771- // `app:info` round-trip completes on mount.
769+ /* Persisted composer defaults seed the empty-state model, project path, and
770+ * recent workspace history so the home view is populated before the async
771+ * `app:info` round-trip completes on mount. */
772772 const persistedComposerDefaults = loadComposerDefaults ( ) ;
773773 const [ helpOpen , closeHelp , openHelp ] = useKeyboardHelp ( ) ;
774774 const [ paletteOpen , openPalette , closePalette ] = useCommandPalette ( ) ;
@@ -1829,6 +1829,14 @@ function AppShellContent({
18291829 } ) ;
18301830 }
18311831
1832+ const stop = createAppShellStopAction ( {
1833+ uiLocale,
1834+ activeIdRef,
1835+ stopPending : sessionUiController . stopPending ,
1836+ removeTransientMessage,
1837+ toastApi,
1838+ } ) ;
1839+
18321840 /**
18331841 * The send the composer calls, wrapped so the new-task target cannot move
18341842 * out from under it (#3408). `sendCurrent` captures the draft key it
@@ -1920,8 +1928,6 @@ function AppShellContent({
19201928 ? sessionsRef . current . find ( ( session ) => session . id === sessionId ) ?. runningTurnIds
19211929 : undefined ;
19221930 const hasActiveTurn = hasActiveTurnAtSubmit ( { liveTurn, runningTurnIds } ) ;
1923- // Slash commands are not follow-up text for a live turn — dispatch them.
1924- // Explicit Shift+Enter steer / queue still uses metadata.followUpMode.
19251931 const followUpAtSubmit = ! slashCommand ? metadata ?. followUpMode : undefined ;
19261932 if ( sessionId && followUpAtSubmit ) {
19271933 const queued = await enqueueFollowUp ( sessionId , text , followUpAtSubmit , {
@@ -1931,32 +1937,7 @@ function AppShellContent({
19311937 if ( queued ) delete retractedWorkspaceReferencesRef . current [ sessionId ] ;
19321938 return queued ;
19331939 }
1934- // Plain Enter during a live turn: interrupt first, then fall through to a
1935- // new root send so the typed message stops the runaway loop (#4083).
1936- // Interrupt retracts any prior queue entries, so this must precede send.
1937- // Host `turn.interrupt` awaits the cancelled turn's terminal fact before
1938- // resolving, so the Session lane is free for the root send below — not
1939- // merely "usually fast enough" relative to settlement.
1940- if ( sessionId && hasActiveTurn && ! slashCommand ) {
1941- try {
1942- const stopped = await window . maka . sessions . stop ( sessionId , { source : 'stop_button' } ) ;
1943- if ( stopped ?. kind === 'interrupted' ) {
1944- for ( const messageId of stopped . retractedMessageIds ) {
1945- removeTransientMessage ( sessionId , messageId ) ;
1946- }
1947- }
1948- } catch ( error ) {
1949- if ( activeIdRef . current === sessionId ) {
1950- const copy = getDesktopConversationCopy ( uiLocale ) . actions ;
1951- showSessionError (
1952- sessionId ,
1953- copy . operationFailedTitle ,
1954- localizedShellErrorMessage ( error , copy . operationFailedFallback , uiLocale ) ,
1955- ) ;
1956- }
1957- return false ;
1958- }
1959- }
1940+ if ( sessionId && hasActiveTurn && ! slashCommand && ! ( await stop ( ) ) ) return false ;
19601941 if (
19611942 revisionSend &&
19621943 revision &&
@@ -2208,14 +2189,6 @@ function AppShellContent({
22082189 ) ;
22092190 }
22102191
2211- const stop = createAppShellStopAction ( {
2212- uiLocale,
2213- activeIdRef,
2214- stopPending : sessionUiController . stopPending ,
2215- removeTransientMessage,
2216- toastApi,
2217- } ) ;
2218-
22192192 const [ sessionDisplayBatch ] = useState ( createAppShellSessionDisplayBatch ) ;
22202193 const {
22212194 handleEvent,
0 commit comments