/** * public/queue.ts — W2: client helper for the server-side PTY-inject queue. * * Enqueue a follow-up prompt that the server fires into a session's PTY the next * time Claude goes idle (so "run tests → open a PR" advances itself while you're * away). Enqueue is a same-origin HTTP POST (Origin-guarded server-side), NOT a * WS frame — it must work with zero tabs open and target any session (e.g. from * the manage page), not just the WS-bound one. * * Discipline (mirrors quick-reply): every call NEVER throws — a failed fetch or * non-2xx response returns a falsy result the caller can ignore. Byte-shuttle: * `text` is sent verbatim; `appendEnter` tells the server to append \r. */ /** Result of an enqueue attempt. `length` = the new queue depth on success. */ export interface EnqueueResult { ok: boolean /** New queue depth (present on success). */ length?: number /** HTTP status on a non-2xx response (absent on network error). */ status?: number } /** POST a follow-up prompt to a session's inject queue. Never throws. */ export async function enqueueFollowup( sessionId: string, text: string, appendEnter: boolean, ): Promise { try { if (typeof fetch === 'undefined') return { ok: false } const res = await fetch(`/live-sessions/${encodeURIComponent(sessionId)}/queue`, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, appendEnter }), }) if (!res.ok) return { ok: false, status: res.status } const data = (await res.json()) as { length?: unknown } return { ok: true, length: typeof data.length === 'number' ? data.length : undefined } } catch { return { ok: false } } } /** DELETE all pending entries for a session (cancel-all). Returns success. Never throws. */ export async function clearQueue(sessionId: string): Promise { try { if (typeof fetch === 'undefined') return false const res = await fetch(`/live-sessions/${encodeURIComponent(sessionId)}/queue`, { method: 'DELETE', credentials: 'same-origin', }) return res.ok } catch { return false } } /** * Extract the queue depth from an incoming server frame. Returns the length for a * `{type:'queue',length}` frame, else null (so callers can filter unrelated * frames). Defensive: tolerates any unknown input shape. */ export function queueLengthFromFrame(msg: unknown): number | null { if (msg === null || typeof msg !== 'object') return null const o = msg as Record if (o['type'] !== 'queue') return null return typeof o['length'] === 'number' ? o['length'] : null }