@@ -223,6 +223,7 @@ import {
223223} from '../utils/tokenUsageDisplay' ;
224224import { agentAPI } from '@/infrastructure/api/service-api/AgentAPI' ;
225225import type { SessionPermissionMode } from '@/infrastructure/api/service-api/AgentAPI' ;
226+ import { isSessionInUseError } from '@/infrastructure/api/errors/TauriCommandError' ;
226227import { isPeerDeviceModeActive } from '@/infrastructure/peer-device/peerModeFlag' ;
227228import { usePeerDeviceModeOptional } from '@/infrastructure/peer-device/peerDeviceContextState' ;
228229import { isBtwSessionDraft } from '../utils/modelSelectionTarget' ;
@@ -577,6 +578,10 @@ export const ChatInput: React.FC<ChatInputProps> = ({
577578 // other open session.
578579 const [ sessionPermissionMode , setSessionPermissionMode ] =
579580 useState < SessionPermissionMode | null > ( null ) ;
581+ // A failed read leaves `sessionPermissionMode` at null, which makes the control
582+ // fall back to the user-level default. That fallback is safe, but it must not
583+ // pass for the Session's own selection: this flag keeps the two apart.
584+ const [ sessionPermissionModeUnread , setSessionPermissionModeUnread ] = useState ( false ) ;
580585 // One-off state has two owners: the idle composer arms a future submission,
581586 // while an executing turn keeps a mutable override until it ends.
582587 const [ armedTurnPermissionMode , setArmedTurnPermissionMode ] =
@@ -589,6 +594,10 @@ export const ChatInput: React.FC<ChatInputProps> = ({
589594 sessionId : string | null ;
590595 activeTurnId : string | null ;
591596 } > ( { sessionId : null , activeTurnId : null } ) ;
597+ // Reports a fallback to the default once per Session: the read effect re-runs
598+ // on every Session and turn change, so without this the same unresolved read
599+ // would notify on each pass.
600+ const permissionModeUnreadNotifiedRef = useRef < string | null > ( null ) ;
592601 const { addMessage : addToHistory , getSessionHistory } = useInputHistoryStore ( ) ;
593602
594603 const conversationScope = useConversationViewScope ( ) ;
@@ -2426,6 +2435,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
24262435 if ( sessionChanged ) {
24272436 setArmedTurnPermissionMode ( null ) ;
24282437 setActiveTurnPermissionMode ( null ) ;
2438+ setSessionPermissionModeUnread ( false ) ;
24292439 } else if ( activeTurnChanged ) {
24302440 // A locally submitted one-off becomes the active turn's initial mode.
24312441 // Keep it armed until start_dialog_turn acknowledges so a failed send
@@ -2437,6 +2447,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
24372447
24382448 if ( ! effectiveTargetSessionId || isAcpTargetSession ) {
24392449 setSessionPermissionMode ( null ) ;
2450+ setSessionPermissionModeUnread ( false ) ;
24402451 setArmedTurnPermissionMode ( null ) ;
24412452 setActiveTurnPermissionMode ( null ) ;
24422453 return undefined ;
@@ -2455,15 +2466,27 @@ export const ChatInput: React.FC<ChatInputProps> = ({
24552466 } ) ;
24562467 if ( permissionModeRequestGenerationRef . current !== generation ) return ;
24572468 setSessionPermissionMode ( response . mode ?? null ) ;
2469+ setSessionPermissionModeUnread ( false ) ;
2470+ permissionModeUnreadNotifiedRef . current = null ;
24582471 if ( activePermissionTurnId && response . activeTurnId === activePermissionTurnId ) {
24592472 setActiveTurnPermissionMode ( response . turnMode ?? null ) ;
24602473 }
24612474 } catch ( error ) {
24622475 log . warn ( 'Failed to read session permission mode' , error ) ;
24632476 // Falling back to the global default is the safe read: it never shows a
2464- // wider mode than the session actually runs with.
2477+ // wider mode than the session actually runs with. Report the fallback
2478+ // once per Session so it cannot pass for that Session's own selection.
24652479 if ( permissionModeRequestGenerationRef . current === generation ) {
24662480 setSessionPermissionMode ( null ) ;
2481+ setSessionPermissionModeUnread ( true ) ;
2482+ if ( permissionModeUnreadNotifiedRef . current !== effectiveTargetSessionId ) {
2483+ permissionModeUnreadNotifiedRef . current = effectiveTargetSessionId ;
2484+ notificationService . error ( t (
2485+ isSessionInUseError ( error )
2486+ ? 'chatInput.permissionMode.unreadSessionInUse'
2487+ : 'chatInput.permissionMode.unread' ,
2488+ ) ) ;
2489+ }
24672490 }
24682491 }
24692492 } ) ( ) ;
@@ -2477,6 +2500,7 @@ export const ChatInput: React.FC<ChatInputProps> = ({
24772500 effectiveTargetSession ?. parentSessionId ,
24782501 isBtwDraftTarget ,
24792502 isAcpTargetSession ,
2503+ t ,
24802504 ] ) ;
24812505
24822506 const applySessionPermissionMode = useCallback ( async (
@@ -2509,6 +2533,8 @@ export const ChatInput: React.FC<ChatInputProps> = ({
25092533 && effectiveTargetSessionIdRef . current === targetSessionId
25102534 ) {
25112535 setSessionPermissionMode ( response . mode ?? null ) ;
2536+ setSessionPermissionModeUnread ( false ) ;
2537+ permissionModeUnreadNotifiedRef . current = null ;
25122538 setActiveTurnPermissionMode ( null ) ;
25132539 }
25142540 } catch ( error ) {
@@ -2521,7 +2547,11 @@ export const ChatInput: React.FC<ChatInputProps> = ({
25212547 if ( activePermissionTurnIdRef . current === targetTurnId ) {
25222548 setActiveTurnPermissionMode ( previousActiveTurnMode ) ;
25232549 }
2524- notificationService . error ( t ( 'chatInput.permissionMode.changeFailed' ) ) ;
2550+ notificationService . error ( t (
2551+ isSessionInUseError ( error )
2552+ ? 'chatInput.permissionMode.changeFailedSessionInUse'
2553+ : 'chatInput.permissionMode.changeFailed' ,
2554+ ) ) ;
25252555 }
25262556 } finally {
25272557 setPermissionModeSaving ( false ) ;
@@ -2625,6 +2655,8 @@ export const ChatInput: React.FC<ChatInputProps> = ({
26252655 && activePermissionTurnIdRef . current === targetTurnId
26262656 ) {
26272657 setSessionPermissionMode ( response . mode ?? null ) ;
2658+ setSessionPermissionModeUnread ( false ) ;
2659+ permissionModeUnreadNotifiedRef . current = null ;
26282660 setActiveTurnPermissionMode ( response . turnMode ?? null ) ;
26292661 }
26302662 } catch ( error ) {
@@ -2635,7 +2667,11 @@ export const ChatInput: React.FC<ChatInputProps> = ({
26352667 && activePermissionTurnIdRef . current === targetTurnId
26362668 ) {
26372669 setActiveTurnPermissionMode ( previousMode ) ;
2638- notificationService . error ( t ( 'chatInput.permissionMode.changeFailed' ) ) ;
2670+ notificationService . error ( t (
2671+ isSessionInUseError ( error )
2672+ ? 'chatInput.permissionMode.changeFailedSessionInUse'
2673+ : 'chatInput.permissionMode.changeFailed' ,
2674+ ) ) ;
26392675 }
26402676 } finally {
26412677 setPermissionModeSaving ( false ) ;
@@ -6095,6 +6131,10 @@ export const ChatInput: React.FC<ChatInputProps> = ({
60956131 saving : permissionModeSaving ,
60966132 scopeLabel : t ( 'chatInput.permissionMode.sessionScope' ) ,
60976133 overridden : permissionModeOverridden ,
6134+ // The trigger falls back to the user-level default when the read
6135+ // failed, so the menu must not mark that fallback as this
6136+ // Session's own selection.
6137+ unread : sessionPermissionModeUnread ,
60986138 nextTurnMode : temporaryPermissionMode
60996139 ? chatInputPermissionMode ( temporaryPermissionMode )
61006140 : null ,
0 commit comments