11import type { SessionStatus } from './activity-monitor' ;
22import type { AlertButtonActionResult } from './alert-manager' ;
33import type { AlertStateDetail } from './platform/types' ;
4- import type { PersistedAlertState } from './session-types' ;
4+ import type { PersistedAlertState , PersistedPane } from './session-types' ;
55import { getPlatform } from './platform' ;
66import {
77 getEntryByPtyId ,
@@ -21,8 +21,19 @@ export const DEFAULT_ACTIVITY_STATE: ActivityState = {
2121
2222const activityListeners = new Set < ( ) => void > ( ) ;
2323let cachedSnapshot : Map < string , ActivityState > | null = null ;
24+
25+ // Transient staging for activity that arrives *before* a terminal registry entry
26+ // exists. Consumed and deleted when the entry is minted (consumePrimedActivity).
2427const primedActivityStates = new Map < string , Partial < ActivityState > > ( ) ;
2528
29+ // Persistent activity for non-PTY surfaces (browser iframes / agent-browser).
30+ // A browser surface never gets a registry entry, so — unlike primedActivityStates
31+ // — this is its permanent home: keyed by pane id, written when its TODO toggles
32+ // or is restored, and cleared only when the pane is killed or replaced
33+ // (clearLocalSurfaceActivity). Kept separate so terminal creation never consumes
34+ // it and a no-arg primed reset never wipes it.
35+ const localSurfaceActivity = new Map < string , ActivityState > ( ) ;
36+
2637export function notifyActivityListeners ( ) : void {
2738 cachedSnapshot = null ;
2839 activityListeners . forEach ( ( listener ) => listener ( ) ) ;
@@ -37,7 +48,7 @@ export function getActivitySnapshot(): Map<string, ActivityState> {
3748 if ( cachedSnapshot ) return cachedSnapshot ;
3849
3950 const snapshot = new Map < string , ActivityState > ( ) ;
40- const ids = new Set < string > ( [ ...registry . keys ( ) , ...primedActivityStates . keys ( ) ] ) ;
51+ const ids = new Set < string > ( [ ...registry . keys ( ) , ...primedActivityStates . keys ( ) , ... localSurfaceActivity . keys ( ) ] ) ;
4152 for ( const id of ids ) {
4253 const state = readActivity ( id ) ;
4354 if ( state ) {
@@ -67,10 +78,13 @@ function readLiveActivity(id: string): ActivityState | null {
6778function readActivity ( id : string ) : ActivityState | null {
6879 const primedState = primedActivityStates . get ( id ) ;
6980 const liveState = readLiveActivity ( id ) ;
81+ const localState = localSurfaceActivity . get ( id ) ;
7082
71- if ( ! liveState && ! primedState ) return null ;
83+ if ( ! liveState && ! primedState && ! localState ) return null ;
84+ // A live PTY is authoritative, so it outranks a stale local-surface entry left
85+ // behind if an id is reused; primed staging overrides on top.
7286 return {
73- ...( liveState ?? DEFAULT_ACTIVITY_STATE ) ,
87+ ...( liveState ?? localState ?? DEFAULT_ACTIVITY_STATE ) ,
7488 ...primedState ,
7589 } ;
7690}
@@ -103,24 +117,38 @@ export function clearPrimedActivity(id?: string): void {
103117 notifyActivityListeners ( ) ;
104118}
105119
120+ /**
121+ * Drop the activity for a non-PTY surface. Called when a browser pane is killed
122+ * or replaced (Wall.tsx) so its TODO doesn't outlive the pane or leak onto a
123+ * later terminal that reuses the id.
124+ */
125+ export function clearLocalSurfaceActivity ( id : string ) : void {
126+ if ( ! localSurfaceActivity . delete ( id ) ) return ;
127+ notifyActivityListeners ( ) ;
128+ }
129+
106130function setLocalSurfaceTodo ( id : string , todo : boolean ) : void {
107131 if ( ! todo ) {
108- if ( ! primedActivityStates . delete ( id ) ) return ;
109- notifyActivityListeners ( ) ;
132+ clearLocalSurfaceActivity ( id ) ;
110133 return ;
111134 }
112135
113- const current = readActivity ( id ) ?? DEFAULT_ACTIVITY_STATE ;
114- primedActivityStates . set ( id , {
115- ...current ,
116- status : 'WATCHING_DISABLED' ,
117- watchingEnabled : false ,
118- todo : true ,
119- notification : null ,
120- } ) ;
136+ localSurfaceActivity . set ( id , { ...DEFAULT_ACTIVITY_STATE , todo : true } ) ;
121137 notifyActivityListeners ( ) ;
122138}
123139
140+ /**
141+ * Restore a browser surface's persisted TODO into the local activity store.
142+ * Browser surfaces have no PTY, so the TODO is reconstructed from the saved pane
143+ * (the `alert` blob) rather than replayed from a PTY alert. Shared by the cold
144+ * restore (session-restore.ts) and live resume (reconnect.ts) paths.
145+ */
146+ export function restoreBrowserSurfaceTodo ( pane : Pick < PersistedPane , 'id' | 'surfaceType' | 'alert' > ) : void {
147+ if ( pane . surfaceType === 'browser' && pane . alert ?. todo === true ) {
148+ setLocalSurfaceTodo ( pane . id , true ) ;
149+ }
150+ }
151+
124152export function consumePrimedActivity ( id : string ) : Partial < ActivityState > | undefined {
125153 const primed = primedActivityStates . get ( id ) ;
126154 if ( primed ) {
0 commit comments