@@ -12,6 +12,10 @@ import {
1212import { useFreebuffSessionStore } from '../state/freebuff-session-store'
1313import { getAuthTokenDetails } from '../utils/auth'
1414import { IS_FREEBUFF } from '../utils/constants'
15+ import {
16+ isFreebuffInstanceOwnedByDeadLocalProcess ,
17+ recordFreebuffInstanceOwner ,
18+ } from '../utils/freebuff-instance-owner'
1519import { logger } from '../utils/logger'
1620import { saveFreebuffModelPreference } from '../utils/settings'
1721
@@ -363,9 +367,9 @@ interface UseFreebuffSessionResult {
363367 * Manages the freebuff waiting-room session lifecycle:
364368 * - GET on mount to probe state (no auto-join; the user picks a model in
365369 * the landing screen, which calls joinFreebuffQueue)
366- * - if the probe sees an existing seat, asks before POSTing to take over
367- * (rotates the instance id so any other CLI on the same account is
368- * superseded)
370+ * - if the probe sees an existing seat, auto-takes-over when the prior
371+ * local owner process is gone; otherwise asks before POSTing to rotate
372+ * the instance id so any other CLI on the same account is superseded
369373 * - polls GET while queued (fast) or active (slow) to keep state fresh
370374 * - re-POSTs on explicit refresh (chat gate rejected us, user switched
371375 * models, user rejoined after ending)
@@ -406,6 +410,9 @@ export function useFreebuffSession(): UseFreebuffSessionResult {
406410 let nextMethod : 'GET' | 'POST' = 'GET'
407411
408412 const apply = ( next : FreebuffSessionResponse ) => {
413+ if ( next . status === 'queued' || next . status === 'active' ) {
414+ recordFreebuffInstanceOwner ( next . instanceId )
415+ }
409416 setSession ( next )
410417 setError ( null )
411418 previousStatus = next . status
@@ -479,6 +486,14 @@ export function useFreebuffSession(): UseFreebuffSessionResult {
479486 ( next . status === 'queued' || next . status === 'active' )
480487 ) {
481488 useFreebuffModelStore . getState ( ) . setSelectedModel ( next . model )
489+ // A fast restart after Ctrl+C can observe the old server row before
490+ // best-effort DELETE lands. If the row belongs to a dead local
491+ // process, silently do the same POST as the Take over button.
492+ if ( isFreebuffInstanceOwnedByDeadLocalProcess ( next . instanceId ) ) {
493+ nextMethod = 'POST'
494+ schedule ( 0 )
495+ return
496+ }
482497 apply ( { status : 'takeover_prompt' , model : next . model } )
483498 return
484499 }
0 commit comments