# 智能体与终端

> 列出智能体及其实时状态、轮次事件、读取相连智能体的屏幕、发送提示词，以及在相连终端中运行命令。

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

## 列出智能体

需要 [`agents.read`](https://docs.neurosquad.ai/zh/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')
```

卡片所在工作区中的每个 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` 能被准确区分。

## 事件

```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/zh/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/zh/card-sdk/permissions#agents-prompt)，以及一条连到 **AI** 智能体的箭头（终端接收的是[命令](#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` 只把文本输入到智能体的输入框而不按回车——由用户检查后发送。结果为 `inserted`。 |
| `whenBusy` | `'queue'` | 智能体正在工作时：`queue` 放入智能体的[提示词队列](https://docs.neurosquad.ai/zh/agents/queue)（`queued`），`send` 照样提交（`sent`），`fail` 以 `BUSY` 拒绝。 |

应用对每条提示词都会：

- 让它经过与提示词队列和[预算](https://docs.neurosquad.ai/zh/cards/budget)相同的路径：工作区超出预算时，以 `BUDGET_PAUSED` 失败（手动输入的人从不受限——受限的是你的卡片）。
- 在箭头及其[箭头日志](https://docs.neurosquad.ai/zh/canvas/arrows#arrow-log)中显示，并带上你的卡片名称。
- 对于处于[危险模式](https://docs.neurosquad.ai/zh/agents/dangerous-mode)的智能体，应用会先在它自己覆盖整个窗口的对话框中显示这条提示词，并等待 **Send prompt**；用户拒绝则为 `USER_CANCELLED`；
你的卡片不在屏幕上则为 `NOT_VISIBLE`。
- 每张卡片每分钟最多 6 条——与通过智能体 `prompt` 端口发送的提示词合并计数——每条 ≤ 20 000 个字符。
- 来自卡片的排队提示词每个智能体最多 10 条，会显示来自哪张卡片；如果箭头、权限、卡片或它的卡片包已不存在，或者智能体被切换到了危险模式，它们会在发出之前被丢弃。

> 提示词是发给一个能编辑文件、能运行命令的东西的指令。永远不要在未先展示给用户的情况下，把来自网页、文件或其他卡片的文本转发成提示词——
> 提示词注入就是这样发生的。文本不是你自己写的时，优先使用 `submit: false`。

## 单个智能体的运行

自 NeuroSquad 0.1.264（SDK 1.1）起提供。需要 [`usage.read`](https://docs.neurosquad.ai/zh/card-sdk/permissions#usage-read)
权限，并有箭头连接到一个 AI 智能体——没有箭头的智能体返回 `NOT_CONNECTED`，shell 返回 `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` | 应用无法读取其用量日志的工具（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`](#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/zh/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` 会在之后按回车 |
| 返回 | `{ exitCode, output, truncated, timedOut }` | 无 |
| 限制 | 超时 1–120 秒（默认 120），输出 ≤ 64 KB；每张卡片每分钟 30 条命令，与终端的 `command` 端口共享 | 每分钟 60 次 |

在 cmd 中 `exitCode` 为 `null`，因为 cmd 不报告退出码。用户能在终端本身和箭头上看到每条命令。
命令不受智能体预算限制——终端不是 AI 智能体——但它们以用户的完整权限运行：参见[安全检查清单](https://docs.neurosquad.ai/zh/card-sdk/security)。
