Errors & limits
CardSdkError
Every refusal — from the app, or from the SDK’s own checks before a request leaves your card — is a
CardSdkError:
import { CardSdkError, isCardSdkError } from '@neurosquad/card-sdk'
async function saveReport(text: string): Promise<void> {
try {
await card.fs.writeText('reports/latest.md', text, { createDirs: true })
} catch (error) {
if (isCardSdkError(error, 'PERMISSION_DENIED')) {
card.ui.toast(`This needs the ${error.permission} permission`)
} else if (isCardSdkError(error, 'RATE_LIMITED')) {
await new Promise((resolve) => setTimeout(resolve, error.retryAfterMs ?? 1000))
return saveReport(text)
} else if (error instanceof CardSdkError) {
card.log.error(error.code, error.method, error.hostMessage, error.data)
} else {
throw error
}
}
}| Property | What |
|---|---|
code | One of the codes below. |
message | method: host message [CODE] — hint, ready for the log. |
hostMessage | The app’s message alone (English, for developers — show your own text to users). |
method | The method that failed, when it came from a call. |
data | Details: schema issues, { permission }, { retryAfterMs }, { conflict }… |
hint | A one-line suggestion, for the codes that have one. |
permission | For PERMISSION_DENIED: the missing permission. |
retryAfterMs | For RATE_LIMITED: how long to wait. |
issues | For INVALID_PARAMS / BAD_REQUEST: { path, keyword, message }[] — where the params did not match. |
isCardSdkError(error, code?) is a type guard, optionally for one code.
Error codes
| Code | Means | Usually |
|---|---|---|
BAD_REQUEST | The message was malformed or its params are not plain JSON. | A Date, Map or class instance in params — send plain data. |
INVALID_PARAMS | Params do not match the method’s schema. | See error.issues for the exact path. Also a port value that fails a schema, or a {{secret:…}} that is not set. |
METHOD_NOT_FOUND | The app does not know this method. | An older app — check with card.host.supports(). |
PERMISSION_DENIED | The package lacks the permission. | Declare it in the manifest, or ask with card.permissions.request() if optional. |
NOT_CONNECTED | The target card or agent is not connected to yours by an arrow. | Ask the user to draw one. |
NOT_FOUND | No such card, agent, key, file or port. | |
QUOTA_EXCEEDED | A quota is used up: storage bytes or keys, spawned cards, file watchers, an agent’s full prompt queue. | Delete old data, stop old watchers, wait. |
RATE_LIMITED | Too many calls. data.retryAfterMs says how long to wait. | Batch, debounce, or back off. (Title, status, badge, overview and attention never reject for this — the SDK folds and retries them.) |
TOO_LARGE | A message, value or response over its limit. | See Limits. |
TIMEOUT | No answer in time (a port request, a command, a fetch). | |
BUDGET_PAUSED | The workspace is over its budget; programmatic prompts are refused. | The user raises the limit in the Budget card. |
BUSY | The agent is mid-turn and you asked whenBusy: 'fail'. | |
HOST_NOT_ALLOWED | net.fetch to a host outside the grant, or one that resolves to a blocked address. | Add the host to the network permission. |
NETWORK_ERROR | DNS, connection, TLS or stream failure. | |
FS_DENIED | A path outside the workspace folder, inside .git/, or through a link that leaves it. | Use a relative path. |
FS_ERROR | Any other file problem; data.conflict when ifMtimeMs did not match. | |
NOT_VISIBLE | Needs the card on screen: dialogs, links, focus, permission requests, prompts to an agent in dangerous mode. | Try again when card.visibility === 'visible'. |
USER_CANCELLED | The user said no (a confirm, a prompt to a dangerous-mode agent), or a call was cancelled. | |
UNAVAILABLE | The target is not running, the workspace is not open, the card is suspended, or the connection closed. | |
PROTOCOL_MISMATCH | The card was built for a newer protocol than the app speaks. | The user updates NeuroSquad. |
NOT_READY | A call was made before the handshake finished. | Wait for connect(). |
INTERNAL | A bug in the app. | Report it with the card log. |
Limits
All limits are in card.limits (the LIMITS constant of the SDK). The SDK checks the cheap ones
before sending; the app enforces all of them.
| Area | Limit |
|---|---|
| Messages | ≤ 1 MB each (fs.write and net.fetch requests, and fs.read, fs.list, net.fetch responses: ≤ 12 MB); ≤ 64 calls in flight (the SDK queues beyond that); 200 calls/s, bursts of 400; values ≤ 64 levels deep and ≤ 200 000 nodes |
| Package | archive ≤ 50 MB; unpacked ≤ 100 MB; ≤ 5 000 files; each ≤ 20 MB; paths ≤ 240 characters and 20 levels; manifest ≤ 256 KB; icon ≤ 128 KB |
| Manifest | ≤ 40 settings; ≤ 16 inputs and 16 outputs; ≤ 32 tools; ≤ 32 network hosts; schemas ≤ 500 nodes, 16 levels, enum ≤ 256 options and 16 KB, const ≤ 4 KB |
| Storage | key ≤ 256 characters; value ≤ 1 MB; ≤ 10 000 keys; 5 MB per card, 20 MB per package |
| Network | body ≤ 5 MB; response ≤ 10 MB; 6 at once; 120 a minute; ≤ 5 redirects; timeout 30 s (max 120 s), body included; ≤ 4 streams, each closed after 5 min of silence or 256 MB |
| Files | read and write ≤ 10 MB; list ≤ 5 000 entries; ≤ 20 watchers |
| Agents | prompt ≤ 20 000 characters, 6 a minute per card (method and port together), ≤ 10 queued per agent; command ≤ 20 000 characters; 30 commands a minute per card (run and the terminal port together, timeout ≤ 120 s), write 60 a minute; screen ≤ 500 lines; output delivered every 100 ms or 64 KB |
| Tools | result ≤ 1 MB; timeout 30 s (max 120 s); progress 4 a second |
| Ports | 20 messages/s per output; retained value ≤ 256 KB; request timeout 30 s (max 120 s) |
| Card UI | title ≤ 120 (30 a minute); status ≤ 80, status/badge/overview 120 a minute each; overview lines ≤ 160; toast ≤ 280 (one per 2 s); confirm text ≤ 1 000 (10 a minute); ≤ 12 menu items (30 changes a minute); settings.set 60 a minute; tools.setEnabled 30 a minute; usage.summary 6 a minute; attention every 10 s; focus every 5 s; resize every 500 ms; ≤ 4 spawned cards; clipboard once a second, ≤ 1 MB |
| Log | line ≤ 2 000 characters; 50 lines a second |
| Frames | ≤ 24 card pages running app-wide; ≤ 8 of them in the background; suspended after 60 s hidden, with 1 s to save; heartbeat every 5 s, “not responding” after 15 s; ready within 10 s of starting |