智能体与终端
列出智能体
需要 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')卡片所在工作区中的每个 AI 智能体和终端(其他卡片不是智能体):
| 字段 | 类型 | 含义 |
|---|---|---|
id | string | 这张卡片在画布上的 id。 |
name | string | 用户看到的名称。 |
harness | string | claude-code、codex-cli、opencode、qwen-code、shell-bash、shell-powershell、shell-cmd…… |
kind | 'ai' 或 'shell' | AI 智能体还是终端。 |
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,来自它自身的钩子(hook)(对于 OpenCode 和 Kilo Code,来自 NeuroSquad 插件;对于 Hermes Agent,来自它的生命周期 webhook),因此 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,以及一条连到 AI 智能体的箭头(终端接收的是命令)。
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 只把文本输入到智能体的输入框而不按回车——由用户检查后发送。结果为 inserted。 |
whenBusy | 'queue' | 智能体正在工作时:queue 放入智能体的提示词队列(queued),send 照样提交(sent),fail 以 BUSY 拒绝。 |
应用对每条提示词都会:
- 让它经过与提示词队列和预算相同的路径:工作区超出预算时,以
BUDGET_PAUSED失败(手动输入的人从不受限——受限的是你的卡片)。 - 在箭头及其箭头日志中显示,并带上你的卡片名称。
- 对于处于危险模式的智能体,应用会先在它自己覆盖整个窗口的对话框中显示这条提示词,并等待 Send prompt;用户拒绝则为
USER_CANCELLED; 你的卡片不在屏幕上则为NOT_VISIBLE。 - 每张卡片每分钟最多 6 条——与通过智能体
prompt端口发送的提示词合并计数——每条 ≤ 20 000 个字符。 - 来自卡片的排队提示词每个智能体最多 10 条,会显示来自哪张卡片;如果箭头、权限、卡片或它的卡片包已不存在,或者智能体被切换到了危险模式,它们会在发出之前被丢弃。
提示词是发给一个能编辑文件、能运行命令的东西的指令。永远不要在未先展示给用户的情况下,把来自网页、文件或其他卡片的文本转发成提示词——
提示词注入就是这样发生的。文本不是你自己写的时,优先使用 submit: false。
单个智能体的运行
自 NeuroSquad 0.1.264(SDK 1.1)起提供。需要 usage.read
权限,并有箭头连接到一个 AI 智能体——没有箭头的智能体返回 NOT_CONNECTED,shell 返回 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 | 应用无法读取其用量日志的工具(Amp、Cursor)为 false:所有词元字段为 null,用时照常统计。 |
firstPromptAt、lastFinishedAt | 毫秒时间戳或 null。 |
model、provider、status、timingComplete、scannedAt | 这些数字的上下文。应用的状态历史晚于 since 开始时(应用或智能体启动较晚),timingComplete 为 false;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 会在之后按回车 |
| 返回 | { exitCode, output, truncated, timedOut } | 无 |
| 限制 | 超时 1–120 秒(默认 120),输出 ≤ 64 KB;每张卡片每分钟 30 条命令,与终端的 command 端口共享 | 每分钟 60 次 |
在 cmd 中 exitCode 为 null,因为 cmd 不报告退出码。用户能在终端本身和箭头上看到每条命令。
命令不受智能体预算限制——终端不是 AI 智能体——但它们以用户的完整权限运行:参见安全检查清单。