import { ChildSessionLink } from "./child-session-link"; /** * SessionChrome — compact merged session signals above the composer. * * Built-in session chrome for production and embeds. Token-themed * (`--og-session-chrome-*`); hosts override on `.og-session-chrome` or an ancestor. * * ## Host tokens * Defaults live in `tokens.css`. * * | Token | Role | * | --- | --- | * | `--og-session-chrome-surface` / `-open` | Dock fill (collapsed / expanded) | * | `--og-session-chrome-border` / `-open` | Dock edge | * | `--og-session-chrome-highlight` / `-ring` | Sliding chip selection fill + edge | * | `--og-session-chrome-shadow` / `-open` | Elevation | * | `--og-session-chrome-radius` | Dock corner radius | * | `--og-session-chrome-chip-min-height` | Signal chip height | * | `--og-session-chrome-chip-pad-x` / `--og-session-chrome-chip-gap` | Chip padding / rail gap | * | `--og-session-chrome-rail-pad` | Outer rail inset | * | `--og-session-chrome-panel-pad-x` / `-y` | Expanded panel padding | * | `--og-session-chrome-panel-max-height` | Cap for expanded body (scrolls inside) | * | `--og-session-chrome-duration` / `--og-session-chrome-ease` | Expand + pill motion | * | `--og-session-chrome-crossfade-duration` | Segment content opacity crossfade | * | `--og-session-chrome-row-hover` | Inbox / queue row hover wash | * * Inbox and queue stay separate segments. Queue hover actions wire to * `UseTurnQueueResult` (`editTurn` / `steerTurn` / `moveTurn` / `removeTurn`). * The collapsed queue chip previews the latest queued message beside its Steer * action; open queue rows keep Steer visible and reveal the rest on hover. * Inbox has no product dismiss API; pass `onDismissIncoming` when the host * wants a visible action (dev harness may use a local dummy). * * Queue opens by default when chrome is idle and at least one authoritative * prompt is queued. Closing the queue dismisses that session's offer until * occupancy drains; a different session on the same chrome instance may * offer again. An in-flight optimistic Send never opens the drawer — * admission stays a paint-only receipt on the chip so layout does not jump * under the pointer. * * Segment switches keep the panel shell mounted and crossfade content. The * shell uses one CSS grid-track transition for deliberate open/close actions; * live queue reconciliation never feeds measurements back into layout. */ import type { SessionGoal, SessionPendingInputPreview, SessionStatus, SessionTurn, } from "@opengeni/sdk"; import { ActivityIcon, AudioLinesIcon, ArrowDownIcon, ArrowUpIcon, BotIcon, CheckIcon, InboxIcon, ListOrderedIcon, Loader2Icon, PauseIcon, PencilIcon, PlayIcon, Trash2Icon, TerminalIcon, TriangleAlertIcon, XIcon, CornerDownRightIcon, TargetIcon, } from "lucide-react"; import { AnimatePresence, motion, useReducedMotion } from "motion/react"; import { useEffect, useId, useMemo, useRef, useState, type ReactNode } from "react"; import type { ComposerOptimisticMessage, ComposerState } from "../hooks/use-composer"; import type { UseGoalResult } from "../hooks/use-goal"; import type { UseTurnQueueResult } from "../hooks/use-turn-queue"; import { cn } from "../lib/cn"; import { useErrorMessage } from "../lib/error-message"; import { formatClockTime } from "../lib/format"; import { countAuthoritativeQueuedTurns, isAuthoritativeQueuedTurn, QUEUE_REPLACE_DRAFT_COPY, queuedTurnPresentation, queueNeighborAnchors, } from "../queue-presentation-model"; import { requestQueueDraftEdit } from "./queue-draft-policy"; import { QUEUE_ITEM_CONTENT_UNAVAILABLE, queueItemContent } from "./queue-item-content"; import { TimelineAnnotationsChip, type TimelineAnnotationLike } from "./timeline-annotations"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip"; import { SingleSendWording, useOpenGeniText, type OpenGeniText } from "../messages/runtime"; export type SessionChromeSignalId = | "incoming" | "steering" | "queue" | "goal" | "agents" | "commands"; export type SessionChromeSignalTone = "neutral" | "accent" | "waiting" | "running"; export type SessionChromeAgentsSignal = { count: number; detail?: string | undefined; tone?: SessionChromeSignalTone | undefined; }; export type SessionChromeProps = { /** Authoritative execution status; omitted by older embedding hosts. */ sessionStatus?: SessionStatus | undefined; queue: UseTurnQueueResult; /** Open and focus an existing queued prompt without changing its delivery. */ queueFocusTarget?: { turnId: string; requestId: number } | undefined; /** Needed for queue edit → composer checkout. Omit with `readOnly`. */ composer?: ComposerState | undefined; /** Focus the composer after a successful queue checkout has applied its draft. * Connect to the composer controller's `focusInput`, or a custom input ref. * Never called for failed checkout or while replacement awaits confirmation. */ onComposerFocus?: (() => void) | undefined; goal?: UseGoalResult | null | undefined; /** Expanded agents body (host supplies tree / list). */ agentsPanel?: ReactNode; /** Active commands only. The host mounts the body only when opened. */ commandsPanel?: ReactNode; commandsCount?: number | undefined; /** Chip summary; when `count > 0` the agents segment appears. */ agentsSignal?: SessionChromeAgentsSignal | undefined; /** * Optional inbox dismiss. Product pending-inputs have no remove API — hosts * (and the gallery) may still pass a handler so the action is visible. */ onDismissIncoming?: ((inputId: string) => void) | undefined; /** Open a typed child update's source through the host's normal authorized route. */ onOpenSession?: ((sessionId: string) => void) | undefined; readOnly?: boolean | undefined; className?: string | undefined; /** Controlled active segment; omit for uncontrolled. */ active?: SessionChromeSignalId | null | undefined; defaultActive?: SessionChromeSignalId | null | undefined; onActiveChange?: ((next: SessionChromeSignalId | null) => void) | undefined; }; type GoalPillState = | "pursuing" | "waiting" | "scheduled" | "session_failed" | "blocked" | "held" | "paused" | "invariant_broken" | "completed"; const GOAL_LABEL = (t: OpenGeniText): Record => ({ pursuing: t("sessionChrome.pursuing"), waiting: t("sessionChrome.waiting"), scheduled: t("sessionChrome.waiting"), blocked: t("sessionChrome.blocked"), session_failed: t("sessionChrome.blockedBySessionFailure"), held: t("sessionChrome.held"), paused: t("sessionChrome.paused"), invariant_broken: t("sessionChrome.needsAttention"), completed: t("sessionChrome.done"), }); /** * Short pill suffix per `pausedReason`. `max_auto_continuations` is pacing * (new input resumes it); `limits` is an unspecified legacy admission gate; `user_pause`/`api` is * the human's own override; `agent` is the model declaring it is blocked on a * human decision. Unknown or legacy reasons keep the bare "Paused". */ const GOAL_PAUSED_REASON_SUFFIX = (t: OpenGeniText): Record => ({ max_auto_continuations: "cap", limits: "limits", model_unavailable: "model", model_policy: "policy", credits: "credits", budget: "budget", usage_limit: t("sessionChrome.usageLimit"), usage_policy: "limits", allowance: "allowance", session_usage_limit: t("sessionChrome.usageLimit"), user_pause: "manually", api: "manually", agent: "agent", }); const GOAL_PAUSED_REASON_EXPLANATION = (t: OpenGeniText): Record => ({ max_auto_continuations: t("sessionChrome.pausedAtTheAutomaticContinuation"), limits: t("sessionChrome.pausedByAnAdmissionLimit"), model_unavailable: t("sessionChrome.theSelectedModelIsUnavailable"), model_policy: t("sessionChrome.theSelectedModelIsBlocked"), credits: t("sessionChrome.insufficientOpengeniCreditsAddCredits"), budget: t("sessionChrome.monthlyModelSpendingLimitReached"), usage_limit: t("sessionChrome.monthlyAgentRunLimitReached"), usage_policy: t("sessionChrome.theApplicationSUsagePolicy"), allowance: t("sessionChrome.opengeniUsageAllowanceExhaustedResume"), session_usage_limit: t("sessionChrome.pausedAtTheTaskS"), user_pause: t("sessionChrome.pausedManuallyByAPerson"), api: t("sessionChrome.pausedManuallyByAPerson"), agent: t("sessionChrome.pausedByTheAgentIt"), }); type GoalPillRecord = Pick & { rationale?: SessionGoal["rationale"]; continuation?: SessionGoal["continuation"] | null | undefined; }; /** Pill label, with the pause reason spelled out: "Paused · cap" / "Paused · manually". */ export function sessionChromeGoalPillLabel( t: OpenGeniText, state: GoalPillState, record: GoalPillRecord | null | undefined, ): string { if (state !== "paused") return GOAL_LABEL(t)[state]; const suffix = record?.pausedReason ? GOAL_PAUSED_REASON_SUFFIX(t)[record.pausedReason] : undefined; return suffix ? t("sessionChrome.pausedSuffix", { suffix }) : t("sessionChrome.paused"); } /** * One human sentence explaining WHY the goal is not pursuing right now: the * pause reason, the agent's own `wait_for_input` hold (reason + deadline), or the * next idle-backoff check time. Null when the state needs no explanation. */ export function sessionChromeGoalPillExplanation( t: OpenGeniText, state: GoalPillState, record: GoalPillRecord | null | undefined, ): string | null { const continuation = record?.continuation ?? null; if (state === "session_failed") { return t("sessionChrome.resolveTheSessionFailureThen"); } if (state === "paused") { const rationale = record?.rationale?.trim(); if (rationale) return rationale; return record?.pausedReason ? (GOAL_PAUSED_REASON_EXPLANATION(t)[record.pausedReason] ?? null) : null; } if (state === "scheduled") { return continuation?.nextAttemptAt ? t("sessionChrome.continuesAtClocktime", { clockTime: formatClockTime(t, continuation.nextAttemptAt), }) : t("sessionChrome.waitingToContinueAutomatically"); } if (state === "waiting" && continuation?.reason === "system_work_pending") { return t("sessionChrome.waitingForOtherSessionWork"); } if (state === "held" && continuation?.reason === "held_for_input") { const reason = continuation.holdReason?.trim(); const until = continuation.nextAttemptAt ? ` until ${formatClockTime(t, continuation.nextAttemptAt)}` : ""; return t("sessionChrome.waitingForInputValueUntil", { value: reason ? `: ${reason}` : "", until, }); } return null; } export { countAuthoritativeQueuedTurns }; function isOptimisticQueuedMessage( message: ComposerOptimisticMessage, queuedTurnIds: ReadonlySet, snapshot: UseTurnQueueResult["snapshot"], ): boolean { return ( message.delivery === "send" && message.destination === "queue" && (!message.turnId || !queuedTurnIds.has(message.turnId)) && !( message.turnId && message.appliedQueueVersion !== null && message.appliedQueueVersion !== undefined && snapshot && snapshot.version >= message.appliedQueueVersion ) ); } export function countOptimisticQueuedMessages( messages: readonly ComposerOptimisticMessage[] | undefined, queuedTurnIds: ReadonlySet, snapshot: UseTurnQueueResult["snapshot"], ): number { return (messages ?? []).filter((message) => isOptimisticQueuedMessage(message, queuedTurnIds, snapshot), ).length; } /** * First uncontrolled segment. An existing authoritative queue opens itself * when the host did not pick another default; optimistic-only occupancy * stays collapsed so a live Send does not shove a drawer under the pointer. */ export function sessionChromeInitialActive(input: { defaultActive: SessionChromeSignalId | null; authoritativeQueuedCount: number; }): SessionChromeSignalId | null { if (input.defaultActive != null) return input.defaultActive; return input.authoritativeQueuedCount >= 1 ? "queue" : null; } /** * Whether idle chrome should offer the queue panel. A dismissed session * stays collapsed until occupancy drains. Controlled hosts own this. */ export function sessionChromeShouldOfferQueue(input: { controlled: boolean; active: SessionChromeSignalId | null; activityOpen: boolean; authoritativeQueuedCount: number; suppressed: boolean; }): boolean { return ( !input.controlled && input.active === null && !input.activityOpen && input.authoritativeQueuedCount >= 1 && !input.suppressed ); } /** Select pill state from the goal's authoritative continuation projection. */ export function sessionChromeGoalPillState( goalStatus: "active" | "paused" | "completed", continuation: SessionGoal["continuation"] | null | undefined, sessionStatus?: SessionStatus, ): GoalPillState { if (goalStatus === "completed") return "completed"; if (goalStatus === "paused") return "paused"; if (sessionStatus === "failed") return "session_failed"; if (!continuation) return "invariant_broken"; if (continuation.state === "running") { return continuation.reason === "goal_turn_running" ? "pursuing" : continuation.reason === "human_turn_running" ? "waiting" : "invariant_broken"; } // `backoff_pending` (idle pacing between consecutive no-input continuations, // next evaluation at `nextAttemptAt`) is an ordinary scheduled state. if (continuation.state === "scheduled") return "scheduled"; if (continuation.state === "blocked") { if ( continuation.reason === "human_turn_running" || continuation.reason === "system_work_pending" ) { return "waiting"; } // `held_for_input` is the agent's own wait_for_input hold (waiting for child // results / external input until a deadline); it shares the Held pill. return continuation.reason === "workstream_paused" || continuation.reason === "held_for_input" ? "held" : "blocked"; } return "invariant_broken"; } function formatCoarseElapsed(ms: number): string { const totalSeconds = Math.max(0, Math.floor(ms / 1000)); const days = Math.floor(totalSeconds / 86_400); const hours = Math.floor((totalSeconds % 86_400) / 3_600); const minutes = Math.floor((totalSeconds % 3_600) / 60); const seconds = totalSeconds % 60; if (days > 0) return `${days}d ${hours}h`; if (hours > 0) return `${hours}h ${minutes}m`; if (minutes > 0) return `${minutes}m ${seconds}s`; return `${seconds}s`; } function useLiveElapsed( startIso: string | null | undefined, live: boolean, endIso?: string | null, ) { const start = startIso ? Date.parse(startIso) : Number.NaN; const [now, setNow] = useState(() => Date.now()); useEffect(() => { if (!live) return; const id = setInterval(() => setNow(Date.now()), 1_000); return () => clearInterval(id); }, [live]); if (!Number.isFinite(start)) return null; const end = live ? now : endIso ? Date.parse(endIso) : now; return formatCoarseElapsed((Number.isFinite(end) ? end : now) - start); } function pendingKindLabel(t: OpenGeniText, kind: SessionPendingInputPreview["kind"]): string { switch (kind) { case "child_terminal_result": return t("sessionChrome.childResult"); case "child_requires_action": return t("sessionChrome.childNeedsInput"); case "child_requires_action_resolved": return t("sessionChrome.childUnblocked"); case "child_paused": return t("sessionChrome.childPaused"); case "child_waiting_capacity": return t("sessionChrome.childWaiting"); case "child_progress": return t("sessionChrome.childProgress"); case "agent_steer_instruction": return t("sessionChrome.steer"); case "scheduled_occurrence": return t("sessionChrome.schedule"); case "goal_continuation": return t("sessionChrome.goalWake"); case "agent_message": return t("sessionChrome.update"); case "background_command_result": return t("sessionChrome.commandResult"); case "session_wait_timeout": return t("sessionChrome.waitEnded"); case "media_generation_result": return t("sessionChrome.videoResult"); default: return t("sessionChrome.incoming"); } } function toneClass(tone: SessionChromeSignalTone, selected: boolean): string { switch (tone) { case "accent": return "text-og-accent"; case "waiting": return "text-og-status-waiting"; case "running": return "text-og-status-running"; default: return selected ? "text-og-fg" : "text-og-fg-subtle"; } } /** * Under a single-Send composer (`composer.singleSend`) the chrome speaks of * waiting messages rather than a queue, and offers no Steer on them. */ export function SessionChrome(props: SessionChromeProps) { return ( ); } function SessionChromeView({ sessionStatus, queue, queueFocusTarget, composer, onComposerFocus, goal, agentsPanel, commandsPanel, commandsCount = 0, agentsSignal, onDismissIncoming, onOpenSession, readOnly = false, className, active: activeControlled, defaultActive = null, onActiveChange, }: SessionChromeProps) { const t = useOpenGeniText(); const formatError = useErrorMessage(); const [activityRequested, setActivityOpen] = useState( Boolean(defaultActive && ["incoming", "agents", "commands"].includes(defaultActive)), ); const reactId = useId(); const panelId = `og-session-chrome-panel-${reactId}`; const reduceMotion = useReducedMotion(); const record = goal?.goal ?? null; const incoming = queue.pendingInputs; const turns = queue.queue; const queueMutationFor = queue.mutationFor; const queuedTurns = useMemo( () => turns.filter((turn) => isAuthoritativeQueuedTurn(turn, queueMutationFor)), [queueMutationFor, turns], ); const queuedTurnIds = useMemo(() => new Set(queuedTurns.map((turn) => turn.id)), [queuedTurns]); const optimisticQueued = useMemo( () => (composer?.optimisticMessages ?? []).filter((message) => isOptimisticQueuedMessage(message, queuedTurnIds, queue.snapshot), ), [composer?.optimisticMessages, queue.snapshot, queuedTurnIds], ); const stoppingKind = composer?.stoppingAttempt ?? (queue.stoppingPreviousAttempt ? queue.effectiveControl?.state === "paused" ? "current" : "previous" : null); const stopping = stoppingKind !== null; const canMutateQueue = !readOnly && composer !== undefined; // One Send: nothing to choose, so waiting messages offer no Steer. const canSteerQueued = canMutateQueue && composer?.singleSend !== true; const elapsed = useLiveElapsed(record?.createdAt, Boolean(record)); const goalState = record ? sessionChromeGoalPillState(record.status, record.continuation, sessionStatus) : null; const initialAuthoritativeQueuedCount = countAuthoritativeQueuedTurns( queue.queue, queue.mutationFor, ); const [activeUncontrolled, setActiveUncontrolled] = useState(() => sessionChromeInitialActive({ defaultActive, authoritativeQueuedCount: initialAuthoritativeQueuedCount, }), ); const active = activeControlled !== undefined ? activeControlled : activeUncontrolled; const rootRef = useRef(null); const openedQueueTargetRef = useRef(null); const focusedQueueTargetRef = useRef(null); const queueTargetKey = queueFocusTarget ? `${queueFocusTarget.turnId}:${queueFocusTarget.requestId}` : null; useEffect(() => { if (!queueTargetKey || openedQueueTargetRef.current === queueTargetKey) return; openedQueueTargetRef.current = queueTargetKey; if (activeControlled === undefined) setActiveUncontrolled("queue"); onActiveChange?.("queue"); }, [queueTargetKey, activeControlled, onActiveChange]); useEffect(() => { if (!queueFocusTarget || active !== "queue" || focusedQueueTargetRef.current === queueTargetKey) return; const root = rootRef.current; if (!root) return; const focus = () => { const row = Array.from(root.querySelectorAll("[data-queue-turn-id]")).find( (element) => element.dataset.queueTurnId === queueFocusTarget.turnId, ); if (!row) return false; focusedQueueTargetRef.current = queueTargetKey; row.focus({ preventScroll: true }); row.scrollIntoView?.({ block: "nearest" }); return true; }; if (focus()) return; const observer = new MutationObserver(() => { if (focus()) observer.disconnect(); }); observer.observe(root, { childList: true, subtree: true }); return () => observer.disconnect(); }, [queueFocusTarget, queueTargetKey, active, queuedTurns]); const activityOpen = activityRequested || Boolean(active && ["incoming", "agents", "commands"].includes(active)); const activityVisibleRef = useRef(activityOpen); activityVisibleRef.current = activityOpen; // Two independent suppressions, both occupancy-scoped: // - admission: a live Send while idle must not open a drawer under the pointer // - dismiss: closing the queue remembers that session until occupancy drains const queueOfferAdmissionSuppressedRef = useRef( defaultActive == null && initialAuthoritativeQueuedCount === 0 && countOptimisticQueuedMessages(composer?.optimisticMessages, queuedTurnIds, queue.snapshot) > 0, ); const queueOfferDismissedSessionIdsRef = useRef>(new Set()); const occupancySessionId = queuedTurns[0]?.sessionId ?? null; const setActive = (next: SessionChromeSignalId | null) => { if (active === "queue" && next === null) { if (occupancySessionId) { queueOfferDismissedSessionIdsRef.current.add(occupancySessionId); } else { queueOfferAdmissionSuppressedRef.current = true; } } if (activeControlled === undefined) setActiveUncontrolled(next); onActiveChange?.(next); }; const hasCommandsPanel = commandsPanel != null; const signals = useMemo(() => { const rows: Array<{ id: SessionChromeSignalId; label: string; detail?: string | undefined; /** Hover explanation (why paused / held / when the next check is). */ title?: string | undefined; tone: SessionChromeSignalTone; icon: ReactNode; }> = []; if (incoming.length > 0) { const detail = incoming[0]?.summary; rows.push({ id: "incoming", label: `${incoming.length} in`, ...(detail ? { detail } : {}), tone: incoming.some( (item) => item.classification === "action_required" || item.classification === "failure", ) ? "waiting" : "neutral", icon: , }); } const queuedCount = queuedTurns.length + optimisticQueued.length; const queueProblem = queue.mutationError ?? queue.error; if (queuedCount > 0 || queueProblem) { const presentations = queuedTurns.map((turn) => queuedTurnPresentation(t, turn)); const first = presentations[0]; const allVoiceRequests = optimisticQueued.length === 0 && presentations.length > 0 && presentations.every(({ kind }) => kind === "realtime_voice"); const onlyVoiceHandoff = optimisticQueued.length === 0 && presentations.length === 1 && first?.kind === "realtime_voice_handoff"; const voiceOnly = allVoiceRequests || onlyVoiceHandoff; const detail = queueProblem ? queue.mutationError ? t("sessionChrome.actionNotConfirmed") : t("sessionChrome.queueUnavailable2") : (first?.text ?? optimisticQueued[0]?.text); rows.push({ id: "queue", label: queueProblem ? queuedCount > 0 ? t("sessionChrome.queuedcountQueuedNeedsAttention", { queuedCount }) : t("sessionChrome.queueNeedsAttention") : allVoiceRequests ? queuedCount === 1 ? t("sessionChrome.voiceRequestQueued") : t("sessionChrome.queuedcountVoiceRequestsQueued", { queuedCount }) : onlyVoiceHandoff ? t("sessionChrome.voiceHandoffQueued") : t("sessionChrome.queuedPromptCount", { count: queuedCount }), ...(detail ? { detail } : {}), tone: queueProblem ? "waiting" : "neutral", icon: queueProblem ? ( ) : voiceOnly ? ( ) : ( ), }); } if (record && goalState) { const waiting = goalState === "waiting" || goalState === "blocked" || goalState === "session_failed" || goalState === "held" || goalState === "paused"; const explanation = sessionChromeGoalPillExplanation(t, goalState, record); rows.push({ id: "goal", label: sessionChromeGoalPillLabel(t, goalState, record), detail: elapsed ? `${elapsed} · ${record.text}` : record.text, ...(explanation ? { title: explanation } : {}), tone: waiting ? "waiting" : goalState === "pursuing" || goalState === "scheduled" ? "accent" : "neutral", icon: , }); } if (agentsSignal && agentsSignal.count > 0) { const detail = agentsSignal.detail; rows.push({ id: "agents", label: t("counts.agents", { count: agentsSignal.count }), ...(detail ? { detail } : {}), tone: agentsSignal.tone ?? "neutral", icon: , }); } if (commandsCount > 0 || (active === "commands" && hasCommandsPanel)) { rows.push({ id: "commands", label: commandsCount > 0 ? t("counts.commands", { count: commandsCount }) : t("sessionChrome.commands2"), tone: commandsCount > 0 ? "running" : "neutral", icon: , }); } return rows; }, [ commandsCount, hasCommandsPanel, active, agentsSignal, elapsed, goalState, incoming, optimisticQueued, queue.error, queue.mutationError, queuedTurns, record, t, ]); const optimisticQueueKeys = optimisticQueued.map((message) => message.clientEventId).join(","); const previousOptimisticQueueKeys = useRef(optimisticQueueKeys); const [queueArrivalNonce, setQueueArrivalNonce] = useState(0); useEffect(() => { const previous = new Set(previousOptimisticQueueKeys.current.split(",").filter(Boolean)); const arrived = optimisticQueued.some((message) => !previous.has(message.clientEventId)); previousOptimisticQueueKeys.current = optimisticQueueKeys; if (!arrived) return; // Queue admission must not move the conversation or open a drawer beneath // the pointer. A stable, paint-only receipt on the queue chip communicates // destination without participating in layout. setQueueArrivalNonce((current) => current + 1); if (activeControlled === undefined && active === null) { queueOfferAdmissionSuppressedRef.current = true; } }, [active, activeControlled, optimisticQueueKeys, optimisticQueued]); const queueOccupied = queuedTurns.length > 0 || optimisticQueued.length > 0; useEffect(() => { if (queueOccupied) return; // A later wave may offer again. A prior session's dismiss must not stick // after this instance has actually gone empty. queueOfferAdmissionSuppressedRef.current = false; queueOfferDismissedSessionIdsRef.current.clear(); }, [queueOccupied]); const queueOfferSuppressed = queueOfferAdmissionSuppressedRef.current || (occupancySessionId != null && queueOfferDismissedSessionIdsRef.current.has(occupancySessionId)); useEffect(() => { if ( !sessionChromeShouldOfferQueue({ controlled: activeControlled !== undefined, active, activityOpen, authoritativeQueuedCount: queuedTurns.length, suppressed: queueOfferSuppressed, }) ) { return; } if (activeControlled === undefined) setActiveUncontrolled("queue"); onActiveChange?.("queue"); }, [ active, activeControlled, activityOpen, occupancySessionId, onActiveChange, queueOfferSuppressed, queuedTurns.length, ]); const [replaceDraftFor, setReplaceDraftFor] = useState(null); const chipRefs = useRef>>({}); const railRef = useRef(null); const [pill, setPill] = useState({ left: 0, top: 0, width: 0, height: 0, opacity: 0 }); const signalIds = signals.map((signal) => signal.id).join(","); useEffect(() => { if (!signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id))) setActivityOpen(false); }, [signals]); useEffect(() => { if (active && !signalIds.split(",").includes(active)) { if (activeControlled === undefined) setActiveUncontrolled(null); onActiveChange?.(null); } }, [active, activeControlled, onActiveChange, signalIds]); useEffect(() => { if (active !== "queue" || !replaceDraftFor) return; if (!queuedTurns.some((turn) => turn.id === replaceDraftFor)) { setReplaceDraftFor(null); } }, [active, queuedTurns, replaceDraftFor]); useEffect(() => { const rail = railRef.current; const measure = () => { if (!rail || !active) { setPill((prev) => (prev.opacity === 0 ? prev : { ...prev, opacity: 0 })); return; } const chip = chipRefs.current[active]; if (!chip) { setPill((prev) => ({ ...prev, opacity: 0 })); return; } // Measure against the chip's own box so a wrapped multi-row rail never // stretches the highlight into a tall stripe across every signal. const railBox = rail.getBoundingClientRect(); const chipBox = chip.getBoundingClientRect(); setPill({ left: chipBox.left - railBox.left, top: chipBox.top - railBox.top, width: chipBox.width, height: chipBox.height, opacity: 1, }); }; measure(); if (!rail) return; const observer = new ResizeObserver(measure); observer.observe(rail); for (const chip of Object.values(chipRefs.current)) { if (chip) observer.observe(chip); } window.addEventListener("resize", measure); return () => { observer.disconnect(); window.removeEventListener("resize", measure); }; }, [active, signals]); const open = active !== null; // Collapsed queue chip shows the latest queued message so a Send that lands // in the queue is visible without opening the list; Steer acts on that // message once the server has confirmed it. const latestOptimisticQueued = optimisticQueued[optimisticQueued.length - 1]; const latestQueuedTurn = queuedTurns[queuedTurns.length - 1]; const queuePeek: { text: string | null; turnId: string | null } | null = queue.error || queue.mutationError ? null : latestOptimisticQueued ? { text: latestOptimisticQueued.text.trim() || null, turnId: null } : latestQueuedTurn ? { text: queuedTurnPresentation(t, latestQueuedTurn).text.trim() || null, turnId: latestQueuedTurn.id, } : null; if (signals.length === 0 && !stopping) return null; const shellDuration = reduceMotion ? 0 : 0.22; const crossfadeDuration = reduceMotion ? 0 : 0.18; const ease = [0.22, 1, 0.36, 1] as const; const panelBody = active === "incoming" ? ( ) : active === "queue" ? ( setReplaceDraftFor(null)} onConfirmReplace={ canMutateQueue && composer && replaceDraftFor ? () => { const turnId = replaceDraftFor; setReplaceDraftFor(null); void (async () => { const checkedOut = await queue.editTurn(turnId, { expectedDraftRevision: composer.draftRevision, replaceDraft: true, }); if (checkedOut) { composer.applyDraft(checkedOut); onComposerFocus?.(); } })(); } : undefined } onEdit={ canMutateQueue && composer ? (turn) => { requestQueueDraftEdit( composer, () => setReplaceDraftFor(turn.id), () => { void (async () => { const checkedOut = await queue.editTurn(turn.id, { expectedDraftRevision: composer.draftRevision, replaceDraft: false, }); if (checkedOut) { composer.applyDraft(checkedOut); onComposerFocus?.(); } })(); }, ); } : undefined } onSteer={ canSteerQueued ? (turnId) => { void queue.steerTurn(turnId); } : undefined } onRemove={ canMutateQueue ? (turnId) => { void queue.removeTurn(turnId); } : undefined } onMove={ canMutateQueue ? (turnId, beforeTurnId) => { void queue.moveTurn(turnId, beforeTurnId); } : undefined } /> ) : active === "goal" && record && goalState && goal ? ( ) : active === "commands" ? (
{commandsPanel}
) : active === "agents" ? (
{agentsPanel ?? (

{t("sessionChrome.noAgentDetails")}

)}
) : null; return (
{signals .filter((signal) => !["incoming", "agents", "commands"].includes(signal.id)) .map((signal) => { const selected = active === signal.id; return (
{signal.id === "goal" && goal && record && !readOnly ? (
{record.status === "active" || record.status === "paused" ? ( void (record.status === "paused" ? goal.resume() : goal.pause("Paused from session chrome")) } > {goal.updating ? ( ) : record.status === "paused" ? ( ) : ( )} ) : null} void goal.deleteGoal()} >
) : signal.id === "queue" && canSteerQueued && queuePeek && !selected ? (
} disabled={ !queuePeek.turnId || queue.mutating || Boolean(queue.mutationFor(queuePeek.turnId)) } onClick={() => { if (queuePeek.turnId) void queue.steerTurn(queuePeek.turnId); }} > {queuePeek.turnId && queue.mutationFor(queuePeek.turnId) === "steer" ? ( ) : ( )}
) : null}
); })} {signals.some((signal) => ["incoming", "agents", "commands"].includes(signal.id)) ? ( ) : null} {stopping ? ( ) : null}
{activityOpen ? (
{signals .filter((signal) => ["incoming", "agents", "commands"].includes(signal.id)) .map((signal) => ( ))}
) : null}
{goal?.mutationError ? (

{t("sessionChrome.goalActionNotConfirmedError", { error: formatError(goal.mutationError), })}

) : null}
{active && active !== "commands" && panelBody ? ( {panelBody} ) : null} {active === "commands" ? ( {panelBody} ) : null}
); } function IncomingPanel({ inputs, onDismiss, onOpenSession, }: { inputs: SessionPendingInputPreview[]; onDismiss?: ((inputId: string) => void) | undefined; onOpenSession?: ((sessionId: string) => void) | undefined; }) { const t = useOpenGeniText(); // When a sandbox is shut down or lost, every command on it reports at once. // One row reads better than dozens of identical ones; each result still // reaches the agent. Ordinary results keep their own rows. const lostWithSandbox = inputs.filter(isLostWithSandboxResult); const grouped = new Set( lostWithSandbox.length >= 2 ? lostWithSandbox.map((input) => input.id) : [], ); const firstGrouped = lostWithSandbox.length >= 2 ? lostWithSandbox[0]!.id : null; return (

{t("sessionChrome.waitingToBeIncludedIn")}

    {inputs.map((input) => grouped.has(input.id) ? ( input.id === firstGrouped ? ( ) : null ) : (
  • {pendingKindLabel(t, input.kind)}

    {input.summary}

    {onDismiss ? (
    onDismiss(input.id)} danger >
    ) : null}
  • ), )}
); } /** Matches the worker's notice for a command whose sandbox is gone. */ function isLostWithSandboxResult(input: SessionPendingInputPreview): boolean { return ( input.kind === "background_command_result" && input.summary.includes("is no longer running because its sandbox was shut down or lost") ); } function commandResultsSummary(t: OpenGeniText, inputs: SessionPendingInputPreview[]): string { return t("sessionChrome.lengthBackgroundCommandsAreNo", { length: inputs.length }); } function CommandResultsRow({ inputs, onDismiss, }: { inputs: SessionPendingInputPreview[]; onDismiss?: ((inputId: string) => void) | undefined; }) { const t = useOpenGeniText(); return (
  • {t("sessionChrome.commandResults")}

    {commandResultsSummary(t, inputs)}

    {onDismiss ? (
    { for (const input of inputs) onDismiss(input.id); }} danger >
    ) : null}
  • ); } function QueuePanel({ turns, optimistic, loadError, mutationError, onRefresh, onClearMutationError, onRetryOptimistic, onRestoreOptimistic, onRemoveOptimistic, readOnly, mutationFor, replaceDraftFor, onCancelReplace, onConfirmReplace, onEdit, onSteer, onRemove, onMove, }: { turns: SessionTurn[]; optimistic: ComposerOptimisticMessage[]; loadError: Error | null; mutationError: Error | null; onRefresh: () => Promise; onClearMutationError: () => void; onRetryOptimistic?: ((clientEventId: string) => void) | undefined; onRestoreOptimistic?: ((clientEventId: string) => void) | undefined; onRemoveOptimistic?: ((clientEventId: string) => void) | undefined; readOnly: boolean; mutationFor: UseTurnQueueResult["mutationFor"]; replaceDraftFor?: string | null | undefined; onCancelReplace?: (() => void) | undefined; onConfirmReplace?: (() => void) | undefined; onEdit?: ((turn: SessionTurn) => void) | undefined; onSteer?: ((turnId: string) => void) | undefined; onRemove?: ((turnId: string) => void) | undefined; onMove?: ((turnId: string, beforeTurnId: string | null) => void) | undefined; }) { const t = useOpenGeniText(); const reduceMotion = useReducedMotion(); const turnIdsKey = turns.map((turn) => turn.id).join(","); const [interactiveTurnIds, setInteractiveTurnIds] = useState>( () => new Set(), ); useEffect(() => { const visibleTurnIds = new Set(turnIdsKey ? turnIdsKey.split(",") : []); setInteractiveTurnIds((current) => { const retained = new Set([...current].filter((turnId) => visibleTurnIds.has(turnId))); return retained.size === current.size ? current : retained; }); if (visibleTurnIds.size === 0) return; // An optimistic queue row and its authoritative replacement have different // React identities. Do not expose an actionable control during that short // handoff: a pointer can otherwise press the outgoing DOM node and release // over its replacement, producing a completed-looking click with no event. const timer = setTimeout( () => setInteractiveTurnIds((current) => new Set([...current, ...visibleTurnIds])), reduceMotion ? 0 : 240, ); return () => clearTimeout(timer); }, [reduceMotion, turnIdsKey]); return (
      {turns.map((turn, index) => { const presentation = queuedTurnPresentation(t, turn); const voice = presentation.kind !== "prompt"; const pending = mutationFor(turn.id); const settling = !interactiveTurnIds.has(turn.id); const { beforeUp, beforeDown } = queueNeighborAnchors(turns, index); const showActions = !readOnly && (onEdit || onSteer || onRemove || onMove); const confirmingReplace = replaceDraftFor === turn.id; return (
    1. {voice ? (
      {confirmingReplace ? (

      {QUEUE_REPLACE_DRAFT_COPY(t).title}

      {QUEUE_REPLACE_DRAFT_COPY(t).detail}

      ) : null}
    2. ); })} {optimistic.map((message, index) => ( {turns.length + index + 1}
      {message.state === "failed" && message.error ? (

      {message.error}

      ) : null}
      {message.state === "failed" ? message.outcomeUnknown ? t("sessionChrome.notConfirmed") : t("sessionChrome.messageNotSent") : message.state === "sending" ? t("sessionChrome.placingInQueue") : t("sessionChrome.queued")} {message.state === "failed" ? (
      {message.retryable !== false && onRetryOptimistic ? ( ) : null} {message.retryable === false && onRestoreOptimistic ? ( ) : null} {onRemoveOptimistic ? ( ) : null}
      ) : message.state === "sending" ? (
      ))}
      {loadError ? (
    3. {t("sessionChrome.queueUnavailable")}
    4. ) : null} {mutationError ? (
    5. {t("sessionChrome.notConfirmedCheckTheQueue")}
    6. ) : null}
    ); } function CompactQueueItemContent({ text, annotations, }: { text: string; annotations: readonly TimelineAnnotationLike[]; }) { const t = useOpenGeniText(); const content = queueItemContent(text, annotations.length); return (
    {content === "text" ? (

    {text}

    ) : content === "unavailable" ? (

    {QUEUE_ITEM_CONTENT_UNAVAILABLE(t)}

    ) : null} {annotations.length > 0 ? ( ) : null}
    ); } function GoalPanel({ goal, state, elapsed, readOnly, }: { goal: UseGoalResult; state: GoalPillState; elapsed: string | null; readOnly: boolean; }) { const t = useOpenGeniText(); const formatError = useErrorMessage(); const record = goal.goal; if (!record) return null; const canToggle = !readOnly && (record.status === "active" || record.status === "paused"); const explanation = sessionChromeGoalPillExplanation(t, state, record); return (
    {sessionChromeGoalPillLabel(t, state, record)} {elapsed ? ( {t("sessionChrome.createdElapsedAgo", { elapsed })} ) : null} · v{record.version}

    {record.text}

    {record.successCriteria ? (

    {t("sessionChrome.doneWhen")}{" "} {record.successCriteria}

    ) : null} {explanation ? (

    {explanation}

    ) : null} {record.continuation?.lastError ? (

    {formatError(record.continuation.lastError)}

    ) : null}
    {t("sessionChrome.autocontinuationsConsecutiveUnattendedContinues", { autoContinuations: record.autoContinuations, })} {t("sessionChrome.noprogressstreakStalled", { noProgressStreak: record.noProgressStreak, })}
    {!readOnly ? (
    {canToggle ? ( ) : null}
    ) : null}
    ); } function QueueSteerTip() { const t = useOpenGeniText(); return ( {t("sessionChrome.steer")} {t("sessionChrome.steerTip")} ); } const QUEUE_EDIT_TIP = (t: OpenGeniText) => t("sessionChrome.editInComposer"); const QUEUE_DELETE_TIP = (t: OpenGeniText) => t("sessionChrome.deleteThisQueuedPrompt"); function IconAction({ text, label, analyticsAction, tip, onClick, disabled, danger, children, }: { label: string; /** Stable, content-free control label a host's product analytics may read. */ analyticsAction?: "steer"; text?: string; tip: ReactNode; onClick: () => void; disabled?: boolean; danger?: boolean; children: ReactNode; }) { return ( {tip} ); }