@@ -19,7 +19,7 @@ import {
1919import { create } from "zustand" ;
2020import { immer } from "zustand/middleware/immer" ;
2121import { getCloudUrlFromRegion } from "@/constants/oauth" ;
22- import { trpcReact , trpcVanilla } from "@/renderer/trpc" ;
22+ import { trpcVanilla } from "@/renderer/trpc" ;
2323
2424const log = logger . scope ( "session-store" ) ;
2525const CLOUD_POLLING_INTERVAL_MS = 500 ;
@@ -76,6 +76,41 @@ type SessionStore = SessionState & { actions: SessionActions };
7676
7777const connectAttempts = new Set < string > ( ) ;
7878const cloudPollers = new Map < string , NodeJS . Timeout > ( ) ;
79+ // Track active tRPC subscriptions for cleanup
80+ const subscriptions = new Map < string , { unsubscribe : ( ) => void } > ( ) ;
81+
82+ /**
83+ * Subscribe to agent session events via tRPC subscription.
84+ * Called synchronously after session is created, before any prompts are sent.
85+ */
86+ function subscribeToChannel ( taskRunId : string ) {
87+ if ( subscriptions . has ( taskRunId ) ) return ;
88+
89+ const subscription = trpcVanilla . agent . onSessionEvent . subscribe (
90+ { sessionId : taskRunId } ,
91+ {
92+ onData : ( payload : unknown ) => {
93+ useStore . setState ( ( state ) => {
94+ const session = state . sessions [ taskRunId ] ;
95+ if ( session ) {
96+ session . events . push ( payload as AcpMessage ) ;
97+ }
98+ } ) ;
99+ } ,
100+ onError : ( err ) => {
101+ log . error ( "Session subscription error" , { taskRunId, error : err } ) ;
102+ } ,
103+ } ,
104+ ) ;
105+
106+ subscriptions . set ( taskRunId , subscription ) ;
107+ }
108+
109+ function unsubscribeFromChannel ( taskRunId : string ) {
110+ const subscription = subscriptions . get ( taskRunId ) ;
111+ subscription ?. unsubscribe ( ) ;
112+ subscriptions . delete ( taskRunId ) ;
113+ }
79114
80115function getAuthCredentials ( ) : AuthCredentials | null {
81116 const authState = useAuthStore . getState ( ) ;
@@ -397,6 +432,7 @@ const useStore = create<SessionStore>()(
397432 session . logUrl = logUrl ;
398433
399434 addSession ( session ) ;
435+ subscribeToChannel ( taskRunId ) ;
400436
401437 const result = await trpcVanilla . agent . reconnect . mutate ( {
402438 taskId,
@@ -412,6 +448,7 @@ const useStore = create<SessionStore>()(
412448 if ( result ) {
413449 updateSession ( taskRunId , { status : "connected" } ) ;
414450 } else {
451+ unsubscribeFromChannel ( taskRunId ) ;
415452 removeSession ( taskRunId ) ;
416453 }
417454 } ;
@@ -450,6 +487,7 @@ const useStore = create<SessionStore>()(
450487 session . model = defaultModel ;
451488
452489 addSession ( session ) ;
490+ subscribeToChannel ( taskRun . id ) ;
453491
454492 if ( initialPrompt ?. length ) {
455493 await get ( ) . actions . sendPrompt ( taskId , initialPrompt ) ;
@@ -572,6 +610,7 @@ const useStore = create<SessionStore>()(
572610 } catch ( error ) {
573611 log . error ( "Failed to cancel session" , error ) ;
574612 }
613+ unsubscribeFromChannel ( session . taskRunId ) ;
575614 }
576615
577616 removeSession ( session . taskRunId ) ;
@@ -690,24 +729,3 @@ useAuthStore.subscribe(
690729 }
691730 } ,
692731) ;
693-
694- /**
695- * Hook to subscribe to agent session events via tRPC subscription.
696- * This should be used in a component that renders when a session is active.
697- */
698- export function useAgentSessionSubscription ( sessionId : string | undefined ) {
699- trpcReact . agent . onSessionEvent . useSubscription (
700- { sessionId : sessionId ?? "" } ,
701- {
702- enabled : ! ! sessionId ,
703- onData : ( payload ) => {
704- useStore . setState ( ( state ) => {
705- const session = state . sessions [ sessionId ! ] ;
706- if ( session ) {
707- session . events . push ( payload as AcpMessage ) ;
708- }
709- } ) ;
710- } ,
711- } ,
712- ) ;
713- }
0 commit comments