Hooks de React
Ejecuta una sesión de Beeuni en React con el hook useSession.
@beeuni/sdk/react añade hooks de React sobre el SDK. Esta página cubre
useSession, el hook que ejecuta una sesión de principio a fin, y los demás hooks
confirmados como estables para aplicaciones de React.
useSession(projectId, sessionId, options?)
useSession inicia la sesión, abre el transporte de eventos seleccionado por el servidor y
sincroniza los mensajes, el estado y las solicitudes pendientes. Llámalo una vez por vista de sesión.
import { useSession } from '@beeuni/sdk/react';
function Chat({ projectId, sessionId }: { projectId: string; sessionId: string }) {
const s = useSession(projectId, sessionId);
if (s.phase !== 'ready') return <Booting stage={s.stage} onRetry={s.retry} />;
return (
<>
{s.messages.map(({ info, parts }) => (
<Message key={info.id} info={info} parts={parts} />
))}
<Composer busy={s.isBusy} onSend={s.send} onStop={s.cancel} />
</>
);
}
La disponibilidad la determina el servidor. El entorno de ejecución está listo cuando POST /start devuelve
stage: 'ready'. useSession no ejecuta una comprobación de estado independiente en el cliente.
Valores devueltos
| Campo | Tipo | Qué contiene |
|---|---|---|
phase |
'starting' | 'ready' | 'error' |
Estado general. Muestra una pantalla de inicio hasta que ready. |
messages |
{ info, parts }[] |
La lista de mensajes. Las partes llegan en tiempo real. |
savedTranscript |
'loading' | 'shown' | 'none' |
Indica si la conversación guardada puede mostrarse antes de que se active el ordenador. loading: hay una copia guardada en camino. shown: los mensajes están en la lista. none: no se puede mostrar nada hasta que responda el entorno de ejecución. |
conversationEmpty |
boolean |
La copia guardada demuestra que la conversación está vacía, que no ha terminado ningún turno desde entonces y que no hay nada abierto ni en cola. Muestra el compositor, no una pantalla de inicio. |
status |
SessionStatus |
El estado de la sesión. |
isBusy |
boolean |
El agente está generando una respuesta. |
questions, permissions |
array | Preguntas pendientes del agente y solicitudes de aprobación de herramientas. Un permission aquí es una aprobación de herramienta del entorno de ejecución, no un permiso de IAM. |
diffs, todos |
array | Diferencias de archivos y tareas pendientes en tiempo real. |
sendError |
BeeuniSendError | null |
El último error de send: billing, runtime-not-ready o runtime-error. |
rewindMessageId |
string | null |
El mensaje del usuario seleccionado mientras se prepara un retroceso reversible. |
rewindPending |
boolean |
Hay una solicitud de retroceso o restauración en curso. |
rewindError |
BeeuniSendError | null |
El último error de retroceso o restauración. |
models, agents, defaultAgent, commands |
— | Modelos seleccionables, agentes seleccionables, el agente predeterminado y comandos de barra. Disponibles antes de que se inicie el entorno de ejecución. |
retry |
() => void |
Fuerza una nueva comprobación de /start. |
Acciones
| Acción | Qué hace |
|---|---|
send(text, override?) |
Envía una solicitud. override establece { model?, agent? } solo para este mensaje. |
sendParts(parts, override?) |
Envía texto y partes de archivos de URL del entorno de ejecución mediante el transporte seleccionado. Los identificadores privados preparados usan las rutas de solicitudes de la plataforma. |
rewind(messageId) |
Retrocede esta sesión canónica hasta un mensaje del usuario. El mensaje seleccionado y la ruta posterior quedan ocultos y se pueden recuperar. |
restoreRewind() |
Restaura la ruta eliminada antes de que otra solicitud confirme su reemplazo. |
cancel() |
Detiene la ejecución actual y borra las preguntas y los permisos pendientes. Retiene primero las solicitudes en cola de la sesión, para que una solicitud en cola no inicie una nueva ejecución. |
runCommand(command, args) |
Ejecuta un comando de barra del proyecto. |
answerQuestion(id, answers) |
Responde a una pregunta pendiente del agente. |
rejectQuestion(id) |
Rechaza una pregunta pendiente del agente. |
answerPermission(id, reply, message?) |
Responde a una solicitud de aprobación de herramienta. reply es 'once', 'always' o 'reject'. |
useSession también devuelve removeQuestion y removePermission. No los uses. Borran la solicitud del estado local, pero nunca notifican al agente, por lo que la ejecución sigue bloqueada. Usa answerQuestion, rejectQuestion o
answerPermission en su lugar.
Opciones
| Opción | Predeterminado | Qué hace |
|---|---|---|
waitMs |
15000 |
El presupuesto de sondeo prolongado enviado a /start. |
replayStartStash |
true |
Reproduce una solicitud guardada antes de que existiera la sesión, una vez que la sesión esté lista. |
enabled |
true |
Establece false para retrasar el hook, por ejemplo hasta que se apruebe una comprobación de facturación. |
chatEngine |
true |
Establece false si tu aplicación monta su propia interfaz de chat para esta sesión, para evitar sincronizar los mensajes dos veces. |
initialRuntimeSessionId |
null |
Un pin de sesión del entorno de ejecución que tu servidor ya conserva para esta sesión. La transcripción se muestra a partir de él mientras se ejecuta /start; la respuesta de /start sigue siendo la autoridad. initialOpenCodeSessionId es su nombre obsoleto. |
browserPresence |
false |
Mantiene el arrendamiento de presencia de esta pestaña mientras una persona con sesión iniciada usa la página (entrada en los últimos 10 minutos). El arrendamiento mantiene activo el ordenador de la sesión. |
presenceAlerts |
false |
Esta pestaña muestra sus propias notificaciones. Se envía mediante el arrendamiento de presencia: el servidor omite el teléfono y Web Push solo mientras se usa una pestaña con alertas. Un cambio se envía de inmediato. Pasa true solo cuando la marca notification_center del proyecto está activada; el servidor la ignora en caso contrario. Requiere browserPresence. |
presencePageExit |
false |
Finaliza el arrendamiento de presencia cuando la página se cierra o entra en la caché de navegación hacia atrás/adelante, para que un turno que termine justo después de cerrar la pestaña siga enviando una notificación. Activado, pagehide informa de ausencia, y cada informe de ausencia se envía con keepalive, por lo que sobrevive a la página. Desactivado: no hay informe de pagehide ni keepalive. Una pestaña que se cierra sigue pasando a ocultarse e informa de ausencia, pero el navegador puede cancelar esa solicitud mientras descarga la página; el arrendamiento permanece entonces hasta su expiración de 90 s. Pasa la marca notification_center del proyecto. El cambio se aplica de inmediato, sin un nuevo informe de presencia. Requiere browserPresence. |
repositoryMode |
— | Solo telemetría. Una sesión creada antes de que comience un reemplazo del repositorio se inicia y ejecuta con la configuración actual del proyecto sin ese reemplazo. Solo su clon /workspace sigue procediendo del repositorio antiguo, por lo que un push desde él requiere primero un rebase. |
El envío es optimista. send muestra tu mensaje de inmediato y, después, los eventos de transmisión completan la respuesta del agente.
rewind(messageId) nunca crea una sesión. Usa la sesión canónica de POST /start. El entorno de ejecución restaura el estado de los archivos y mantiene recuperable la ruta de transcripción eliminada. La siguiente solicitud aceptada confirma la ruta de reemplazo.
Otros hooks estables
@beeuni/sdk/react también exporta hooks de React Query para datos que no necesitan una sesión en ejecución. Cada uno refleja un método del cliente y no necesita ningún proveedor.
| Hook | Lee |
|---|---|
useProjectModels(projectId) |
Modelos seleccionables para el proyecto. |
useProjectModelPickerCatalog(projectId) |
El registro /model-picker sin procesar por modelo de protocolo (reasoning_options, temperature, limit) y las estimaciones de precios para el cliente de managedPricingRoutes para cada ruta gestionada apta. La ruta que atiende la solicitud determina el cargo final en créditos. |
useProjectSessions(projectId, { parent, startedBy, q, limit }) |
Las sesiones de un proyecto, paginadas. parent: 'root' devuelve las sesiones de nivel superior con child_count; startedBy: 'me' | 'others' | 'automated' filtra por quién inició la ejecución; q busca en todas las sesiones que el usuario puede abrir, no solo en las páginas cargadas. Cada conjunto de filtros tiene su propia entrada de caché. |
useSessionChildren(projectId, parentSessionId, { q, limit, enabled }) |
Las sesiones que generó una sesión principal (parent=<id>), de más reciente a más antigua, paginadas. Establece enabled: false hasta que se abra la sesión principal. |
useVisibleAgents({ projectId }) |
Los agentes visibles del proyecto. |
useProjectConfig(projectId) |
La configuración del entorno de ejecución del proyecto: agente predeterminado, comandos. |
useProjectSecrets(projectId) |
Secretos: listar, añadir, eliminar y anulaciones personales. |
useAccountSecretResources(accountId) |
Metadatos de secretos de proveedores de la cuenta y permisos de uso. Los valores nunca se devuelven. |
useSessionProviderSecretPools(projectId, sessionId) |
Grupos configurados, incluidas las selecciones vacías; setPool actualiza un proveedor y refresca la caché de sesiones. |
useProjectTriggers(projectId) |
Disparadores: listar, crear, actualizar, eliminar, activar. |
useProjectTriggerEventTypes(projectId, connector) |
Los eventos de la aplicación que pueden activar un conector. Inactivo hasta que se establezca connector. |
useChangeRequests(projectId, status?) |
Solicitudes de cambio: listar, abrir, fusionar, cerrar, solicitar cambios. |
useRuntimeSupports(capability) |
Si el entorno de ejecución de la sesión abierta ofrece una función (session.rewind, session.compact, …), según las respuestas de /beeuni/health que lee el sondeador de reconexión del entorno de ejecución. true hasta la primera respuesta. Consulta Qué admite el entorno de ejecución. |
useNotificationInbox({ userId, limit?, enabled? }) |
La página más reciente de la bandeja de entrada de quien llama (limit con valor predeterminado 20), unreadCount y markRead(ids), markAllRead(), markSessionRead(sessionId). Sondea cada 60 s mientras la página está visible y vuelve a obtener los datos al recibir el foco. Una escritura cambia la caché de inmediato, la restaura si falla y luego vuelve a obtener cada entrada de la bandeja de entrada. No se realiza ninguna solicitud sin userId. |
useNotificationPreferences({ userId, enabled? }) |
La elección de notificaciones push y correo electrónico de quien llama por tipo, email_available y update(patch) (optimista). |
useSessionWatch({ userId, projectId, sessionId, enabled? }) |
{ watching } para una sesión y setWatching(watching) (optimista) para silenciarla o seguirla. |
resetRuntimeQueries(queryClient) |
No es un hook. Elimina todas las consultas del entorno de ejecución almacenadas en caché (sesiones, mensajes, agentes, comandos, diferencias). Llámalo después de que el entorno de ejecución de la sesión se sustituya mediante un reinicio o una recarga de configuración, para que la siguiente lectura provenga del nuevo entorno de ejecución. |
Los tres hooks de notificaciones usan userId (qk.notifications) como clave de caché,
por lo que un cambio de usuario en la misma pestaña nunca muestra la bandeja de entrada del usuario anterior. Pasa
el ID del usuario que ha iniciado sesión.
Las notificaciones dependen del notification_center por proyecto,
un indicador de función desactivado de forma predeterminada. Condiciona cada hook con
enabled: useFeatureFlag(projectId, 'notification_center').enabled, para que un
proyecto con el indicador desactivado no envíe ninguna solicitud de notificaciones.
Enlaces de configuración de conectores
Un agente puede generar un enlace de conexión con un clic (/connect/<token>). Los datos del flujo
y la política del entorno de ejecución residen en el SDK; el anfitrión renderiza su propio diálogo sobre ellos e
inyecta sus adaptadores.
| Exportación | Función |
|---|---|
useConnectorSetup(token, options) |
El flujo completo: cargar lo que identifica el enlace, POST /start, abrir la página alojada del proveedor mediante el openPopup inyectado, sondear POST /finalize según el intervalo de 3 s / 5 s / 5 minutos e informar de phase (loading, ready, starting, opened, connected, error), la información de la aplicación, el error y la identidad con la que se autorizó la cuenta. options incluye el backendUrl del anfitrión, el adaptador storage y openPopup; onOpened se activa después de cada apertura de una ventana emergente. |
useConnectorLinkInfo(token, options) |
Una caché compartida y respaldada por almacenamiento por token de enlace, para que todas las tarjetas y el modal compartan un único GET de la ruta pública con límite de solicitudes y muestren el logotipo de la aplicación en el primer fotograma. Las mismas opciones backendUrl/storage. |
createConnectorLinkInfoCache(fetchInfo, { storage }) |
La propia fábrica de caché (sin framework, con almacenamiento inyectado) para un anfitrión que quiera su propia instancia. |
resolveConnectorStart({ start, finalize }) |
La regla de resultado de /start: URL alojada → ventana emergente, ya conectado → finalizar una vez, de lo contrario, el mensaje de error. |
nextConnectorPollDelay(attempt, elapsedMs) |
El programa de sondeo limitado (3 s al principio, 5 s después, se detiene a los 5 minutos). |
Datos de conectores
useConnectorQuery(projectId, slug, action, args, { account, enabled, staleTime })
lee la salida de una acción de conector como una consulta en caché. error es un
ConnectorCallError: renderiza un botón de conexión a partir de error.connectUrl o un
selector de cuentas a partir de error.availableAccounts. Úsalo solo para una acción de
lectura; cada obtención ejecuta la acción y escribe un evento de auditoría. La salida se mantiene
actualizada durante 30 s, recibir el foco nunca vuelve a obtener los datos y solo se
reintenta un 429 o 503 del servicio ascendente. Consulta Conectores desde código.
Inicia pronto la lectura de apertura
prefetchSessionOpen(queryClient, projectId, sessionId) inicia la lectura de apertura de la sesión
antes de que se monte la vista de la sesión. Envía una solicitud GET .../snapshot
(fila de la sesión, turno, cola y los primeros 40 mensajes de la transcripción) e inicializa la
caché de filas de sesiones con ella. useSession usa entonces esa solicitud en lugar de
iniciar la suya propia.
Llámalo cuando una ruta identifique una sesión, o al pasar el cursor, dar el foco o tocar un enlace de
sesión. Es de solo lectura: nunca llama a /start ni activa un ordenador. Nunca
rechaza la promesa. Una llamada repetida para la misma sesión en un plazo de 30 segundos no envía ninguna
solicitud.
import { prefetchSessionOpen } from '@beeuni/sdk/react';
<a
href={`/projects/${projectId}/sessions/${sessionId}`}
onFocus={() => void prefetchSessionOpen(queryClient, projectId, sessionId)}
/>;
Renderiza desde la caché al recargar
Dos almacenes conservan lo que un usuario ya ha visto, para que una recarga o un inicio en frío de la aplicación lo renderice en el primer fotograma y lo actualice en el mismo lugar. Ambos son por usuario y tienen límites, y el anfitrión borra ambos al cerrar sesión.
createPersistedQueryCacheconserva los resultados de React Query que el usuario usa para navegar: cuentas, proyectos, detalles del proyecto, la lista paginada de sesiones y las filas de sesiones (isPersistableQueryKey). Una entrada restaurada conserva su antigüedad original, por lo que vuelve a obtenerse al montar. Los límites predeterminados son 1.000.000 de caracteres y 7 días.createSavedCopyStoreconserva las últimas copias guardadas de las transcripciones que envió el servidor. Regístralo consetSavedCopyStoreyuseSessionmuestra la copia conservada antes del primer fotograma. La copia del servidor se concilia entonces con ella por ID de mensaje, y el SDK la vuelve a leer 3 segundos después de que termine cada turno. Los límites predeterminados son 12 sesiones, 1.500.000 caracteres y 14 días. Una copia de más de 300.000 caracteres conserva los mensajes más recientes que quepan; los mensajes anteriores se cargan desde el servidor cuando el usuario se desplaza hacia arriba.
import {
browserKeyValueStorage,
createPersistedQueryCache,
createSavedCopyStore,
isPersistableQueryKey,
setSavedCopyStore,
} from '@beeuni/sdk';
const storage = browserKeyValueStorage(); // null where there is no localStorage
if (storage) {
const cache = createPersistedQueryCache({ storage, userId, shouldPersist: isPersistableQueryKey });
cache.restore(queryClient); // synchronous with localStorage: no pending frame
const stop = cache.persist(queryClient);
setSavedCopyStore(createSavedCopyStore({ storage, userId }));
}
En React Native, pasa AsyncStorage como storage. restore devuelve entonces una promesa.
El almacén de copias guardadas conserva solo capturas del servidor: la réplica de la transcripción que la API escribe cuando termina un turno. La transcripción en vivo nunca se escribe en el dispositivo, por lo que un turno detenido no puede reaparecer como uno en ejecución. Una lectura del entorno de ejecución que no devuelve mensajes mantiene los mensajes guardados en pantalla: significa que el ordenador perdió su estado, no que la conversación esté vacía. La primera lectura del entorno de ejecución con mensajes los concilia.
Cierre de sesión y cambio de cuenta
El SDK conserva en memoria el estado de sesión por usuario: transcripciones, preguntas
y permisos pendientes, comprobantes de turnos y selecciones de modelos. Llama a resetIdentityState() en
cada cambio de identidad: al cerrar sesión y cuando inicie sesión un usuario diferente. Esto incluye
un inicio de sesión en otra pestaña que cambie el usuario sin cargar la página.
Llama a este controlador cuando tu flujo de autenticación inicie o cierre la sesión de un usuario.
import { resetIdentityState } from '@beeuni/sdk/react';
let previousUserId: string | null = null;
function onIdentityChange(userId: string | null) {
if (userId !== previousUserId) {
queryClient.clear();
resetIdentityState();
}
previousUserId = userId;
}
resetIdentityState() también elimina la entrada localStorage del almacén del modelo. No
borra las cachés propias de la aplicación anfitriona, como React Query, ni la fuente de tokens
pasada a createBeeuni. Restablécelas en el mismo manejador. Nunca lanza excepciones.
Con los almacenes de Renderizar desde la caché al recargar,
bórralos en el mismo manejador: await cache.clear(), después
await savedCopies.clear() y setSavedCopyStore(null). Ninguno de los dos almacenes
restaura datos de otro usuario, pero un dispositivo con la sesión cerrada no debería conservarlos.
Siguiente
- Sesiones — el identificador de sesión que encapsula
useSessiony los tipos deBeeuniSendError. - Referencia — toda la interfaz REST de la que leen estos hooks.
Historial guardado durante el inicio
useSession(projectId, sessionId) muestra el historial de chat guardado mientras se inicia su computadora.
El SDK lee la base de datos independientemente de si el entorno de ejecución está listo. Después, los mensajes en vivo se concilian
mediante sus ID originales. Las aplicaciones anfitrionas no necesitan una solicitud de historial ni un almacén de mensajes independientes.
El historial guardado se renderiza exactamente igual que la transcripción en vivo: texto, razonamiento y cada llamada a herramienta
con su entrada, salida y error. La vista completa de un subagente lee la transcripción guardada del propio
subagente: pasa el ámbito del padre y savedChild: true a
useSessionSync(subAgentSessionId, { beeuniSessionScope, savedChild: true }). La ventana inicial contiene los 40 mensajes más recientes, con
un máximo de 1.000.000 de caracteres. Al desplazarse hacia arriba se cargan ventanas anteriores del historial guardado, incluso mientras
la computadora está apagada. Los archivos adjuntos que requieren la computadora estarán disponibles cuando el
entorno de ejecución esté listo.
savedTranscript indica a la aplicación anfitriona qué renderizar mientras se inicia la computadora. Es loading mientras
aún puede llegar una copia guardada, shown cuando los mensajes están en messages y none cuando no
se puede mostrar nada antes de que responda el entorno de ejecución. Un estado desconocido es loading, nunca none.
conversationEmpty es verdadero cuando no hay nada que esperar. La copia guardada demuestra que la
conversación está vacía: una lectura completa del entorno de ejecución de la sesión no encontró mensajes. Desde entonces no ha terminado ningún turno
y no hay nada abierto ni en cola. La ausencia de registros nunca hace que sea verdadero, por lo que una sesión antigua
con historial nunca se muestra vacía. Una sesión obtiene esa prueba en su primer inicio con el
historial guardado activado.
if (s.messages.length === 0) {
// Nothing saved and nothing to wait for: the user can type now.
if (s.conversationEmpty) return <Composer />;
// One round trip for a saved copy; the computer can take minutes.
return s.savedTranscript === 'none' ? <Booting stage={s.stage} /> : <MessageSkeleton />;
}
Las llamadas a herramientas guardadas antes del 2026-09-28 perdieron su entrada y salida. El servidor sirve cada una con la entrada que acredita su título o sus metadatos conservados: el comando y la salida de un comando, la ruta de una llamada a archivo, el patrón de una búsqueda, la descripción de un subagente, la URL de una solicitud fetch, una lista de tareas pendientes, el nombre de una habilidad. El siguiente inicio de la sesión vuelve a guardar todo el historial, 1.
Prompts pendientes
useSessionPrompts(projectId, sessionId) envía a la bandeja de entrada persistente de la sesión.
El servidor ejecuta automáticamente los prompts pendientes, cada uno como un turno independiente.
Pasa placement: 'transcript' para mostrar un mensaje pendiente en la conversación.
Se ejecuta antes de cada entrada composer y termina la respuesta activa después de que
finalice la llamada a herramienta actual.
Pasa placement: 'composer' para mantenerlo en la lista editable encima del campo de entrada.
Espera a que termine la respuesta activa. Cada ubicación mantiene el orden de envío.
Pasa delivery: 'steer' | 'queue' | 'interrupt' para indicar cómo llega el prompt a un
turno en ejecución. steer lo entrega al turno en su siguiente paso; el turno no
se detiene. queue espera a que termine el turno. interrupt termina el turno después de la
herramienta en ejecución. Con solo delivery, el SDK envía la ubicación que implica:
interrupt es transcript; las demás son composer. Una fila de redirección que no puede
redirigir muestra delivery: 'queue' con steer_fallback establecido en unsupported,
not_prompter o turn_ended. interrupt(promptId) (“Stop and send”) convierte una
fila en espera en una entrada de la transcripción; una fila ya enviada responde 409.
El editor web envía Enter como steer mientras se ejecuta un turno, y como un envío a la transcripción
mientras la sesión está inactiva. Command+Enter (Control+Enter en Windows/Linux)
usa la ubicación del editor. Shift+Enter inserta un salto de línea.
Las filas de la lista incluyen full_text para obtener vistas previas completas tras recargar. text sigue siendo
la vista previa limitada para clientes anteriores. Los bytes de los archivos adjuntos se excluyen de las respuestas de
lista. La eliminación devuelve el prompt completo para poder deshacerla sin pérdidas.
Entrega pendiente y respuestas activas
useSessionWorking() mantiene state: 'working' mientras un prompt espera en la bandeja de entrada
persistente. Esto mantiene disponible Stop. pendingDelivery: true marca un prompt que aún está
llegando al entorno de ejecución; la aplicación web muestra un único indicador de actividad siempre que state
es working, independientemente de lo que indique esa bandera. Los turnos activos del entorno de ejecución y la actividad reciente del
entorno de ejecución lo borran. Una cancelación cuyo tiempo de espera se agota no confirma que la ejecución se haya detenido.
La toma de una tarea por un worker solo comprueba la admisión y mantiene el prompt en espera. La entrega comienza después de que la admisión se complete correctamente. Un turno activo confirmado elimina la presentación de pendiente aunque la instantánea anterior de la bandeja de entrada siga incluyendo ese prompt. La actividad del entorno de ejecución conserva el ID de mensaje del turno activo durante esta transición.
El editor web denomina a Command/Ctrl+Enter Queue List y a “Stop and send” de una fila Quick Queue. Las entradas de Quick Queue se ejecutan primero. Una entrada de Quick Queue en espera es una burbuja coloreada después de los turnos entregados, sin texto de estado. Las entradas de Queue List siguen siendo editables hasta que comienza la entrega. El servidor recupera los eventos de finalización perdidos a partir de pruebas exactas del entorno de ejecución, incluso después de recargar, sin tratar un entorno de ejecución inaccesible como un turno completado.
Cada pulsación independiente de Enter muestra su prompt pendiente de inmediato, incluso mientras una solicitud de aceptación anterior sigue sin resolverse. Solo un fallo de entrega añade texto: su causa, Retry y Remove. La bandeja de entrada del servidor sigue controlando el orden de ejecución.
El hook lee los cambios de la cola desde el flujo de control de la sesión
(GET .../sessions/:id/events?channels=control), con una conexión por sesión
independientemente de cuántos componentes lo monten. Cada escritura de una fila de la bandeja de entrada llega como una
trama beeuni.control.queue. El sondeo GET .../prompts solo se ejecuta mientras ese
flujo no está conectado.
Por qué terminó un turno
La transcripción de un turno interrumpido solo contiene MessageAbortedError. El
plano de control conserva lo que sabe sobre cómo terminó cada turno: la causa indicada por el
sandbox, por ejemplo SandboxMemoryGuard, y qué turnos terminaron sin
una causa indicada. Una detención solicitada por alguien —el botón Stop, abort(), un prompt
enviado a una sesión ocupada— se registra como una solicitud y nunca se informa como un
fallo.
useSessionTurnOutcome(projectId, sessionId) devuelve last_ended,
recent_failures y la hora de lectura atMs desde la caché que useSessionWorking()
mantiene actualizada. No realiza ninguna solicitud. Pasa el resultado a turnEndNotice, junto con lo que
la transcripción indica sobre el turno:
const outcome = useSessionTurnOutcome(projectId, sessionId);
const notice = turnEndNotice(outcome, turn.userMessage.info.id, {
hasError: Boolean(turnError),
isAbort: turnErrorIsAbort,
});
// notice: { kind: 'sandbox-memory', usedPct: 97, detail: 'sandbox memory at 97% …' }
// | { kind: 'cause', name: 'SomeGuard', message: '…' }
// | { kind: 'unexplained' }
// | null
La aplicación anfitriona asigna a cada kind su propio texto y nunca analiza el
mensaje del sandbox. null significa “mostrar lo que muestra la transcripción”: un error real se mantiene; una
interrupción no renderiza nada. Un fallo sin explicación solo se informa cuando la lectura
se produce unos segundos después del final del turno, porque la causa suele llegar una trama
después de la interrupción. turnEndCause(outcome, messageId) devuelve la causa
registrada sin procesar, o null.