# React хукови

Покрените Beeuni сесију у React-у помоћу хука useSession.

Canonical page: https://beeuni.ai/docs/sr/sdk/react

`@beeuni/sdk/react` додаје React хукове на SDK. Ова страница описује
`useSession`, хук који води сесију од почетка до краја, и остале хукове
потврђене као стабилне за React апликације.

## useSession(projectId, sessionId, options?)

`useSession` покреће сесију, отвара транспорт догађаја који је сервер изабрао и
синхронизује поруке, статус и упите на чекању. Позовите га једном по приказу сесије.

```tsx
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} />
    </>
  );
}
```

Сервер је меродаван за спремност. Извршно окружење је спремно када `POST /start` врати
`stage: 'ready'`. `useSession` не спроводи засебну проверу исправности на страни клијента.

<a id="returns"></a>

### Повратне вредности

| Поље | Тип | Шта садржи |
|---|---|---|
| `phase` | `'starting' \| 'ready' \| 'error'` | Опште стање. Приказујте екран покретања до `ready`. |
| `messages` | `{ info, parts }[]` | Листа порука. Делови пристижу уживо. |
| `savedTranscript` | `'loading' \| 'shown' \| 'none'` | Да ли сачувани разговор може да се прикаже пре него што се рачунар пробуди. `loading`: сачувана копија је на путу. `shown`: поруке су у листи. `none`: ништа не може да се прикаже док извршно окружење не одговори. |
| `conversationEmpty` | `boolean` | Сачувана копија потврђује да је разговор празан, да од тада ниједан корак није завршен и да ништа није отворено нити у реду чекања. Прикажите поље за писање, а не екран покретања. |
| `status` | `SessionStatus` | Статус сесије. |
| `isBusy` | `boolean` | Агент генерише одговор. |
| `questions`, `permissions` | низ | Питања агента и захтеви за одобрење алата на чекању. `permission` овде представља једно одобрење алата у извршном окружењу, а не IAM дозволу. |
| `diffs`, `todos` | низ | Разлике у датотекама уживо и ставке задатака. |
| `sendError` | `BeeuniSendError \| null` | Последња грешка `send`: `billing`, `runtime-not-ready` или `runtime-error`. |
| `rewindMessageId` | `string \| null` | Изабрана корисничка порука док је припремљено поништиво враћање уназад. |
| `rewindPending` | `boolean` | Захтев за враћање уназад или обнављање је у току. |
| `rewindError` | `BeeuniSendError \| null` | Последња грешка при враћању уназад или обнављању. |
| `models`, `agents`, `defaultAgent`, `commands` | — | Модели који се могу изабрати, агенти који се могу изабрати, подразумевани агент и команде са косом цртом. Доступни су пре покретања извршног окружења. |
| `retry` | `() => void` | Принудно поновите проверу `/start`. |

<a id="actions"></a>

### Радње

| Радња | Шта ради |
|---|---|
| `send(text, override?)` | Шаље упит. `override` поставља `{ model?, agent? }` само за ову поруку. |
| `sendParts(parts, override?)` | Шаље текст и делове датотека са URL адресама извршног окружења преко изабраног транспорта. Приватни припремљени идентификатори користе руте платформе за упите. |
| `rewind(messageId)` | Враћа ову канонску сесију уназад до корисничке поруке. Изабрана порука и каснији ток постају скривени и могу да се обнове. |
| `restoreRewind()` | Обнавља уклоњени ток пре него што други упит потврди његову замену. |
| `cancel()` | Зауставља тренутно извршавање и уклања питања и дозволе на чекању. Прво задржава упите сесије у реду чекања, тако да упит из реда не покрене ново извршавање. |
| `runCommand(command, args)` | Покреће пројектну команду са косом цртом. |
| `answerQuestion(id, answers)` | Одговара на питање агента на чекању. |
| `rejectQuestion(id)` | Одбија питање агента на чекању. |
| `answerPermission(id, reply, message?)` | Одговара на захтев за одобрење алата. `reply` је `'once'`, `'always'` или `'reject'`. |

`useSession` такође враћа `removeQuestion` и `removePermission`. Не користите
их. Уклањају упит из локалног стања, али никада не обавештавају агента, па
извршавање остаје блокирано. Уместо тога користите `answerQuestion`, `rejectQuestion` или
`answerPermission`.

<a id="options"></a>

### Опције

| Опција | Подразумевано | Шта ради |
|---|---|---|
| `waitMs` | `15000` | Временски лимит дугог анкетирања који се шаље у `/start`. |
| `replayStartStash` | `true` | Поново шаље упит сачуван пре него што је сесија постојала, када сесија буде спремна. |
| `enabled` | `true` | Поставите `false` да одложите хук, на пример док провера наплате не прође. |
| `chatEngine` | `true` | Поставите `false` ако ваша апликација монтира сопствени интерфејс за ћаскање за ову сесију, да бисте избегли двоструку синхронизацију порука. |
| `initialRuntimeSessionId` | `null` | Веза са сесијом извршног окружења коју ваш сервер већ чува за ову сесију. Из ње се приказује транскрипт док се `/start` извршава; одговор `/start` остаје меродаван. `initialOpenCodeSessionId` је њен застарели назив. |
| `browserPresence` | `false` | Одржава привремену потврду присуства ове картице док пријављена особа користи страницу (унос у последњих 10 минута). Потврда одржава рачунар сесије будним. |
| `presenceAlerts` | `false` | Ова картица приказује сопствена обавештења. Шаље се уз потврду присуства: сервер прескаче телефон и Web Push само док се користи картица која приказује обавештења. Промена се шаље одмах. Проследите `true` само када је ознака пројекта `notification_center` укључена; у супротном је сервер игнорише. Захтева `browserPresence`. |
| `presencePageExit` | `false` | Завршава потврду присуства када се страница затвори или уђе у кеш за навигацију уназад/унапред, тако да корак који се заврши одмах након затварања картице ипак пошаље обавештење. Укључено: `pagehide` пријављује одсуство, а свака пријава одсуства шаље се са `keepalive`, тако да остаје активна и после затварања странице. Искључено: нема пријаве `pagehide` нити `keepalive`. Картица која се затвара ипак постаје скривена и пријављује одсуство, али прегледач може да откаже тај захтев при уклањању странице; потврда тада важи до истека од 90 с. Проследите ознаку пројекта `notification_center`. Промена се примењује одмах, без нове пријаве присуства. Захтева `browserPresence`. |
| `repositoryMode` | — | Само телеметрија. Сесија направљена пре замене репозиторијума покреће се и ради са тренутном конфигурацијом пројекта без овога. Само њен клон `/workspace` и даље потиче из старог репозиторијума, па слање измена из њега прво захтева rebase. |

Слање је оптимистично. `send` одмах приказује вашу поруку, а затим догађаји
из тока попуњавају одговор агента.

`rewind(messageId)` никада не прави сесију. Користи канонску сесију из
`POST /start`. Извршно окружење обнавља стање датотека и омогућава обнављање уклоњеног
тока транскрипта. Следећи прихваћени упит потврђује заменски ток.

<a id="other-stable-hooks"></a>

## Остали стабилни хукови

`@beeuni/sdk/react` такође извози React Query хукове за податке којима није
потребна покренута сесија. Сваки одговара методи на
[клијенту](/docs/sr/sdk/reference) и не захтева провајдер.

| Хук | Чита |
|---|---|
| `useProjectModels(projectId)` | Моделе који се могу изабрати за пројекат. |
| `useProjectModelPickerCatalog(projectId)` | Сирови `/model-picker` запис за сваки модел у протоколу (`reasoning_options`, `temperature`, `limit`) и `managedPricingRoutes` процене цене за корисника за сваку одговарајућу управљану руту. Рута која обрађује захтев одређује коначну наплату кредита. |
| `useProjectSessions(projectId, { parent, startedBy, q, limit })` | Сесије једног пројекта, по страницама. `parent: 'root'` враћа сесије највишег нивоа са `child_count`; `startedBy: 'me' \| 'others' \| 'automated'` филтрира према томе ко је покренуо извршавање; `q` претражује сваку сесију коју посматрач сме да отвори, не само учитане странице. Сваки скуп филтера има засебан унос у кешу. |
| `useSessionChildren(projectId, parentSessionId, { q, limit, enabled })` | Сесије које је покренула једна родитељска сесија (`parent=<id>`), од најновије, по страницама. Поставите `enabled: false` док се родитељска сесија не отвори. |
| `useVisibleAgents({ projectId })` | Видљиве агенте пројекта. |
| `useProjectConfig(projectId)` | Конфигурацију извршног окружења пројекта: подразумевани агент, команде. |
| `useProjectSecrets(projectId)` | Тајне: листање, додавање, уклањање и лична преоптерећења. |
| `useAccountSecretResources(accountId)` | Метаподатке о тајнама добављача на налогу и дозволе за употребу. Вредности се никада не враћају. |
| `useSessionProviderSecretPools(projectId, sessionId)` | Конфигурисане пулове, укључујући празне изборе; `setPool` ажурира једног добављача и освежава кеш сесија. |
| `useProjectTriggers(projectId)` | Окидаче: листање, креирање, ажурирање, уклањање, активирање. |
| `useProjectTriggerEventTypes(projectId, connector)` | Догађаје апликације на које конектор може да реагује. Неактивно док се не постави `connector`. |
| `useChangeRequests(projectId, status?)` | Захтеве за измене: листање, отварање, спајање, затварање, тражење измена. |
| `useRuntimeSupports(capability)` | Да ли извршно окружење отворене сесије подржава функцију (`session.rewind`, `session.compact`, …), на основу `/beeuni/health` одговора које чита механизам периодичне провере поновног повезивања са извршним окружењем. `true` до првог одговора. Погледајте [Шта извршно окружење подржава](/docs/sr/sdk/sessions#what-the-runtime-supports). |
| `useNotificationInbox({ userId, limit?, enabled? })` | Најновију страницу пријемног сандучета позиваоца (`limit` подразумевано 20), `unreadCount` и `markRead(ids)`, `markAllRead()`, `markSessionRead(sessionId)`. Проверава сваких 60 s док је страница видљива и поново преузима при добијању фокуса. Упис одмах мења кеш, враћа га при неуспеху, а затим поново преузима сваки унос пријемног сандучета. Нема захтева без `userId`. |
| `useNotificationPreferences({ userId, enabled? })` | Избор позиваоца за push и имејл обавештења по врсти, `email_available` и `update(patch)` (оптимистички). |
| `useSessionWatch({ userId, projectId, sessionId, enabled? })` | `{ watching }` за једну сесију и `setWatching(watching)` (оптимистички) за утишавање или праћење. |
| `resetRuntimeQueries(queryClient)` | Није хук. Уклања све кеширане упите извршног окружења (сесије, поруке, агенти, команде, разлике). Позовите га након што поновно покретање или поновно учитавање конфигурације замени извршно окружење сесије, како би следеће читање дошло из новог извршног окружења. |

Три хука за обавештења користе `userId` (`qk.notifications`) као кључ кеша,
тако да промена корисника у истој картици никада не приказује пријемно сандуче претходног корисника. Проследите
ID пријављеног корисника.

Обавештења су условљена `notification_center`
[заставицом функције](/docs/sr/feature-flags) за сваки пројекат, која је подразумевано искључена. Условите сваки хук са
`enabled: useFeatureFlag(projectId, 'notification_center').enabled`, тако да
пројекат са искљученом заставицом не шаље захтеве за обавештења.

<a id="connector-setup-links"></a>

## Везе за подешавање конектора

Агент може да генерише везу за повезивање једним кликом (`/connect/<token>`). Подаци тока
и политика извршног окружења налазе се у SDK-у; апликација домаћин приказује сопствени дијалог над њима и
убацује своје адаптере.

| Извоз | Улога |
|---|---|
| `useConnectorSetup(token, options)` | Цео ток: учитава оно што веза именује, `POST /start`, отвара страницу коју хостује добављач преко убаченог `openPopup`, периодично проверава `POST /finalize` по распореду 3 s / 5 s / 5 минута и пријављује `phase` (`loading`, `ready`, `starting`, `opened`, `connected`, `error`), информације о апликацији, грешку и идентитет под којим је налог ауторизован. `options` носи `backendUrl` домаћина, адаптер `storage` и `openPopup`; `onOpened` се активира после сваког отварања искачућег прозора. |
| `useConnectorLinkInfo(token, options)` | Један дељени кеш поткрепљен складиштем по токену везе, тако да свака картица и модални прозор деле један GET јавне руте са ограниченом учесталошћу захтева и приказују логотип апликације у првом кадру. Исте опције `backendUrl`/`storage`. |
| `createConnectorLinkInfoCache(fetchInfo, { storage })` | Сама фабрика кеша (без зависности од радног оквира, са убаченим складиштем) за домаћина који жели сопствену инстанцу. |
| `resolveConnectorStart({ start, finalize })` | Правило исхода `/start`: хостовани URL → искачући прозор, већ повезано → заврши једном, иначе порука о грешци. |
| `nextConnectorPollDelay(attempt, elapsedMs)` | Ограничен распоред периодичних провера (прво 3 s, затим 5 s, престаје после 5 минута). |

<a id="connector-data"></a>

## Подаци конектора

`useConnectorQuery(projectId, slug, action, args, { account, enabled, staleTime })`
чита излаз једне радње конектора као кеширани упит. `error` је
`ConnectorCallError`: прикажите дугме за повезивање из `error.connectUrl` или
бирач налога из `error.availableAccounts`. Користите га само за радњу која
чита; свако преузимање извршава радњу и уписује догађај ревизије. Излаз остаје
свеж 30 s, добијање фокуса никада не покреће поновно преузимање, а поново се покушава само након узводног `429` или `503`.
Погледајте [Конектори из кода](/docs/sr/sdk/reference#pconnectorslug--one-connector).

<a id="start-the-open-read-early"></a>

## Започните читање за отварање раније

`prefetchSessionOpen(queryClient, projectId, sessionId)` започиње читање за отварање сесије
пре монтирања приказа сесије. Шаље један `GET .../snapshot` захтев
(ред сесије, потез, ред чекања и првих 40 порука транскрипта) и њиме попуњава
кеш реда сесије. `useSession` затим користи тај захтев уместо
да покреће сопствени.

Позовите га када рута именује сесију или при преласку показивача, фокусу или додиру везе
сесије. Само чита: никада не позива `/start` и никада не буди рачунар. Никада
не одбацује обећање. Поновљени позив за исту сесију у року од 30 секунди не шаље
захтев.

```tsx
import { prefetchSessionOpen } from '@beeuni/sdk/react';

<a
  href={`/projects/${projectId}/sessions/${sessionId}`}
  onFocus={() => void prefetchSessionOpen(queryClient, projectId, sessionId)}
/>;
```

<a id="render-from-cache-on-a-reload"></a>

## Приказ из кеша при поновном учитавању

Два складишта чувају оно што је корисник већ видео, тако да поновно учитавање или хладно покретање апликације то приказује
у првом кадру и освежава на месту. Оба су по кориснику и ограничена, а
домаћин брише оба при одјави.

- `createPersistedQueryCache` чува React Query резултате помоћу којих корисник прелази између приказа:
  налоге, пројекте, детаље пројекта, листу сесија по страницама и редове сесија
  (`isPersistableQueryKey`). Враћени унос задржава првобитну старост, па се поново преузима
  при монтирању. Подразумевана ограничења су 1.000.000 знакова и 7 дана.
- `createSavedCopyStore` чува последње сачуване копије транскрипата које је сервер
  послао. Региструјте га са `setSavedCopyStore` и `useSession` приказује сачувану копију
  пре првог кадра. Копија са сервера затим се усклађује с њом по ID-у поруке, а
  SDK је поново чита 3 секунде после завршетка сваког потеза. Подразумевана ограничења су 12
  сесија, 1.500.000 знакова и 14 дана. Копија већа од 300.000 знакова задржава
  најновије поруке које стају; старије поруке се учитавају са сервера када корисник
  скролује нагоре.

```tsx
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 }));
}
```

На React Native-у проследите AsyncStorage као `storage`. `restore` тада враћа обећање.

Складиште сачуваних копија чува само снимке са сервера: огледало транскрипта које API
уписује када се потез заврши. Транскрипт уживо се никада не уписује на уређај, тако да
заустављени потез не може да се врати као активан. Читање из извршног окружења које не врати поруке
задржава сачуване поруке на екрану: то значи да је рачунар изгубио стање, а не да је
разговор празан. Прво читање из извршног окружења са порукама усклађује их.

<a id="sign-out-and-account-switching"></a>

## Одјава и промена налога

SDK чува стање сесија по кориснику у меморији: транскрипте, питања на чекању
и дозволе, потврде потеза и изборе модела. Позовите `resetIdentityState()` при
свакој промени идентитета: одјави и пријави другог корисника. То обухвата
и пријаву у другој картици која мења корисника без учитавања странице.

Позовите овај обрађивач када ваш ток аутентификације пријави или одјави корисника.

```tsx
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()` такође уклања ставку `localStorage` из складишта модела. Не
брише сопствене кешеве апликације, као што је React Query, нити извор токена
прослеђен у `createBeeuni`. Ресетујте их у истом обрађивачу. Никада не баца изузетак.

Када користите складишта из одељка [Приказ из кеша при поновном учитавању](#render-from-cache-on-a-reload),
очистите их у истом обрађивачу: `await cache.clear()`, затим
`await savedCopies.clear()` и `setSavedCopyStore(null)`. Ниједно складиште никада
не враћа податке другог корисника, али уређај са ког је корисник одјављен не би требало да их чува.

<a id="next"></a>

## Даље

- [Сесије](/docs/sr/sdk/sessions) — објекат сесије који `useSession` обавија и врсте `BeeuniSendError`.
- [Референца](/docs/sr/sdk/reference) — комплетан REST интерфејс из ког ови хукови читају.

<a id="saved-history-during-startup"></a>

## Сачувана историја током покретања

`useSession(projectId, sessionId)` приказује сачувану историју разговора док се његов рачунар покреће.
SDK чита базу података независно од спремности извршног окружења. Поруке уживо се затим усклађују
према својим оригиналним ID-јевима. Апликацијама није потребан засебан захтев за историју нити складиште порука.

Сачувана историја се приказује исто као транскрипт уживо: текст, резоновање и сваки позив алата
са улазом, излазом и грешком. Пун приказ подагента чита његов сопствени сачувани
транскрипт: проследите опсег надређеног агента и `savedChild: true` у
`useSessionSync(subAgentSessionId, { beeuniSessionScope, savedChild: true })`. Почетни прозор садржи најновијих 40 порука, са
највише 1.000.000 знакова. Померање нагоре учитава старије прозоре из сачуване историје, чак и док
је рачунар искључен. Прилози којима је потребан рачунар постају доступни када
извршно окружење буде спремно.

`savedTranscript` говори апликацији шта да прикаже док се рачунар покреће. Вредност је `loading` док
сачувана копија још може да стигне, `shown` када су поруке у `messages` и `none` када ништа
не може да се прикаже пре него што извршно окружење одговори. Непознато стање је `loading`, никада `none`.

`conversationEmpty` је true када нема шта да се чека. Сачувана копија доказује да је
разговор празан: потпуно читање извршног окружења сесије није пронашло поруке. Од тада ниједан потез није
завршен и ништа није отворено нити у реду. Недостајући записи никада не дају вредност true, па се старија сесија
са историјом никада не приказује као празна. Сесија добија тај доказ при првом покретању са укљученом
сачуваном историјом.

```tsx
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 />;
}
```

Позиви алата сачувани пре 2026-09-28 изгубили су свој улаз и излаз. Сервер испоручује сваки од њих са
улазом који потврђују сачувани наслов или метаподаци: команда и излаз команде, путања позива за датотеку,
образац претраге, опис подагента, URL преузимања, листа задатака, назив вештине. При
следећем покретању сесије цела историја се поново чува, 1:1.
<a id="pending-prompts"></a>

### Упити на чекању

`useSessionPrompts(projectId, sessionId)` шаље у трајно пријемно сандуче сесије.
Сервер аутоматски извршава упите на чекању, сваки као засебан потез.

Проследите `placement: 'transcript'` да бисте приказали поруку на чекању у разговору.
Она се извршава пре сваке ставке `composer` и завршава активни одговор када се
тренутни позив алата заврши.
Проследите `placement: 'composer'` да бисте је задржали у листи изнад поља за унос, где може да се уређује.
Она чека да се активни одговор заврши. Сваки положај задржава редослед слања.
Проследите `delivery: 'steer' | 'queue' | 'interrupt'` да бисте одредили како упит стиже до
потеза који се извршава. `steer` га предаје потезу у следећем кораку; потез се не
зауставља. `queue` чека да се потез заврши. `interrupt` завршава потез после
алата који се извршава. Ако је наведен само `delivery`, SDK шаље положај који он подразумева:
`interrupt` је `transcript`, остали су `composer`. Ред за усмеравање који не може
да усмерава има вредност `delivery: 'queue'`, са `steer_fallback` постављеним на `unsupported`,
`not_prompter` или `turn_ended`. `interrupt(promptId)` („Stop and send“) претвара
ред на чекању у ставку транскрипта; ред који је већ послат одговара са `409`.

Веб уређивач порука шаље Enter као `steer` док се потез извршава, а као слање у транскрипт
док сесија мирује. Command+Enter (Control+Enter на Windows/Linux)
поставља поруку у уређивач. Shift+Enter убацује нови ред.

Редови листе укључују `full_text` за потпуне прегледе после поновног учитавања. `text` остаје
ограничени преглед за старије клијенте. Бајтови прилога су изостављени из одговора
са листом. Брисање враћа цео упит да би поништавање било без губитака.

<a id="pending-delivery-and-active-responses"></a>

### Испорука на чекању и активни одговори

`useSessionWorking()` задржава `state: 'working'` док упит чека у трајном
пријемном сандучету. Тако Stop остаје доступан. `pendingDelivery: true` означава упит који је још
на путу ка извршном окружењу; веб апликација приказује један индикатор рада кад год је `state`
`working`, без обзира на ту заставицу. Активни потези извршног окружења и његова свежа
активност га уклањају. Истек времена за отказивање не потврђује да је извршавање заустављено.

Преузимање од стране радника само проверава прихватање и оставља упит на чекању. Испорука почиње
када прихватање успе. Потврђен активни потез уклања приказ чекања
чак и ако претходни снимак пријемног сандучета још наводи тај упит. Активност извршног окружења
чува ID поруке активног потеза током ове примопредаје.

Веб уређивач порука назива Command/Ctrl+Enter **Queue List**, а „Stop and
send“ у реду **Quick Queue**. Ставке Quick Queue извршавају се прве. Ставка Quick Queue на чекању је обојени облачић после
испоручених потеза, без текста статуса. Ставке Queue List могу да се уређују док испорука не почне.
Сервер обнавља пропуштене догађаје завршетка на основу прецизних доказа извршног окружења, укључујући
и после поновног учитавања, без третирања недоступног извршног окружења као завршеног потеза.

Сваки засебан Enter одмах приказује свој упит на чекању, чак и док ранији
захтев за прихватање још није разрешен. Само неуспешна испорука додаје текст: узрок,
Retry и Remove. Пријемно сандуче сервера и даље управља редоследом извршавања.

Хук чита промене реда из контролног тока сесије
(`GET .../sessions/:id/events?channels=control`), уз једну везу по сесији,
без обзира на то колико компоненти га монтира. Сваки упис реда пријемног сандучета стиже као
оквир `beeuni.control.queue`. Периодична провера `GET .../prompts` ради само док тај
ток није повезан.

<a id="why-a-turn-ended"></a>

### Зашто се потез завршио

Транскрипт прекинутог потеза садржи само `MessageAbortedError`.
Контролни слој чува оно што зна о завршетку сваког потеза: узрок који је
изоловано окружење навело, на пример `SandboxMemoryGuard`, и који су потези прекинути без
наведеног узрока. Заустављање које је неко затражио — дугме Stop, `abort()`, упит
послат у заузету сесију — бележи се као захтев и никада се не пријављује као
неуспех.

`useSessionTurnOutcome(projectId, sessionId)` враћа `last_ended`,
`recent_failures` и време читања `atMs` из кеша који `useSessionWorking()`
одржава ажурним. Не шаље никакав захтев. Проследите резултат у `turnEndNotice`, уз оно
што транскрипт каже о потезу:

```tsx
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
```

Апликација пресликава сваки `kind` у сопствени текст и никада не парсира поруку
изолованог окружења. `null` значи „прикажи оно што транскрипт приказује“: стварна грешка остаје, а
прекид не приказује ништа. Необјашњен неуспех пријављује се тек када је време читања
неколико секунди после завршетка потеза, јер узрок често стиже један оквир
после прекида. `turnEndCause(outcome, messageId)` враћа сирови забележени
узрок или `null`.
