Агенты и терминалы
Список агентов
Нужно разрешение agents.read.
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 различаются точно.
События
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 и стрелка между вашей
карточкой и агентом или терминалом.
// 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 и стрелка к ИИ-агенту
(терминалам отправляют команды).
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 ставит в очередь промптов агента (queued), send отправляет всё равно (sent), fail отказывает с BUSY. |
Что приложение делает с каждым промптом:
- Он идёт тем же путём, что и очередь промптов и бюджет: если воркспейс вышел за
бюджет, вызов падает с
BUDGET_PAUSED(человек, набирающий руками, не ограничен — ваша карточка ограничена). - Он виден на стрелке и в её журнале с именем вашей карточки.
- Агенту в опасном режиме приложение сначала показывает промпт в своём
диалоге на всё окно и ждёт Send prompt; если пользователь откажется —
USER_CANCELLED; если вашей карточки нет на экране —NOT_VISIBLE. - Не больше 6 промптов в минуту на карточку — вместе с промптами, отправленными через порт агента
prompt, — до 20 000 символов каждый. - Промптов от карточек в очереди — не больше 10 на агента; видно, от какой карточки каждый, и они выбрасываются до отправки, если пропала стрелка, разрешение, сама карточка или её пакет или агента перевели в опасный режим.
Промпт — это указание тому, кто может править файлы и запускать команды. Никогда не пересылайте в
промпт текст из интернета, файла или другой карточки, не показав его сначала пользователю, — именно
так и происходит внедрение промптов (prompt injection). Если текст не ваш, лучше submit: false.
Прогон одного агента
С NeuroSquad 0.1.264 (SDK 1.1). Нужно разрешение usage.read
и стрелка к ИИ-агенту — агент без стрелки даёт NOT_CONNECTED, оболочка — INVALID_PARAMS. Не
чаще 60 вызовов в минуту на карточку. Сначала проверьте card.host.supports('agents.usage'): старое приложение
ответит METHOD_NOT_FOUND.
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;
проверьте card.host.supports('agents.timeline').
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 и стрелка к карточке
терминала (bash, PowerShell или cmd).
// 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 его не сообщает. Пользователь видит каждую команду в самом
терминале и на стрелке. Бюджет агентов на команды не распространяется — терминал не ИИ-агент, — но
они выполняются со всеми правами пользователя: см. чек-лист безопасности.