fix: validate transcript sessions before resume and fix FAB race condition

Server now checks that transcript .jsonl files exist before creating
terminals, preventing dead sessions from --resume errors. Frontend
shows error banner in modal when resume fails. Fixed race condition
where init() would overwrite FAB terminal selection after page refresh
by guarding with pendingSwitchTarget flag.
This commit is contained in:
2026-02-21 12:51:15 -06:00
parent de16be38a9
commit ba4a1a0059
5 changed files with 109 additions and 3 deletions

View File

@@ -1,7 +1,33 @@
import { spawn, type IPty } from '@skitee3000/bun-pty'
import { existsSync } from 'fs'
import { join } from 'path'
import { homedir } from 'os'
import { PORT_TERMINAL, WORKING_DIR, SHELL, SHELL_ARGS, DEFAULT_SESSION_ID, MAX_BUFFER_LINES, MAX_TERMINALS } from '../config'
import { sessionState, type SessionStatePatch } from './session-state'
// Agent transcript directories (mirrored from transcript-debug.ts)
const AGENT_TRANSCRIPT_DIRS: Record<string, string> = {
ejecutor: join(WORKING_DIR, '.claude-ejecutor', 'projects'),
nucleo000: join(WORKING_DIR, '.claude-nucleo000', 'projects'),
claude: join(homedir(), '.claude', 'projects')
}
const PROJECT_HASH = 'C--Users-jodar-agent-ui'
function getTranscriptProjectDir(agent: string): string | null {
const baseDir = AGENT_TRANSCRIPT_DIRS[agent]
if (!baseDir || !existsSync(baseDir)) return null
const exact = join(baseDir, PROJECT_HASH)
if (existsSync(exact)) return exact
return null
}
function transcriptSessionExists(agent: string, sessionId: string): boolean {
const projectDir = getTranscriptProjectDir(agent)
if (!projectDir) return false
return existsSync(join(projectDir, `${sessionId}.jsonl`))
}
interface TerminalSession {
id: string
pty: IPty
@@ -400,6 +426,18 @@ export function startTerminalServer() {
)
}
// Validate transcript session exists before resuming
const tsId = body.transcriptSessionId
if (tsId && tsId !== '__new__' && body.agent) {
if (!transcriptSessionExists(body.agent, tsId)) {
console.warn(`[Terminal] Transcript session not found: ${tsId} (agent: ${body.agent})`)
return Response.json(
{ error: `Transcript session "${tsId}" not found. It may have been deleted.` },
{ status: 404, headers: corsHeaders }
)
}
}
// Generate ephemeralSessionId server-side
const ephemeralSessionId = `pty-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`