diff --git a/apps/desktop/src/app/right-sidebar/terminal/agent-terminal-stream.ts b/apps/desktop/src/app/right-sidebar/terminal/agent-terminal-stream.ts index e22cea5b903..5ffcdb6c22f 100644 --- a/apps/desktop/src/app/right-sidebar/terminal/agent-terminal-stream.ts +++ b/apps/desktop/src/app/right-sidebar/terminal/agent-terminal-stream.ts @@ -40,3 +40,29 @@ export function writeAgentTerminalChunk(procId: string, chunk: string): void { backlog.set(procId, next.length > MAX_BACKLOG ? next.slice(-MAX_BACKLOG) : next) writers.get(procId)?.(chunk) } + +/** Ingest a full output snapshot from process.list/status-stack. This is the + * fallback for older/not-yet-restarted gateways and a seed for tabs opened + * after output already exists. If it extends our current backlog, append only + * the delta; if the registry's rolling tail slid, reset to that tail. */ +export function syncAgentTerminalSnapshot(procId: string, output: string): void { + if (!procId || !output) { + return + } + + const current = backlog.get(procId) ?? '' + + if (output === current) { + return + } + + if (output.startsWith(current)) { + writeAgentTerminalChunk(procId, output.slice(current.length)) + + return + } + + const next = output.length > MAX_BACKLOG ? output.slice(-MAX_BACKLOG) : output + backlog.set(procId, next) + writers.get(procId)?.(`\x1bc${next}`) +} diff --git a/apps/desktop/src/app/right-sidebar/terminal/workspace.tsx b/apps/desktop/src/app/right-sidebar/terminal/workspace.tsx index e63d8e2bad1..987bc9d8af6 100644 --- a/apps/desktop/src/app/right-sidebar/terminal/workspace.tsx +++ b/apps/desktop/src/app/right-sidebar/terminal/workspace.tsx @@ -3,6 +3,7 @@ import { useEffect } from 'react' import { $backgroundStatusBySession } from '@/store/composer-status' +import { syncAgentTerminalSnapshot } from './agent-terminal-stream' import { setActiveTerminalId } from './buffer' import { AgentTerminalInstance, TerminalInstance } from './instance' import { $activeTerminalId, $terminals, ensureAgentTerminal } from './terminals' @@ -30,12 +31,14 @@ export function TerminalWorkspace({ onAddSelectionToChat }: TerminalWorkspacePro } }, []) - // Surface the agent's background processes as read-only tabs (once each); their - // output streams in live via agent.terminal.output, no polling needed. + // Surface the agent's background processes as read-only tabs (once each). + // Live chunks stream via agent.terminal.output; the process-list snapshot also + // seeds/falls back so the tab never stays blank if the stream races startup. useEffect(() => { for (const list of Object.values(background)) { for (const item of list) { ensureAgentTerminal(item.id, item.title) + syncAgentTerminalSnapshot(item.id, item.output ?? '') } } }, [background])