@@ -257,10 +257,13 @@ func HandleAGUIRunProxy(c *gin.Context) {
257257
258258 log .Printf ("AGUI Proxy: run=%s session=%s/%s msgs=%d" , truncID (runID ), projectName , sessionName , len (rawMessages ))
259259
260+ // Use namespace-qualified session ID to avoid cross-project collisions
261+ namespacedSessionID := projectName + "/" + sessionName
262+
260263 sessionLastSeen .Store (sessionName , time .Now ())
261264
262265 // Store project→session mapping for activity tracking in persistStreamedEvent
263- sessionProjectMap .Store (sessionName , projectName )
266+ sessionProjectMap .Store (namespacedSessionID , projectName )
264267
265268 // Resolve and cache the runner port for this session from the registry.
266269 cacheSessionPort (projectName , sessionName )
@@ -297,7 +300,7 @@ func HandleAGUIRunProxy(c *gin.Context) {
297300 runnerURL := getRunnerEndpoint (projectName , sessionName )
298301
299302 // Start background goroutine to proxy runner SSE → persist + broadcast
300- go proxyRunnerStream (runnerURL , bodyBytes , sessionName , runID , threadID )
303+ go proxyRunnerStream (runnerURL , bodyBytes , sessionName , namespacedSessionID , runID , threadID )
301304
302305 // Return metadata immediately — events arrive via GET /agui/events
303306 c .JSON (http .StatusOK , gin.H {
@@ -309,21 +312,22 @@ func HandleAGUIRunProxy(c *gin.Context) {
309312// proxyRunnerStream connects to the runner's SSE endpoint, reads events,
310313// persists them, and publishes them to the live broadcast pipe. Runs in
311314// a background goroutine so the POST /agui/run handler can return immediately.
312- func proxyRunnerStream (runnerURL string , bodyBytes []byte , sessionName , runID , threadID string ) {
315+ // namespacedSessionID is the namespace-qualified session ID (e.g., "namespace/sessionName") for event persistence.
316+ func proxyRunnerStream (runnerURL string , bodyBytes []byte , sessionName , namespacedSessionID , runID , threadID string ) {
313317 log .Printf ("AGUI Proxy: connecting to runner at %s" , runnerURL )
314318 resp , err := connectToRunner (runnerURL , bodyBytes )
315319 if err != nil {
316320 log .Printf ("AGUI Proxy: runner unavailable for %s: %v" , sessionName , err )
317321 // Publish error events so GET /agui/events subscribers see the failure
318- publishAndPersistErrorEvents (sessionName , runID , threadID , "Runner is not available" )
322+ publishAndPersistErrorEvents (sessionName , namespacedSessionID , runID , threadID , "Runner is not available" )
319323 return
320324 }
321325 defer resp .Body .Close ()
322326
323327 if resp .StatusCode != http .StatusOK {
324328 body , _ := io .ReadAll (resp .Body )
325329 log .Printf ("AGUI Proxy: runner returned %d: %s" , resp .StatusCode , string (body ))
326- publishAndPersistErrorEvents (sessionName , runID , threadID , fmt .Sprintf ("Runner error: HTTP %d" , resp .StatusCode ))
330+ publishAndPersistErrorEvents (sessionName , namespacedSessionID , runID , threadID , fmt .Sprintf ("Runner error: HTTP %d" , resp .StatusCode ))
327331 return
328332 }
329333
@@ -343,7 +347,7 @@ func proxyRunnerStream(runnerURL string, bodyBytes []byte, sessionName, runID, t
343347 // Persist every data event to JSONL
344348 if strings .HasPrefix (trimmed , "data: " ) {
345349 jsonData := strings .TrimPrefix (trimmed , "data: " )
346- persistStreamedEvent (sessionName , runID , threadID , jsonData )
350+ persistStreamedEvent (namespacedSessionID , runID , threadID , jsonData )
347351 }
348352
349353 // Publish raw SSE line to all GET /agui/events subscribers
@@ -356,14 +360,15 @@ func proxyRunnerStream(runnerURL string, bodyBytes []byte, sessionName, runID, t
356360// publishAndPersistErrorEvents generates RUN_STARTED + RUN_ERROR events,
357361// persists them, and publishes to the live broadcast so subscribers get
358362// notified of runner failures.
359- func publishAndPersistErrorEvents (sessionName , runID , threadID , message string ) {
363+ // sessionName is used for broadcasting; namespacedSessionID is used for persistence.
364+ func publishAndPersistErrorEvents (sessionName , namespacedSessionID , runID , threadID , message string ) {
360365 // RUN_STARTED
361366 startEvt := map [string ]interface {}{
362367 "type" : "RUN_STARTED" ,
363368 "threadId" : threadID ,
364369 "runId" : runID ,
365370 }
366- persistEvent (sessionName , startEvt )
371+ persistEvent (namespacedSessionID , startEvt )
367372 startData , _ := json .Marshal (startEvt )
368373 publishLine (sessionName , fmt .Sprintf ("data: %s\n \n " , startData ))
369374
@@ -374,7 +379,7 @@ func publishAndPersistErrorEvents(sessionName, runID, threadID, message string)
374379 "threadId" : threadID ,
375380 "runId" : runID ,
376381 }
377- persistEvent (sessionName , errEvt )
382+ persistEvent (namespacedSessionID , errEvt )
378383 errData , _ := json .Marshal (errEvt )
379384 publishLine (sessionName , fmt .Sprintf ("data: %s\n \n " , errData ))
380385}
0 commit comments