connect() и объект карточки
Всё, что делает карточка, идёт через один объект — карточку, которую возвращает connect():
import { connect } from '@neurosquad/card-sdk'
const card = await connect()
card.setStatus(`Hello, ${card.workspace.name}`, { tone: 'success' })connect() ждёт, пока приложение передаст карточке её личный канал, представляется и возвращает
Card. Повторный вызов возвращает ту же карточку.
Импортируйте SDK из входного скрипта. Он начинает слушать рукопожатие приложения сразу после
загрузки. Карточка, которая грузит SDK поздно (динамическим import() после таймера), может его
пропустить и упасть с UNAVAILABLE.
connect(options?)
import { connect } from '@neurosquad/card-sdk'
const card = await connect({
theme: true, // apply the app theme as --ns-* CSS variables on <html> (default true)
syncLang: true, // keep <html lang> equal to the app language (default true)
forwardErrors: true, // send uncaught errors and rejections to the card log (default true)
timeoutMs: 10_000 // how long to wait for the app (default 10 000)
})| Параметр | По умолчанию | Значение |
|---|---|---|
theme | true | false — не трогать ваш CSS; элемент — положить переменные --ns-* на него, а не на <html>. См. Тема. |
syncLang | true | Держать <html lang> равным языку приложения. |
forwardErrors | true | Пересылать error и unhandledrejection в журнал карточки. |
timeoutMs | 10000 | Сколько ждать канал от приложения, а потом его ответ. |
port | — | Использовать этот канал вместо ожидания приложения — для мок-хоста. |
Отклоняется с CardSdkError:
UNAVAILABLE— страница открыта не внутри карточки NeuroSquad (сама по себе в браузере). Для превью используйте мок-хост, как это делают шаблоны.PROTOCOL_MISMATCH— приложение старше протокола карточек, на котором говорит ваш SDK. Карточка показывает «нужен более новый NeuroSquad».
Объект карточки
Контекст
card.context — это HostContext: то, что приложение отправило при подключении карточки,
обновляемое по каждому событию (видимость, размер, тема, язык, настройки, разрешения,
соседи). При каждом изменении объект заменяется, а не мутирует, — его безопасно использовать с
useSyncExternalStore из React.
import type { HostContext } from '@neurosquad/card-sdk'
function describe(context: HostContext): string {
const { instance, workspace, visibility, i18n, launch } = context
return `${instance.displayName} ${instance.version} in "${workspace.name}", ${visibility}, ${i18n.language}, started: ${launch}`
}
card.onContextChange((context) => render(describe(context)))| Поле | Тип | Значение |
|---|---|---|
instance | CardInstanceInfo | instanceId (идентификатор этой карточки на холсте), packageId, name, displayName, version, commit (null для подключённой папки), title, size, dev. |
workspace | WorkspaceInfo | id, name и path (абсолютный) — только с fs.read. |
visibility | 'visible', 'offscreen', 'overview' или 'hidden' | См. Жизненный цикл. |
expanded | boolean | Карточка развёрнута на весь холст. |
theme | ThemeSnapshot | Цвета, скругления и шрифты приложения. |
i18n | { language, locale } | en, ru или zh и локаль для Intl. |
settings | SettingsSnapshot | values и secrets (какие секретные ключи заданы). |
permissions | PermissionState[] | Каждое объявленное разрешение с granted, optional, hosts, reason. |
ports | { inputs, outputs } | Ваши собственные порты, подписи уже на нужном языке. |
peers | PeerInfo[] | Карточки, соединённые стрелками. |
launch | 'created', 'opened', 'resumed', 'reloaded' или 'updated' | Почему запустилась эта страница. |
spawnInit | JSON или нет | Данные от карточки, которая создала эту, при первом запуске. |
chrome | CardChromeState или нет | Что приложение показывает для этой карточки прямо сейчас — title, status, badge, overview, attention; сохраняется между перезагрузками. См. Шапка карточки. В старых версиях приложения отсутствует. |
appVersion | string | Версия NeuroSquad. |
limits | LIMITS | Все лимиты протокола, см. Лимиты. |
protocol | number | Протокол карточек, на котором говорит приложение. |
Сокращения на самой карточке: card.instanceId, card.instance, card.workspace,
card.visibility, card.expanded, card.size, card.theme, card.i18n, card.language,
card.launch, card.spawnInit, card.limits, card.appVersion.
launch говорит, что произошло: created (карточку только что добавили на холст), opened
(открыли воркспейс), resumed (вернулась после усыпления),
reloaded (пользователь нажал Reload, в режиме разработчика изменился файл или отозвали
разрешение), updated (установлена новая версия вашей карточки).
Любой метод, любое событие
Пространства имён — это удобная обёртка над двумя примитивами, оба полностью типизированы по протоколу:
// Any method: params and result are typed from the method name.
const { keys } = await card.call('storage.keys', { scope: 'instance', prefix: 'draft:' })
const info = await card.call('card.getInfo')
// Any event: the payload is typed from the event name. Returns a function that removes the listener.
const off = card.on('agents.turn', (turn) => {
if (turn.phase === 'end') render(`${turn.agentId} finished a turn`)
})
off()
// Once, or as a promise.
card.once('lifecycle.expanded', ({ expanded }) => render(expanded))
const next = await card.waitFor('agents.status', (e) => e.status === 'needs-input', { timeoutMs: 60_000 })
render(next.agentId)Темы с подпиской (agents.status, agents.turn, agents.changed, storage.changed) подписываются
в приложении автоматически, пока есть хотя бы один слушатель, и отписываются, когда уходит
последний. Для agents.output нужны идентификаторы агентов — используйте
card.agents.onOutput(). Если для темы нужно разрешение, которого у
вас нет, слушатель просто ничего не получает, а в журнал карточки уходит предупреждение.
События, которые могут прийти раньше, чем вы подпишетесь, — ports.message, ui.menu,
fs.changed, — сохраняются (до 100) и доставляются первому слушателю, так что между connect() и
вашим on() ничего не теряется.
Все события
| Событие | Данные | Когда |
|---|---|---|
lifecycle.visibility | { state } | Изменилась видимость. |
lifecycle.suspend | { graceMs } | Страницу вот-вот выгрузят. Используйте card.lifecycle.onSuspend. |
lifecycle.expanded | { expanded } | Карточку развернули или вернули. |
lifecycle.resized | { w, h } | Изменился размер карточки. |
settings.changed | SettingsSnapshot | Изменились настройки (форма или settings.set). |
theme.changed | ThemeSnapshot | Изменилась тема приложения. |
i18n.changed | { language, locale } | Изменился язык приложения. |
permissions.changed | PermissionState[] | Изменилась выдача разрешений. |
ui.menu | { id } | Выбран добавленный вами пункт меню. |
ports.message | см. Порты | На вход пришло значение. |
ports.request | см. Порты | Соседняя карточка обращается к входу-запросу. Используйте card.ports.onRequest. |
ports.peersChanged | PeerInfo[] | Изменились стрелки или соседи. |
tools.call, tools.cancel | см. Инструменты | Агент вызывает инструмент. Используйте card.tools.handle. |
net.chunk | см. Сеть | Кусок потокового ответа. Используйте response.chunks(). |
fs.changed | { watchId, path, type } | Изменился отслеживаемый файл. Используйте card.fs.watch. |
storage.changed | { scope, key, byInstance } | Другая копия карточки записала ключ пакета. Тема. |
agents.status | { agentId, status, at } | Изменился статус агента. Тема, agents.read. |
agents.turn | { agentId, phase, at } | Ход начался или закончился. Тема, agents.read. |
agents.changed | { agents } | Агентов добавили, удалили или переименовали. Тема, agents.read. |
agents.output | { agentId, text, at } | Вывод подключённого агента. agents.output. |
host.ping | { seq } | Пульс — SDK отвечает за вас. |
Определение возможностей
Новые методы и события появляются внутри одной версии протокола. Прежде чем пользоваться тем, чего в приложении пользователя может ещё не быть, проверьте:
if (await card.host.supports('usage.summary')) {
const usage = await card.usage('today')
render(usage.totalCostMicroUsd)
}
const { protocol, methods, events } = await card.host.capabilities()
render(protocol, methods.length, events.length)
// A method newer than your SDK's types:
const result = await card.callUnchecked('some.newMethod', { any: 'params' })
render(result)Соглашения
- Идентификаторы. Идентификаторы карточек и агентов — это идентификаторы на холсте, тот же
cardId, что вы получаете изcard.ports.peers,card.agents.list()илиcard.spawn(). - Подключена — значит, между двумя карточками есть стрелка, в любом направлении. Для портов направление ещё и важно (см. Порты).
- Каждый вызов проверяется дважды. SDK проверяет параметры по тем же схемам, что и приложение, и
сразу падает с
INVALID_PARAMSи точным путём; приложение проверяет снова — плюс разрешения, видимость, стрелки и ограничения частоты. - Передаётся только JSON. Никаких
BlobиArrayBuffer: помощники, которые принимают байты (fs.writeBytes, телаnet.fetch), сами кодируют их в base64. - Ответы могут прийти не по порядку; события приходят в том порядке, в каком их отправило приложение.
- Никогда не доверяйте событиям
messageуwindow. Любая другая карточка на холсте может сделатьpostMessageв ваш фрейм. Доверенный канал только один — тот, что SDK получает от приложения вconnect(); не добавляйте свои слушателиmessage, которые что-то делают с пришедшим.