# Агенты и терминалы

> Список агентов и их живой статус, события ходов, чтение экранов подключённых агентов, отправка промптов и запуск команд в подключённых терминалах.

Source: https://docs.neurosquad.ai/ru/card-sdk/api/agents

## Список агентов

Нужно разрешение [`agents.read`](https://docs.neurosquad.ai/ru/card-sdk/permissions#agents-read).

```ts
import type { AgentInfo } from '@neurosquad/card-sdk'

const agents: AgentInfo[] = await card.agents.list()
const waiting = agents.filter((a) => a.kind === 'ai' && a.status === 'needs-input')
const one = await card.agents.get(agents[0].id)
render(waiting.map((a) => a.name), one.model ?? 'default model')
```

Все ИИ-агенты и терминалы воркспейса карточки (другие карточки агентами не считаются):

| Поле | Тип | Значение |
| --- | --- | --- |
| `id` | `string` | Идентификатор карточки на холсте. |
| `name` | `string` | То, что видит пользователь. |
| `harness` | `string` | `claude-code`, `codex-cli`, `opencode`, `qwen-code`, `shell-bash`, `shell-powershell`, `shell-cmd`… |
| `kind` | `'ai'` или `'shell'` | ИИ-агент или терминал. |
| `status` | `AgentStatus` | `working`, `needs-input`, `finished`, `idle` или `exited`. |
| `connected` | `boolean` | С вашей карточкой его соединяет стрелка. |
| `sessionTitle` | `string?` | Заголовок, который агент дал своей сессии. |
| `turnStartedAt` | `number?` | Время начала текущего хода (мс с эпохи), пока статус `working`. |
| `lastTurn` | `{ startedAt, endedAt, endedAs }?` | Последний завершённый ход с момента запуска приложения: время в мс с эпохи (`startedAt` может быть `null`) и `endedAs` — `finished` или `needs-input`; чтобы догнать ходы, пропущенные, пока карточка не работала. |
| `model` | `string?` | Модель, на которой он работает, если известна. |

Статусы берутся из того же трекера, что и в остальном приложении, — для Claude Code из его
собственных хуков (для OpenCode и Kilo Code — из плагина NeuroSquad, для Hermes Agent — из его вебхуков жизненного цикла), поэтому `needs-input` («ждёт вашего разрешения») и `finished` различаются точно.

## События

```ts
card.agents.onStatus(({ agentId, status }) => render(agentId, status))
card.agents.onTurn(({ agentId, phase, at }) => render(agentId, phase, new Date(at)))
card.agents.onChanged((agents) => render(agents.length))   // added, removed or renamed

// Only one agent:
card.agents.onStatus((e) => render(e.status), agentId)
```

- `agents.status` — `{ agentId, status, at }` при каждой смене статуса.
- `agents.turn` — `{ agentId, phase: 'start' | 'end', at }`. Ход начинается, когда промпт отправлен,
и заканчивается, когда агент закончил или ждёт ввода.
- `agents.changed` — `{ agents }`, весь новый список.

SDK подписывается, пока у вас есть слушатели. Каждый метод возвращает функцию, которая убирает
слушателя.

## Чтение вывода

Нужно разрешение [`agents.output`](https://docs.neurosquad.ai/ru/card-sdk/permissions#agents-output) и **стрелка** между вашей
карточкой и агентом или терминалом.

```ts
// The visible screen, as text (up to 500 lines).
const screen = await card.agents.readScreen(agentId, 80)

// The last reply, from the agent's own session log (Claude Code, OpenCode, Kilo Code, Hermes Agent). null for others.
const { text, at } = await card.agents.lastReply(agentId)

// Live output: ANSI colours stripped, delivered in chunks about every 100 ms.
const stop = card.agents.onOutput([agentId, shellId], ({ agentId: from, text: chunk }) => {
  if (chunk.includes('FAIL')) render(`${from} printed a failure`)
})
stop() // when you no longer need it

render(screen, text, at)
```

Для агента без стрелки эти вызовы падают с `NOT_CONNECTED`. `readScreen` для незапущенного агента
падает с `UNAVAILABLE`.

## Промпты агентам

Нужно разрешение [`agents.prompt`](https://docs.neurosquad.ai/ru/card-sdk/permissions#agents-prompt) и стрелка к **ИИ**-агенту
(терминалам отправляют [команды](#terminals)).

```ts
import { isCardSdkError } from '@neurosquad/card-sdk'

try {
  const delivered = await card.agents.prompt(agentId, 'Run the checkout tests and fix what fails.')
  // 'sent'    — submitted now
  // 'queued'  — the agent is working; it goes out when the turn ends
  // 'inserted'— typed in only (submit: false); the user presses Enter
  card.ui.toast(delivered === 'queued' ? 'Queued after the current turn' : 'Sent')
} catch (error) {
  if (isCardSdkError(error, 'NOT_CONNECTED')) card.ui.toast('Draw an arrow from me to an agent')
  else if (isCardSdkError(error, 'BUDGET_PAUSED')) card.ui.toast('The workspace is over its budget')
  else if (isCardSdkError(error, 'USER_CANCELLED')) card.ui.toast('Not sent')
  else throw error
}
```

Параметры — `card.agents.prompt(agentId, text, { submit, whenBusy })`:

| Параметр | По умолчанию | Значение |
| --- | --- | --- |
| `submit` | `true` | `false` набирает текст в поле ввода агента, не нажимая Enter, — пользователь проверяет и отправляет сам. Результат `inserted`. |
| `whenBusy` | `'queue'` | Пока агент работает: `queue` ставит в [очередь промптов](https://docs.neurosquad.ai/ru/agents/queue) агента (`queued`), `send` отправляет всё равно (`sent`), `fail` отказывает с `BUSY`. |

Что приложение делает с каждым промптом:

- Он идёт тем же путём, что и очередь промптов и [бюджет](https://docs.neurosquad.ai/ru/cards/budget): если воркспейс вышел за
бюджет, вызов падает с `BUDGET_PAUSED` (человек, набирающий руками, не ограничен — ваша карточка
ограничена).
- Он виден на стрелке и в её [журнале](https://docs.neurosquad.ai/ru/canvas/arrows#arrow-log) с именем вашей карточки.
- Агенту в [опасном режиме](https://docs.neurosquad.ai/ru/agents/dangerous-mode) приложение сначала показывает промпт в своём
диалоге на всё окно и ждёт **Send prompt**; если пользователь откажется — `USER_CANCELLED`; если
вашей карточки нет на экране — `NOT_VISIBLE`.
- Не больше 6 промптов в минуту на карточку — вместе с промптами, отправленными через порт агента
`prompt`, — до 20 000 символов каждый.
- Промптов от карточек в очереди — не больше 10 на агента; видно, от какой карточки каждый, и они
выбрасываются до отправки, если пропала стрелка, разрешение, сама карточка или её пакет или агента
перевели в опасный режим.

> Промпт — это указание тому, кто может править файлы и запускать команды. Никогда не пересылайте в
> промпт текст из интернета, файла или другой карточки, не показав его сначала пользователю, — именно
> так и происходит внедрение промптов (prompt injection). Если текст не ваш, лучше `submit: false`.

## Прогон одного агента

С NeuroSquad 0.1.264 (SDK 1.1). Нужно разрешение [`usage.read`](https://docs.neurosquad.ai/ru/card-sdk/permissions#usage-read)
и стрелка к ИИ-агенту — агент без стрелки даёт `NOT_CONNECTED`, оболочка — `INVALID_PARAMS`. Не
чаще 60 вызовов в минуту на карточку. Сначала проверьте `card.host.supports('agents.usage')`: старое приложение
ответит `METHOD_NOT_FOUND`.

```ts
const run = await card.agents.usage(agentId, since /* мс эпохи */, until /* необязательно, по умолчанию сейчас */)
```

Возвращает цифры этого агента в окне — те же, из которых строится раздел «Расход» приложения. Ваш
фрейм может стоять на паузе или быть выгружен, пока агент работает: историю статусов ведёт само
приложение, поэтому время считается верно.

| Поле | Значение |
| --- | --- |
| `prompts` | Сколько ходов дали агенту. Ответ на запрос разрешения продолжает тот же ход; завершённый ход без единого запроса к модели (мигание статуса) не считается. |
| `requests` | Запросы к модели (обращения к API), записанные харнессом в окне. |
| `inputTokens` / `outputTokens` | Некешированный ввод; вывод вместе с рассуждениями (`reasoningTokens` — их часть). |
| `cacheReadTokens` / `cacheWriteTokens` | Работа с кешем — там, где харнесс и провайдер о ней сообщают. |
| `totalTokens` | Ввод + вывод + чтение кеша + запись в кеш. |
| `elapsedMs` | От первого промпта до последнего завершения (до текущего момента, пока идёт ход). |
| `workingMs` | Сколько агент работал; ожидание пользователя не входит. |
| `costMicroUsd` | Стоимость, записанная харнессом, иначе по прайсу, с округлением вверх; `null`, если цена неизвестна (в том числе у модели на собственном сервере пользователя). |
| `costPartial` | `true`, если у части запросов нет цены — стоимость покрывает только те, что с ценой. |
| `usageReadable` | `false` у харнесса, чей журнал расхода приложение не читает (Amp, Cursor): все поля токенов — `null`, время считается. |
| `firstPromptAt`, `lastFinishedAt` | Мс эпохи или `null`. |
| `model`, `provider`, `status`, `timingComplete`, `scannedAt` | Контекст для цифр. `timingComplete` — `false`, если история статусов приложения начинается позже `since` (приложение или агент запущены позже); `scannedAt` — когда журналы читались последний раз. |

> `null` значит **не сообщается**: в журнале харнесса нет такого числа (некоторые не пишут запись в
> кеш или рассуждения), локальный сервер не прислал счётчики кеша или приложение вообще не читает
> журнал расхода этого харнесса (Amp, Cursor). Показывайте «не сообщается», а не 0. Токены — точные
> целые числа.

## Запросы на шкале времени

С NeuroSquad 0.1.264 (SDK 1.2). То же разрешение и та же стрелка, что у [`agents.usage`](#usage);
проверьте `card.host.supports('agents.timeline')`.

```ts
const t = await card.agents.timeline(agentId, since, until /* необязательно */)
for (const r of t.requests) draw(r.startedAt ?? r.endedAt, r.endedAt, r.outputTokens, r.tools)
```

| Поле | Значение |
| --- | --- |
| `requests[]` | Каждый запрос к модели, от старых к новым: `endedAt` (всегда), `startedAt` (если журнал харнесса пишет, когда запрос отправлен, иначе `null`), токены (`null` — не сообщается), `tools` (вызовы инструментов, которые запросила модель). |
| `requestTimes` | `start-end` — у запросов есть время отправки (рисуйте полосы); `end` — только время завершения (рисуйте отметки); `none` — журнала нет. |
| `statuses[]` | Статус агента подряд идущими отрезками (`working`, `needs-input`, `finished`, `idle`, `exited`). |
| `turns[]` | Промпт → завершение; `counted: false` — ход без единого запроса (всплеск статуса). |
| `truncated` | В окне больше 2000 запросов: оставлены самые новые. |

## Терминалы

Нужно разрешение [`terminals.write`](https://docs.neurosquad.ai/ru/card-sdk/permissions#terminals-write) и стрелка к карточке
терминала (bash, PowerShell или cmd).

```ts
// Run a command and wait for it to finish.
const result = await card.terminals.run(shellId, 'npm test -- --reporter=dot', { timeoutMs: 120_000 })
if (result.timedOut) render('still running after 2 minutes')
else render(result.exitCode === 0 ? 'passed' : `failed with ${result.exitCode}`, result.output)

// Or just type into it (and press Enter).
await card.terminals.write(shellId, 'git status', { submit: true })
```

| | `run(agentId, command, { timeoutMs? })` | `write(agentId, text, { submit? })` |
| --- | --- | --- |
| Делает | Выполняет одну команду и ждёт, пока вернётся приглашение | Набирает текст; `submit: true` нажимает после него Enter |
| Возвращает | `{ exitCode, output, truncated, timedOut }` | ничего |
| Лимиты | тайм-аут 1–120 с (по умолчанию 120), вывод до 64 КБ; 30 команд в минуту на карточку, вместе с портом терминала `command` | 60 в минуту |

В cmd `exitCode` равен `null` — cmd его не сообщает. Пользователь видит каждую команду в самом
терминале и на стрелке. Бюджет агентов на команды не распространяется — терминал не ИИ-агент, — но
они выполняются со всеми правами пользователя: см. [чек-лист безопасности](https://docs.neurosquad.ai/ru/card-sdk/security).
