/** Model and ACP rendering for persistent terminal tool results. */ interface RenderedSessionStatusRunning { kind: 'running' } interface RenderedSessionStatusExited { kind: 'exited' exitCode: number | null signal: string | null } type RenderedSessionStatus = RenderedSessionStatusRunning | RenderedSessionStatusExited interface RenderedSessionSnapshot { sessionId: string name?: string type: string pid?: number status: RenderedSessionStatus } interface RenderedSpawnResult extends RenderedSessionSnapshot { motd: string } interface RenderedSendResult { viewport: string waitReason: 'stdin_read' | 'inferred_idle' | 'timeout' | 'session_exit' sessionStatus: RenderedSessionStatus truncated: boolean } interface RenderedSendRead { delta: string truncated: boolean } interface RenderedReadResult { text: string totalLines: number lineBegin: number lineEnd: number truncated: boolean } /** * Render one created session and its bounded MOTD. * @param result - published spawn result. * @returns Model-facing session acknowledgement. */ export function renderSpawn(result: RenderedSpawnResult): string { const label = result.name === undefined ? result.sessionId : `${result.sessionId} (${result.name})` return `started terminal session ${label} [type: ${result.type}]\n${result.motd || '(no startup output)'}` } /** * Render one settled interactive send. * @param result - settled send outcome. * @returns Terminal output plus wait/session markers. */ export function renderSend(result: RenderedSendResult): string { const output = result.viewport || '(no new output)' const status = result.sessionStatus.kind === 'running' ? 'running' : `exited code=${result.sessionStatus.exitCode ?? 'null'} signal=${result.sessionStatus.signal ?? 'null'}` return `${output}\n[wait: ${result.waitReason}]\n[session: ${status}]${result.truncated ? '\n[output truncated]' : ''}` } /** * Render one incremental background operation read. * @param read - consuming operation delta. * @returns Delta plus truncation marker when needed. */ export function renderSendRead(read: RenderedSendRead): string { return `${read.delta}${read.truncated ? `${read.delta.endsWith('\n') || read.delta.length === 0 ? '' : '\n'}[output truncated]` : ''}` } /** * Render one bounded historical page. * @param result - retained scrollback page. * @returns Page text plus pagination and truncation markers. */ export function renderRead(result: RenderedReadResult): string { const output = result.text || '(no retained output)' return `${output}\n[lines: ${result.lineBegin}-${result.lineEnd} of ${result.totalLines}]${result.truncated ? '\n[output truncated]' : ''}` } /** * Render owner-visible live sessions. * @param sessions - fresh owner-scoped snapshots. * @returns One line per session or the empty marker. */ export function renderList(sessions: readonly RenderedSessionSnapshot[]): string { if (sessions.length === 0) return '(no terminal sessions)' return sessions.map((session) => { const name = session.name === undefined ? '' : ` (${session.name})` const pid = session.pid === undefined ? '' : ` pid=${session.pid}` const status = session.status.kind === 'running' ? 'running' : `exited code=${session.status.exitCode ?? 'null'} signal=${session.status.signal ?? 'null'}` return `${session.sessionId}${name} [${session.type}] ${status}${pid}` }).join('\n') }