@blade-hq/agent-react

中文文档
2610.0.6发布于 2026/11/11TypeScriptES Module近 30 天下载 9759

Blade Agent React 绑定:BladeProvider、useAgentSession、开箱即用的 ChatView 聊天界面。

原始使用文档
文档目录33 节
  1. 快速开始
  2. BladeProvider
  3. useBladeClient
  4. useAgentSession
  5. SessionMemoryToggle
  6. useReplay
  7. ChatView
  8. 纯 LLM 模式(不建会话)
  9. 让用户自己改模型服务地址
  10. MarkdownContent
  11. Canonical 引用消息
  12. isAgentComputerCommand / isAgentComputerToolCall / classify…
  13. 选择模型
  14. 样式定制
  15. classNames 覆盖哪些区块
  16. 锚点清单
  17. part 清单( <blade-chat> 的 Shadow DOM 专用)
  18. CSS 变量清单
  19. 区块级渲染器(renderers)
  20. 自定义消息列表滚动
  21. 打字机式显示节流
  22. 长会话历史导航(useTurnNavigation / TurnNavRail)
  23. 纯静态 HTML / Vue?
  24. 附录:连接器与插件选择
  25. 附录:re-export 自 @blade-hq/agent-client
  26. 会话消息队列
  27. 会话插件
  28. 连接器面板
  29. MCP Apps
  30. 子智能体卡片
  31. 对话分支选择器
  32. 会话图片
  33. 对话执行耗时

@blade-hq/agent-react

Blade Agent 的 React 绑定:BladeProvider + useAgentSession + 开箱即用的 ChatView 聊天界面。支持 React 18 与 19(CI 两个版本 matrix 验证)。

pnpm add @blade-hq/agent-client @blade-hq/agent-react

默认装到的是当前长期支持版(LTS),厂内离线交付按它开发。要跟两周一发的公网版本,改用 @next。

最稳妥的做法是先读目标 Server 的 GET /api/version,按返回的版本号在 package.json 里钉死——NPM 的 latest 不一定和目标环境跑的版本一致。

注意两个包都要装:agent-client 是运行时依赖,只装 agent-react 在 pnpm 下会直接报错。

快速开始

import { BladeClient } from "@blade-hq/agent-client"
import { BladeProvider, ChatView } from "@blade-hq/agent-react"
import "@blade-hq/agent-react/style.full.css"

const client = new BladeClient({ baseUrl: "https://blade.example.com" })

export function App() {
  return (
    <BladeProvider client={client}>
      <ChatView />
    </BladeProvider>
  )
}
  • 不传 sessionId 自动创建新会话;未登录时 ChatView 自己渲染登录按钮(弹窗授权,见 agent-client 的 client.auth.login())。
  • 同一 BladeProvider 下可以放多个 ChatView,各自独立会话、互不干扰。

BladeProvider

只负责 client 级共享(一条 Socket 连接、一份鉴权),不含任何全局状态。

<BladeProvider client={client}>{children}</BladeProvider>

Props 见 BladeProviderProps;在 Provider 外调用本包任何 hook 都会抛错。

useBladeClient

const client = useBladeClient() // 当前 Provider 的 BladeClient

useAgentSession

自建 UI 的入口:连接(或创建)会话并订阅其状态,内部使用 useSyncExternalStore。

const { session, state, error } = useAgentSession(sessionId, {
  createOptions,        // 不传 sessionId 时的建会话配置(UseAgentSessionOptions)
  onSessionCreated: (id) => saveSomewhere(id),
  onSessionConnected: (session) => session.on("toolResult", handleResult),
})

state?.messages     // ChatMessage[],直接渲染
state?.isStreaming  // 是否正在流式回复
await session?.send("你好")

send() 在服务端接受消息后返回 true;整理中或其他冲突被拒绝时返回 false,内置 ChatView 会保留输入内容。

返回值见 UseAgentSessionResult。语义要点:

  • 优先级:sessionId(连接既有会话)> createOptions(按配置新建)> 默认新建。
  • 自动创建只发生一次(含 React StrictMode 双跑);创建出的 id 通过 onSessionCreated 交还,重挂载时把它作为 sessionId 传回即可复用会话。
  • 必须覆盖连接窗口内事件时,用 onSessionConnected 在历史加载和房间订阅开始前完成订阅,并返回取消订阅函数。
  • 同一 sessionId 的多次调用复用同一 AgentSession 实例(引用计数);卸载后延迟释放,路由抖动不会断连。

SessionMemoryToggle

把当前会话是否使用记忆放进宿主自己的会话设置面板:

const [memoryEnabled, setMemoryEnabled] = useState(session.memory_enabled !== false)

<SessionMemoryToggle
  sessionId={session.id}
  enabled={memoryEnabled}
  onSaved={(_sessionId, enabled) => setMemoryEnabled(enabled)}
  onError={(error) => toast.error(String(error))}
/>

组件默认使用 BladeProvider 的 client;已有独立 client 装配层的宿主也可以显式传 client={client}。同一 client、同一会话的多个开关共享保存锁,即使切换会话或同时存在多个 设置入口,也不会并发提交相互覆盖。服务端确认后的权威值通过 onSaved 返回。 全部属性见 SessionMemoryToggleProps。

useReplay

会话回放(演示 / 彩排):拿一个已经聊完的会话当素材,重现当时的回复和工具调用, 完全不调用模型。

进入回放是"派生一个新会话",不是把当前会话切模式:startReplay() 返回的是新会话 的 id,拿它去渲染(<ChatView sessionId={newId} /> 或自己 connect)才进入回放。源会话 原样不动。连上之后也不会自动播——用户照着源会话的问题问,才逐句重现。

同一个 hook 覆盖两种身份——传进去的会话既可能是准备被回放的源会话,也可能是回放中的会话:

// —— 源会话页面:提供"开始回放"入口 ——
const { session: sourceSession } = useAgentSession(sourceId)
const source = useReplay(sourceSession)

source.canReplay        // false 时 source.unsupportedReason 可直接展示
const replayId = await source.startReplay(5)   // 返回**新建的**回放会话 id,路由过去

// —— 回放会话页面:状态与控制 ——
const { session: replaySession } = useAgentSession(replayId)
const replay = useReplay(replaySession)

replay.isReplay         // 是不是在回放
replay.speed            // 当前倍速
replay.canControl       // 改回放是 owner 专属;分享出去的只读会话为 false
replay.mismatch         // 输入和录制对不上时的待决策项
await replay.setSpeed(2)
await replay.exitToAutonomous()                // 退出回放,之后真的运行

配套两个组件,直接接上 useReplay() 的返回值即可:

<ReplayBar
  isReplay={replay.isReplay}
  speed={replay.speed}
  canControl={replay.canControl}
  onSpeedChange={(s) => void replay.setSpeed(s)}
  onExit={() => void replay.exitToAutonomous()}
/>
<ReplayMismatchPrompt mismatch={replay.mismatch} />

返回值见 UseReplayResult,冲突项见 ReplayMismatch,组件属性见 ReplayBarProps / ReplayMismatchPromptProps。

只读身份(分享链接打开的会话)仍然会显示「回放模式」——这是会话事实,藏起来会让分享出去的 回放看着像真实运行;但倍速和退出会被禁用,因为服务端的 PATCH 是 owner 专属。

ChatView 已经自带这一整套——用它打开回放会话,状态条、倍速、退出、冲突卡片都在, 不用自己接。下面这些 API 是给自建 UI 用的。

注册即接管:底层的 replayMismatch 只交给最先注册的处理器。调用 useReplay()(或用 ChatView)就意味着由它接管冲突;两者都不用则维持 SDK 默认行为——静默转为真实运行。

回放状态本身住在 SessionState.replay 里,本 hook 只是它的 React 绑定。非 React 宿主 (Vue、Svelte、自建 UI)直接订阅快照即可,不需要这个包,用法见 agent-client 的会话回放章节。

ChatView

完整聊天界面:消息列表(Markdown、代码高亮、工具调用、提问卡片)+ 输入框 + 连接状态条 + 登录引导。

ChatView 固定使用普通展示模式。产品内置 Web 的精简/开发者模式不属于 SDK 公共能力, 因此没有 renderMode 等切换属性。

<ChatView
  sessionId={id}                       // 可选;不传自动建会话
  createOptions={{ intent: "..." }}    // 自动建会话时的配置
  onSessionCreated={(id) => ...}       // 自动创建的会话 id 交还调用方
  onSessionReady={(session) => ...}    // 会话实例就绪(页面协作高级用法)
  commands={{ "map.highlight": (data) => map.highlight(data) }}  // 智能体 → 页面指令
  onFollowupInteraction={(event) => analytics.capture(event.type, event)} // 厂商无关交互回调
  placeholder="输入消息..."
  classNames={{ root: "...", banner: "...", messages: "...", input: "..." }}  // ChatViewClassNames
  renderers={{ toolCall: (info) => <MyToolCard info={info} /> }}              // ChatViewRenderers
  slots={{ header: <MyHeader />, emptyState: <MyEmpty />, footer: <MyTip /> }} // ChatViewSlots
/>

全部 props 见 ChatViewProps。样式说明:

智能体调用 update_plan 新增或更新任务时,ChatView 会在输入框上方自动展开最新任务进度, 5 秒后平滑收起;连续更新会重新计时,用户手动展开或收起后以用户选择为准。自建聊天布局可直接 使用 CurrentPlanPanel / PlanUpdateBlock,或用 getPlanUpdateDisplayState、 parsePlanUpdate 和 pickCurrentPlanStep 复用同一套判定。

后台任务自动唤醒会话时,ChatView 显示简短的 BackgroundWakeNotice,说明助手为何继续处理。卡片由明确的后台来源标记生成,流式和历史加载一致;自定义消息列表可以复用该组件及 agent-client 的 isBackgroundWakeMessage,无需解析内部提示文本。

ChatView 的普通展示模式不显示只供诊断的项目说明、可用能力和当前工作环境。需要自建诊断界面时,可直接使用独立的 ContextCard,属性见 ContextCardProps;状态到文案的映射来自 agent-client 的 getContextDisplayState,不需要接入方重复判断。一次输入通常会同时注入十几项上下文,把渲染序列里连续的几项用 ContextGroupCard(属性见 ContextGroupCardProps)折成一行更合适;哪几项算一组由 agent-client 的 groupAdjacentContextRuns 判定,折叠行文案来自 getContextGroupDisplayState / ContextGroupDisplayState。底层数据类型为 ContextProjectionData / ContextProjectionFields,展示结果为 ContextDisplayState,并保留 ContextAction、ContextSourceInfo 和 contextProjectionData 给 headless 消费方。

记忆引用提示可通过 MemoryRefsHint 展示,collectMemoryRefs 用于从一轮消息中聚合引用,保证宿主自定义消息布局与 SDK 默认界面保持一致。

MCP App 卡片(tool_ui block)在默认消息流里自动渲染,无需配置;自建消息布局时可用 ToolUiCardView 渲染单个卡片,并传入 sessionId 启用共享 AppBridge。卡片的实例、内容解析与终态(archived 留档不重放)判定来自 agent-client 的共享层(collectInlineToolUiCards / resolveToolUiCardContent 等)。preview 卡片不进消息流,由 toolPreview 事件推给宿主自建面板。 文件引用卡片(resource-file)只带 sourcePath,ToolUiCardView 在 BladeProvider 下由父页面带鉴权读取当前文件,再放入无令牌、无同源权限的 iframe 渲染;同一路径的新卡片不会自动重载当前页面,用户点击卡片上方的“刷新”按钮才重新读取。自建面板也应按路径去重、首次打开时读取,并保留用户操作中的页面直到手动刷新。

onFollowupInteraction 使用 FollowupInteractionEvent,覆盖下一步建议展示/采纳、成果展示/打开/下载以及结果评分。SDK 不内置分析服务;宿主回调抛错也不会中断用户点击或下载。 文件成果以文件名为文本的标准下载链接展示;Vue / 纯 HTML 使用的 <blade-chat> 与 ChatView 行为一致。

  • 必须引入一份样式,按宿主有没有 Tailwind 二选一:
    • style.full.css(默认选它):CSS 变量、容器查询、Markdown 排版等兜底 + 编译好的 Tailwind 产物,自包含,宿主没装 Tailwind 也是完整视觉。
    • style.css:只有兜底那部分(Tailwind 类表达不了的规则)。宿主自己装了 Tailwind、且 content 配了扫描 node_modules/@blade-hq/agent-react 时用它,可以少一份重复 CSS。选错不会报错,只是布局、配色、工具块和思考块会退化成裸 HTML——组件的视觉本来就只定义一次,都在它的 Tailwind 类上。
    • 两份都用同一套层序 @layer properties, theme, base, components, utilities, blade-chat-overrides;(在 src/scoped-preflight.css 里声明一次;properties 是 Tailwind 的 --tw-* 初始值层,必须排在最前)。兜底规则在 components 层;Tailwind 工具类在 utilities 层;SDK 里那几条必须压过自己工具类的规则(窄容器里放宽用户消息、移动端聚焦不缩放、Markdown 与代码块排版)在最后一层 blade-chat-overrides(名字带 SDK 前缀,避免宿主已有的同名层把全局层序定到别处)——放进 components 会被同元素的工具类盖掉,等于没写。宿主写的普通 CSS 不分层,按层序赢过这里所有层,与加载顺序无关,也不需要 !important:这是「接入方覆盖必胜」的实现方式,改 src/style.css 时不要把规则挪出这两层。
    • 两份都不含 Tailwind preflight 这类全局 reset,引入后不会影响宿主页面自己的排版;组件需要的那点重置限定在 .blade-chat 子树内(见 src/scoped-preflight.css)。Shadow DOM 版(<blade-chat>)不受此限,它注入的是带 preflight 的完整产物。
  • 颜色走 CSS 变量,可整体换肤;深色页面要在根元素上写 data-theme="dark",否则拿到的是默认的浅色变量。
  • renderers.toolCall 返回 null 时回落到默认工具卡片。
  • 内边距按容器宽度伸缩,嵌进窄侧栏(300–400px)时会自动收窄,不看浏览器视口。代价是聊天容器需要有确定宽度——放在 flex/grid 里被拉伸,或显式给宽度都可以;放进 width: fit-content、inline-flex 这类由内容决定宽度的父容器会塌缩。

纯 LLM 模式(不建会话)

有些应用只要「一问一答」——分类、改写、总结,不需要智能体、不需要会话、也不需要用户登录 Blade。这时把 ChatView 切到 mode="llm",或者直接用下层的 LlmChat,界面完全一样:

<ChatView />                                                  // 默认:智能体模式,行为不变
<ChatView mode="llm" llm={{ baseURL: "/api/llm", model }} />   // 切成纯 LLM
<LlmChat baseURL="/api/llm" model={model} />                   // 或者直接用(LlmChatProps)
<AgentChat sessionId={id} />                                   // 智能体那侧同样可以直接用(AgentChatProps)

`AgentChat` 还支持 `sessionPluginControls(sessionId)` 插槽,宿主可复用
`@blade-hq/agent-client` 的会话插件状态与激活控制。

协议就是 OpenAI 的:POST {baseURL}/chat/completions,stream: true,读 choices[0].delta。任何 OpenAI 兼容实现(Blade 网关、vLLM、Ollama、OpenAI 本身、你自己的 透传代理)都能直接接,不用为这个组件专门写后端。

密钥默认不进浏览器:把 baseURL 指向自己后端的一个路径,后端补上 Authorization 再原样 转发即可(协议是标准的,这个代理就是纯转发)。确实要前端直连时才传 apiKey——只适合受控内网、 演示,或密钥本身就属于当前用户的场景。

模型服务地址和模型名别写死,用 client.models.list() 拿,各部署端口不一样:

const { baseUrl, defaultServiceModel, models } = await client.models.list()

// 后端透传(推荐):baseURL 指向自己的路径,模型名仍然要用 defaultServiceModel
<ChatView mode="llm" llm={{ baseURL: "/api/llm", model: defaultServiceModel }} />

// 浏览器直连:仅当 baseUrl 对浏览器也可达时
<ChatView mode="llm" llm={{ baseURL: baseUrl, model: defaultServiceModel, apiKey }} />

// 让用户选模型时,过滤掉不在这个服务上的
models.filter((m) => m.serviceModelId)

模型名要用 defaultServiceModel / serviceModelId,不是 default / id。 后者是平台内部 标识,平台接了多个模型服务时形如 provider-xxx::deepseek-v4-flash,发给模型服务只会得到 「模型不存在」。baseUrl 同理是服务端视角的地址,平台常配成 http://127.0.0.1:30000/v1, 那是给自己后端转发用的,不能直接丢给浏览器。

工具调用走 OpenAI 标准的多轮流程,不需要任何私有扩展:声明 tools,在 onToolCall (收到 LlmToolCall)里执行——通常就是调你自己应用的同源接口——返回值会作为 role: "tool" 消息回喂给模型,直到它给出最终回答或达到 maxToolRounds(默认 4)。 每一轮工具调用都会渲染成和智能体模式一样的工具卡片。

<LlmChat
  baseURL="/api/llm"
  model={model}
  system="你是订单助手"
  tools={[{ type: "function", function: { name: "query_orders", parameters: schema } }]}  // ChatCompletionTool
  onToolCall={async ({ name, arguments: args }) => {
    if (name !== "query_orders") throw new Error(`没有这个函数:${name}`)
    // 参数是模型生成的不可信输入:先校验类型,再用 URLSearchParams 拼,
    // 直接塞进查询串的话,一个 `李航&limit=all` 就能多带出参数
    const { owner } = JSON.parse(args || "{}")
    if (typeof owner !== "string") throw new Error("owner 必须是字符串")
    const query = new URLSearchParams({ owner })
    const resp = await fetch(`/api/orders?${query}`)
    if (!resp.ok) throw new Error(`订单接口返回 ${resp.status}`)
    return resp.json()
  }}
  onReady={(handle) => setChat(handle)}   // LlmChatHandle:insertText / send / reset
/>

让用户自己改模型服务地址

传 advanced 就在输入框上方多一行「高级设置」(默认收起),用户可以改模型服务地址和模型名; 改过之后会显示「已自定义」,也能一键恢复默认。不传就一个节点都不渲染。

<LlmChat baseURL="/api/llm" model={model} advanced />
<LlmChat baseURL="/api/llm" model={model} advanced={{ apiKey: true, storage: "memory" }} />
<ChatView mode="llm" llm={{ baseURL: "/api/llm", model, advanced: true }} />

LlmAdvancedSettings 可细调:baseURL / model 各自能不能改(默认都能)、apiKey 要不要 出现(默认不出现,打开意味着密钥存在浏览器里,只适合受控场景)、storage 是记住选择 (local,默认)还是只在本次有效(memory)、storageKey 自定义存储键。用户改了会回调 onOverrideChange(LlmOverride),宿主想自己也记一份时用。

其余可选项见 LlmChatOptions:headers、historyTurns(默认 12 轮)、temperature、 extraBody、fetchImpl。只要状态机、界面自己写的,用 useLlmChat(返回 UseLlmChatResult:messages / isStreaming / error / send / stop / reset)。

纯 LLM 模式不做会话持久化、文件上传、提问卡片、记忆和检查点——那些是智能体协议的能力, 需要就用默认模式。

MarkdownContent

单独渲染一段智能体消息 Markdown(含代码复制按钮、XSS 消毒;HTML 经 sanitize 白名单处理,不接受任意 raw HTML):

<MarkdownContent content={markdownText} />
<MarkdownContent sessionId={sessionId}>{markdownText}</MarkdownContent>

normalizeAdjacentUrlFormatting 可在复用其他 Markdown 渲染器前,保护紧贴行内格式标记的 URL。

Canonical 引用消息

ChatView 会自动识别 ship-attack 会话中的 [引用] / [用户输入] canonical 消息,并按原顺序展示引用来源、快照和用户输入。自建消息列表时可用 parseWhatIfPrompt 解析同一格式,再交给 WhatIfUserBubble 渲染; onQuoteClick 可接入宿主的步骤跳转。

const parsed = parseWhatIfPrompt(messageText)
return parsed ? (
  <WhatIfUserBubble parsed={parsed} onQuoteClick={jumpToStep} />
) : null

isAgentComputerCommand / isAgentComputerToolCall / classifyAgentComputerLaunchOutcome

判断智能体是否在沙盒里启动了通用 GUI 应用镜像(computer launch)——后端 blade-agent 通过 computer CLI 驱动一块虚拟屏幕运行任意 GUI 软件,这些函数用于自行接入方判断要不要展示对应的实时画面入口(比如自建一个类似「电脑镜像」的标签页)。当前 SDK 侧还没有内置这块 UI,只导出判定规则,避免各接入方重新猜一遍匹配逻辑。

isAgentComputerCommand/isAgentComputerToolCall 只回答"这条命令是不是一次 launch 尝试",不回答"这次 launch 最终成不成功"——待处理、已失败的 launch 也会命中。要判断真实结果(比如决定要不要展示画面入口),用 classifyAgentComputerLaunchOutcome:它会解析工具调用的 status/result,返回 "pending" | "succeeded" | "failed" | "unknown"。"unknown" 表示 status 已经是终态但 result 字段本身缺失(常见于会话整理/compaction 之后的历史投影,归档时不会重新序列化完整 result),跟"result 回来了、里面确实没有成功标记"的 "failed" 是两种不同性质的证据——接入方要按自己的场景决定怎么处理(展示类场景可以偏宽松,当作足够展示;自动跳转/替用户做决定的场景应该偏保守,当作跟 "failed" 一样处理)。

import {
  isAgentComputerCommand,
  isAgentComputerToolCall,
  classifyAgentComputerLaunchOutcome,
} from "@blade-hq/agent-react"

isAgentComputerCommand("computer launch --exec /opt/apps/foo/bar") // true
isAgentComputerCommand("computer status") // false(只认 launch,不认查询类子命令)

isAgentComputerToolCall(toolCall.arguments) // 工具调用的 JSON 参数版本

classifyAgentComputerLaunchOutcome(toolCall) // "pending" | "succeeded" | "failed" | "unknown"

选择模型

SDK 的 ChatView / <blade-chat> 不内置模型选择器(默认用后端配置的模型)。指定模型有两种方式:

  • 建会话时指定(React):<ChatView createOptions={{ model: "..." }} />
  • <blade-chat> 属性(纯 HTML / Vue):<blade-chat base-url="..." model="..."></blade-chat>
  • 自建 UI 时逐条覆盖:chat.send(text, { model: "..." })

可用模型清单由后端提供,接入方对照后端 Swagger(<baseUrl>/docs)自行获取。

样式定制

默认是浅色主题。四个层次,按需选用:

  1. 深浅主题:<ChatView theme="dark" /> / <blade-chat theme="dark"> 切内置深色,不传或 "light" 为浅色。宿主已经在祖先元素上写了 data-theme="dark" 时不用传,ChatView 会跟着变。<blade-chat> 渲染在 Shadow DOM 内,页面上的 data-theme 对它无效,只认 theme 属性。

  2. CSS 变量换肤(React 与 <blade-chat> 通用):所有颜色走 --primary、--background、--muted 等变量(完整清单见下面的「CSS 变量清单」,默认值在 style.css 开头)。页面直接覆盖即可穿透 Shadow DOM:

blade-chat { --primary: 262 83% 58%; height: 640px; }
  1. classNames props(React):ChatView 的 classNames 把自定义 class(含你自己构建的 Tailwind 工具类)挂到各个区块上,见 ChatViewClassNames。SDK 内部用 cn(clsx + tailwind-merge)拼接,你传的类在最后一个参数,所以 classNames={{ chatInput: "p-0" }} 会直接盖掉组件内部的 py-3,与 CSS 加载顺序无关:
<ChatView
  sessionId={id}
  classNames={{
    root: "h-[640px] rounded-2xl border",
    userBubble: "bg-sky-100 text-sky-900",
    assistantText: "text-[15px] leading-7",
    chatInput: "px-2",
    inputInner: "bg-white shadow-sm",
  }}
/>
  1. 内嵌 <style> 与 ::part(纯 HTML / Vue):<blade-chat> 的直接 <style> 子元素会被注入 Shadow DOM、排在内置样式之后,可用 .blade-chat-* 类名做任意深度定制;每个主要区块同时带一个与锚点同名的 part(blade-chat-user-bubble → part="user-bubble"),页面外层用 ::part() 也能选到它:
<blade-chat base-url="...">
  <style>
    .blade-chat-user-bubble { background: #eef2ff; }
    .blade-chat-thinking { font-size: 15px; }
  </style>
</blade-chat>
blade-chat::part(user-bubble) { background: #eef2ff; }
blade-chat::part(input-toolbar) { gap: 12px; }

classNames 覆盖哪些区块

区块与锚点一一对应(键名 = 锚点名去掉 blade-chat- 前缀):

classNames 键锚点落点
root.blade-chat聊天根容器
banner.blade-chat-banner连接状态条
errorBar.blade-chat-error-bar顶部出错条
messageList.blade-chat-messages消息区
messagesScroll.blade-chat-messages-scroll消息区滚动内容(内边距)
messagesContent.blade-chat-messages-content消息内容列(居中、最大宽度)
empty.blade-chat-empty空状态
scrollToBottom.blade-chat-scroll-bottom「滚动到底部」按钮
userMessage.blade-chat-user-row用户消息行
userBubble.blade-chat-user-bubble用户气泡
fileCard.blade-chat-file-card消息里的文件附件
errorMessage.blade-chat-error-row消息流里的出错块
assistantTurn.blade-chat-assistant-turn一轮助手回复
thinking.blade-chat-thinking思考过程
assistantText.blade-chat-assistant-text助手正文
memoryRefs.blade-chat-memory-refs记忆引用
toolRow.blade-chat-tool-row执行过程里的工具行(智能体模式下工具调用都走这条路径)
agentLoop.blade-chat-agent-loop子智能体行
askCard.blade-chat-ask-card提问卡
askOption.blade-chat-ask-option提问卡的每个选项
planPanel.blade-chat-plan输入框上方的计划面板
queuePanel.blade-chat-queue会话队列面板
followup.blade-chat-followup本轮小结与跟进区
followupAction.blade-chat-followup-action跟进建议按钮
chatInput.blade-chat-input输入区外层
inputInner.blade-chat-input-inner输入框卡片(边框、圆角、底色)
textarea.blade-chat-textarea输入框
inputToolbar.blade-chat-input-toolbar输入框右侧按钮组

同一批锚点也是普通 CSS 的抓手:锚点只负责「可被选中」,视觉规则不在它身上,所以对你的锚点写 .blade-chat-user-bubble { ... } 不会被 SDK 的样式盖掉(SDK 的规则都在 @layer components / @layer blade-chat-overrides,Tailwind 工具类在 @layer utilities,你的样式不分层,按 CSS 层序本来就赢)。

锚点清单

每个可见区块的根元素都固定带一个 blade-chat-* 类,构成公开契约(src/style.css 顶部的登记块是唯一来源,构建会校验它和组件源码、三份 CSS 产物一致):

区块锚点
聊天根容器.blade-chat
登录引导 / 连接状态条 / 出错条 / 回放条.blade-chat-login、.blade-chat-banner、.blade-chat-error-bar、.blade-chat-replay-bar
消息区.blade-chat-messages、.blade-chat-messages-scroll、.blade-chat-messages-content、.blade-chat-empty、.blade-chat-scroll-bottom、.blade-chat-scroll-bottom-label
用户消息.blade-chat-user-row、.blade-chat-user-col、.blade-chat-user-bubble、.blade-chat-file-card(+ -icon / -name)
助手回复.blade-chat-assistant-turn、.blade-chat-assistant-text、.blade-chat-thinking、.blade-chat-memory-refs、.blade-chat-render-error
工具调用.blade-chat-tool-row(执行过程里的工具行)、.blade-chat-agent-loop(子智能体行)
提问卡.blade-chat-ask-card、.blade-chat-ask-option
计划与队列.blade-chat-plan、.blade-chat-plan-block、.blade-chat-queue、.blade-chat-queue-item、.blade-chat-queue-notice
跟进区.blade-chat-followup、.blade-chat-followup-action
输入区.blade-chat-input、.blade-chat-input-inner、.blade-chat-textarea、.blade-chat-input-toolbar
上下文卡片.blade-chat-context-card、.blade-chat-context-summary、.blade-chat-context-icon、.blade-chat-context-copy、.blade-chat-context-title、.blade-chat-context-status、.blade-chat-context-chevron、.blade-chat-context-detail、.blade-chat-context-group-items
Markdown 与代码块.blade-chat-markdown、.blade-chat-prose、.blade-chat-codeblock、.blade-chat-codeblock-header、.blade-chat-codeblock-body
其他.blade-chat-host(<blade-chat> 内的挂载点)、.blade-chat-advanced、.blade-chat-mcp-app、.blade-chat-error-row、.blade-chat-error-block

part 清单(<blade-chat> 的 Shadow DOM 专用)

Shadow DOM 把上面的类名挡在页面之外,所以主要区块额外带一个与锚点同名的 part(去掉 blade-chat- 前缀;根容器的锚点是 blade-chat,约定用 root),页面外层用 ::part() 选:

blade-chat::part(user-bubble) { background: #eef2ff; }
blade-chat::part(input-toolbar) { gap: 12px; }

清单(src 里每一处 part 都由构建校验,必须能对回锚点清单):

分组part
容器与状态root、banner、error-bar、replay-bar、mcp-app
消息区messages、messages-scroll、messages-content、empty、scroll-bottom
用户消息user-row、user-bubble、file-card、error-row
助手回复assistant-turn、assistant-text、thinking、memory-refs
工具与提问tool-row、agent-loop、ask-card、ask-option
计划与队列plan、plan-block、queue
跟进区followup、followup-action
输入区input、input-inner、textarea、input-toolbar

外围区块(.blade-chat-context-*、.blade-chat-codeblock-* 等)只有类名锚点,React 下可以直接选;<blade-chat> 下要改它们得用第 4 层的内嵌 <style>(那段样式就注入在 Shadow DOM 里)。

CSS 变量清单

颜色是 hsl() 的三个分量(形如 223 100% 54%),几何是长度值。宿主盖掉这些变量即可整体换肤(React 下写在 .blade-chat 或祖先元素上;<blade-chat> 下写在元素上,变量能穿透 Shadow DOM):

分组变量
基础色--background、--foreground、--card、--card-foreground、--popover、--popover-foreground
语义色--primary、--primary-foreground、--muted、--muted-foreground、--accent、--accent-foreground、--destructive、--border、--ring
用户消息专用--user-msg-bg、--user-msg-fg、--user-msg-border
布局(SDK 按容器宽度推导)--blade-chat-gutter(消息区与输入区的左右留白)、--blade-chat-turn-gap(轮次间距)。两个变量声明在 .blade-chat 上,设在祖先元素上不生效(元素上的声明永远赢过继承值)。React 下直接对 .blade-chat 写规则,或用 classNames.root 传一个设该变量的工具类(如 "[--blade-chat-gutter:2rem]");<blade-chat> 下用第 4 层的内嵌 <style>
布局(接入方按需覆盖)--blade-chat-queue-gutter(队列面板左右留白,默认跟随 gutter)、--blade-chat-pin-spacer(消息顶置的底部补白,组件自己写)、--blade-chat-input-top-border(输入区上边框,组件自己写)

深色主题的变量挂在 [data-theme="dark"] 上,由 theme prop / 属性落到聊天根节点,整棵子树继承。

上面这些是会改变 SDK 组件外观的变量。另外随主题一起声明的还有 --radius、--input、--destructive-foreground:agent-react 的默认组件不消费它们(圆角走 Tailwind 自己的 --radius-*),宿主在聊天容器里自绘 UI 时可以直接用,改动它们不会影响 SDK 自带的界面。

区块级渲染器(renderers)

classNames 换不掉整个区块时用 renderers:每个渲染器拿到一个描述该区块的对象,返回 ReactNode;返回 null 走 SDK 默认渲染。

<ChatView
  sessionId={id}
  renderers={{
    toolCall: (toolCall) => <MyToolCard toolCall={toolCall} />,
    userMessage: ({ message }) => <MyBubble message={message} />,
    assistantText: ({ message, streaming, compact }) => (compact ? null : <MyMarkdown text={message.content} />),
    askUserQuestion: ({ data, answered, onAnswer }) => <MyQuestions data={data} answered={answered} onAnswer={onAnswer} />,
    planUpdate: ({ plan, running }) => <MyPlanSteps plan={plan} running={running} />,
  }}
/>
  • toolCall:(toolCall: ToolCallInfo) => ReactNode | null,见 ToolCallRenderer。
  • userMessage:UserMessageRendererProps——只替换用户消息气泡,消息行(.blade-chat-user-row)与图片/文件附件仍由 SDK 渲染;消息顶置依赖那个行容器,替换掉它新消息就不会被顶到视口顶部。
  • assistantText:AssistantTextRendererProps——只替换助手正文,思考块、工具卡、附件照旧。
  • askUserQuestion:AskUserQuestionRendererProps——data 是 AskUserQuestionData,onAnswer 与默认卡片同一个回调契约;照常作答才算完成这次提问。提问工具优先走这个渲染器,只有没配它时才回落到 toolCall(避免 toolCall 先拿到提问、而它没有作答回调)。
  • planUpdate:PlanUpdateRendererProps——plan 是最近一次成功 update_plan 的解析结果;还没有解析出计划时渲染器不会被调用,回落到默认面板的「正在更新任务进度…」。

自定义消息列表滚动

ChatView、AgentChat 和 LlmChat 已经处理好用户发言顶置,不用额外配置。自建消息列表时可用 useMessagePin 复用同一套触发、补白、视口 resize 和手动滚动接管逻辑;调用方只需提供滚动容器、目标消息与 spacer 的读写函数。完整参数见 public-api.md。

打字机式显示节流

ChatView、AgentChat 和 LlmChat 已经内置这套节流,不用额外配置。服务端到达速率天然不均匀(一次推 5 个字、一次推 20 个字),直接原样渲染会显得忽快忽慢;useTypewriterReveal(targetText, isLive, resetKey) 按估算到达速度、留一个很小的固定滞后量匀速播放,返回 { displayedText, isRevealing, flushNow }——自建消息列表时可以直接复用。isLive 传 false(历史加载 / 回放)时永远整段直接显示;resetKey(比如消息的 entry_id)变化时播放状态从头开始;flushNow() 用于用户点了停止时立即整段显示已到达内容。完整参数见 public-api.md。

长会话历史导航(useTurnNavigation / TurnNavRail)

AgentChat 已经内置这条轨道,不用额外配置:右侧刻度轨列出整条会话的用户发言(目录来自服务端 /turn-index,所以窗口外的旧发言也点得到),点已加载的发言直接滚动并短暂高亮,点窗口外的发言发 一次定位请求(服务端返回「从它到最新」的窗口)后滚动到目标。

自建聊天界面时用 useTurnNavigation 复用同一套时序(滚动 / 加载态 / 高亮):宿主提供 TurnNavigationSource(目录怎么来、定位怎么发、刷新信号)与 getLoadedEntryElement / revealEntry / beforeSeek 三个列表侧回调(合起来就是 TurnNavigationOptions),返回 TurnNavigationResult(items、loadingEntryId、 error、failedEntryId、activeEntryId、selectEntry)。TurnNavRail 是配套的刻度轨组件 (TurnNavRailProps),样式与交互可直接用或照抄;定位高亮的类名是 TURN_NAV_HIGHLIGHT_CLASS。 目录刷新信号用 latestUserSpeechEntryId(turns) 计算——只认最新一条用户发言,往前翻页补进的更早 发言不该作废正在进行的定位;没有目录接口的会话(静态历史 / 分享页)用 turnNavItemsFromMessages 把已加载的窗口转成兜底条目。

纯静态 HTML / Vue?

不需要本包——用后端托管的单文件产物 <script src=".../sdk/blade-agent.js"> + <blade-chat> 自定义元素,行为与 ChatView 一致;详见 agent-client README 与接入文档。

附录:连接器与插件选择

  • 连接器面板:ConnectorPanel、ConnectorPanelProps、PluginConnectorList、PluginConnectorListProps、ConfiguringPlugin
  • 插件行与触发器:PluginConnectorItem、PluginConnectorSelection、selectionFromItem、triggerSummary、TriggerSummary、TRIGGER_ICON_LIMIT、mergeConnectorItems、catalogToConnectorItems、filterConnectorItems
  • 触发器上的头像堆叠:PluginTriggerStack、PluginTriggerStackProps(前三枚插件头像 + +N,宿主自己画触发器时用它,不要另写一份折叠)
  • 启用时序:useSessionPluginActivation、UseSessionPluginActivationResult

附录:re-export 自 @blade-hq/agent-client

为方便单包引入,本包 re-export 了 agent-client 的全部公开声明(BladeClient、AgentSession、SessionState、协议类型等),文档一律见 agent-client 的 README。完整签名见 public-api.md。

本包自有声明:BladeProvider、BladeProviderProps、useBladeClient、useAgentSession、UseAgentSessionOptions、UseAgentSessionResult、useMessagePin、UseMessagePinOptions、useTypewriterReveal、UseTypewriterRevealResult、useReplay、UseReplayResult、ReplayMismatch、ReplayBar、ReplayBarProps、ReplayMismatchPrompt、ReplayMismatchPromptProps、ChatView、ChatViewProps、ChatViewClassNames、ChatViewRenderers、ChatViewSlots、FollowupInteractionEvent、ToolCallRenderer、CurrentPlanPanel、PlanUpdateBlock、PlanUpdateData、PlanUpdateDisplayState、PlanStepStatus、getPlanUpdateDisplayState、isPlanUpdateTool、parsePlanUpdate、pickCurrentPlanStep、PLAN_AUTO_COLLAPSE_MS、MarkdownContent、MarkdownContentProps、MarkdownRenderer、MarkdownRendererProps、MarkdownComponents、AllowedTags、DEFAULT_ALLOWED_TAGS、CodeBlockOverrides、LinkSafetyConfig、RawHtmlPlugin、parseWhatIfPrompt、ParsedWhatIfPrompt、WhatIfQuote、WhatIfUserBubble、WhatIfUserBubbleProps、SessionQueuePanel、SessionQueuePanelProps、isQueuePanelVisible、ExternalAnchor、stripSystemReminders。

会话消息队列

智能体会话的等待消息由服务端持久化,ChatView / AgentChat 已在输入框上方渲染队列区域(空队列不占位,条目数超过三条可折叠)。输入框不再有「直接插入 / 排队执行」开关:会话空闲时提交直接开始处理,运行中提交默认入队;暂停时在面板上显示原因和「继续执行」。面板数据来自 useAgentSession 状态里的 queue(SessionQueueSnapshot),可编辑/删除/立即补充的判定来自 agent-client 的 canEditQueuedMessage / canCancelQueuedMessage / canDeliverQueuedMessage,文案用 queuedMessageStatusLabel / queuePauseReasonLabel。

自建布局可直接使用 <SessionQueuePanel>,属性类型为 SessionQueuePanelProps:传 snapshot、canDeliver、可选的 pendingItemId(提交中的条目禁用操作)与 notice(冲突/失败说明),以及 onResume / onCancel / onEdit / onMove / onReorder / onDeliver 回调。上移/下移与拖动排序最终都提交当前 pending 条目的完整 id 顺序。冲突时队列由服务端快照刷新,ChatView 会把未保存的正文放回输入框,不会静默丢掉用户编辑的内容。

面板比输入框窄一圈、贴在它上方,所以宽度必须跟输入框一致:用可选属性 contentWidthClassName 传入输入框内层的宽度类(不传则按本包输入框的 max-w-[748px])。左右内边距同理——默认值对应本包输入框的 --blade-chat-gutter;如果你给输入框根节点换了左右 padding(例如改成 px-10),必须用 rootClassName 在面板根节点重设 --blade-chat-queue-gutter 成同一个值,否则面板会跟输入框错开一圈、在两者相接的缝上露出来。

输入框的 mergesWithPanelAbove 为 true 时上两角改直角,跟面板拼成一条完整轮廓;否则输入框的上角圆弧会从较窄的面板两侧露出来,形成两个尖角。这个值要取面板上报的状态,不要在宿主侧用 isQueuePanelVisible 复算:面板有本地编辑态(编辑到一半、那条等待消息被服务端拿走时仍要留在原地保住草稿,见 detachedEdit),这时 snapshot 里没有条目、宿主复算会得到 false,输入框就会在面板还显示着的时候把上角恢复成圆角。用 onVisibleChange 拿到面板的真实占位状态,再传给输入框或 ChatSurface 的 queuePanelVisible。本包的 AgentChat 已按这条接好,自建布局照做即可。

拼轮廓的前提是两者真的相邻。中间夹着别的内容时(本包的 MCP 暂存内容、内置 Web 的 composerBanner),面板下两角与输入框上两角都要圆起来——直角是"被对方接住"的表示,露出直角却中间空着一块,看起来像被削掉一角。面板的 mergesWithInputBelow 与输入框的 mergesWithPanelAbove 要取同一个判据;ChatSurface 会用 context 把这一位传给面板,自建布局直接传即可。

PollingBackoff 从 agent-client 重导出,供自定义只读轮询计算退避时间;它不发送或重放请求。用法见 agent-client 轮询说明。

会话插件

默认 AgentChat / 智能体模式 ChatView 自带会话插件选择器,展开时查询 BH,选择按当前会话保存。准备失败会保留勾选并提供重试。独立嵌入可使用 <SessionPluginSelector client={client} sessionId={id} />;sessionPluginControls 可覆盖默认控件。组件参数类型为 SessionPluginSelectorProps。

列表项用插件声明的业务名称与随包本地图标展示(sessionPluginLabel(plugin) 与 <SessionPluginIcon client={client} sessionId={id} plugin={plugin} />,两者都可单独复用,参数类型为 SessionPluginLabelProps / SessionPluginIconProps);没有声明或图标不可用时回退技术 ID 与通用占位。授权、缓存、调用一律仍用 plugin.name。

插件的账号、token 等配置由包内 JSON Schema 声明,选择器就地提供填写入口:首次激活缺配置的插件会直接弹出表单(<SessionPluginConfigDialog client={client} sessionId={id} plugin={plugin} />),已配置的插件在行内提供「配置」按钮。表单本体是 <SessionPluginConfigForm>,参数与提交类型为 SessionPluginConfigFormProps / SessionPluginConfigDraft,也可单独嵌入;SessionPluginConfigDialogProps 是对话框的参数类型。

表单只呈现它确定能呈现的 JSON Schema 构造(字符串/数值/布尔/null、对象与数组、枚举、properties 内的 writeOnly,以及用同一字段的 const 固定的 oneOf 分支);遇到无法安全呈现的写法直接说明原因,不降级成手写 JSON、不静默丢字段。writeOnly 字段永不回显:留空表示保持不变,输入表示修改,显式「清除」才会删除。

连接器面板

首页与对话输入框共用同一个连接器面板:<ConnectorPanel client={client} sessionId={id} />, 参数类型为 ConnectorPanelProps。面板本身只管插件分区,电脑与外部账号由宿主通过 renderComputerSection / renderExternalAccounts / renderFooterActions 注入——那些入口 要跳宿主自己的路由,SDK 不硬编码内部路径。sessionId 传 null 表示"首页":此时开关只记录 本次输入的待选(PluginConnectorSelection、onPendingPluginsChange),不激活任何东西、 不建会话、不启沙盒;pendingDisabledReason 用于说明该模式下为什么用不了待选。

插件行的渲染交给 <PluginConnectorList>(PluginConnectorListProps),它只负责样式与排版: "该显示成什么"全部来自 PluginConnectorItem、mergeConnectorItems、catalogToConnectorItems、 filterConnectorItems,判定不各写一份。sessionPluginLabel 决定 一行读起来是什么名字。

启用时序由 useSessionPluginActivation 收口(返回值类型 UseSessionPluginActivationResult): 先准备配置、再提交激活。缺必填配置时开关不落到已启用、立刻弹表单,保存由服务端确认配置 完整才提交;取消、校验失败、读取失败都保持关闭,不会先写 active=true 再补偿。慢准备/慢激活 期间开关仍可点,语义是取消这次意图而不是再排一次队。配置弹窗由 ConfiguringPlugin 描述。

触发器要在不打开面板时就能显示"选了哪几个、几个":用 selectionFromItem 把一行转成 PluginConnectorSelection,再用 triggerSummary 折成 TriggerSummary(最多 TRIGGER_ICON_LIMIT 个图标,其余折成数量;没有图标 token 的项不占图标位但仍计数)。

MCP Apps

McpAppCard 使用官方 AppBridge 渲染归档卡片,内置 Web 消费同一组件。在 BladeProvider 下传入会话 ID 与来源 ID,也可直接提供 BladeClient。McpAppMessage 表示 App 提供的文本;McpAppMessageContext 将其交给当前对话的待发送区。用户可在 ChatView 查看、移除并确认发送。update-model-context 会把 App 提交的当前状态经 sessions.updateMcpAppContext 存为会话上下文:只落库、不追加聊天、不启动 agent turn,正在运行的一轮保持启动时版本,下一个新的运行才读取;冲突(409)时重读最新 revision 后重提同一份状态一次。

子智能体卡片

AgentTaskCard 提供共享的可折叠子智能体卡片,AgentTaskCardProps 接受任务 title、status、最新动作 activity 和详情 children。AgentTaskStatus 包含 running、waiting_input、completed、failed、cancelled;进入 waiting_input 时自动展开,提示图标持续轻缓闪动,并遵守系统减少动态效果设置。回答表单由宿主通过 children 提供,回答后应更新状态以停止提示。

对话分支选择器

SessionBranchPicker(属性类型 SessionBranchPickerProps)提供与内置 Web 共用的 Git 风格对话树、消息预览、切换中禁用和错误提示,可放进宿主的弹窗或侧栏。tree 使用 client.sessions.getSessionCheckpoints(sessionId) 的返回值;disabled 应来自该会话的运行状态或只读权限,不能仅依据当前标签页是否收到流式消息。

<SessionBranchPicker
  tree={checkpoints}
  disabled={isRunning || readOnly}
  onCheckout={async (checkpointId, position) => {
    const result = await client.sessions.checkoutSession(
      sessionId, checkpointId, position, "conversation",
    )
    await refreshConversation()
    setDraft(result.position === "before" ? result.content : "")
    setCheckpoints(await client.sessions.getSessionCheckpoints(sessionId))
  }}
/>

示例中的刷新消息、设置草稿与保存检查点状态由宿主实现。消息圆点回到发送前(before),虚线圆点切到分支末尾(turn_end)。宿主弹窗应明确说明:这些操作只修改对话记录,不修改、删除或恢复工作目录中的文件;请求使用 conversation 模式,不使用文件恢复模式。

会话图片

MessageImage 是聊天组件共用的图片组件,属性类型为 MessageImageProps。它支持原生 <img> 属性,并从 BladeProvider 获取客户端;也可显式传入 client。会话资源使用带鉴权的独立请求,公开分享图片使用分享令牌授权,在接近可视区域时加载预览,卸载后回收对象 URL。传入 original 可用于原图预览。普通外部图片 URL 仍直接由浏览器加载。

<MessageImage src={imageUrl} alt="附件图片" width={width} height={height} />

对话执行耗时

getChatRun(messages) 取最新一次根对话运行区间;getChatRunDuration(messages, now) 返回结束后的后端总耗时,或运行中从区间起点到校准服务器时间的毫秒数。这个区间包含工具、子任务和结束前的收尾,不重复累加并行任务。

useLiveExecutionDuration 在起点就绪后用单调时钟逐秒更新,停止时清理计时器。自建聊天界面可复用这组函数;模型和工具各自的性能样本仍分别保留。

版本与发布记录

@blade-hq/agent-react 共发布 246 个版本,首次发布于 2026/07/21;过去 12 个月发布了 246 个版本。

版本发布日期距今
2610.0.6latest2026/11/11今天
2612.0.0-beta.84beta2026/11/10今天
2612.0.0-beta.832026/11/10今天
2612.0.0-beta.822026/11/101 天前
2612.0.0-beta.812026/11/101 天前
2612.0.0-beta.802026/11/092 天前

其他发布通道:alpha → 2610.0.1-alpha.1,next → 2610.0.0-rc.0,beta → 2612.0.0-beta.84。

关于 @blade-hq/agent-react 的常见问题

@blade-hq/agent-react 是什么?

Blade Agent React 绑定:BladeProvider、useAgentSession、开箱即用的 ChatView 聊天界面。

如何安装 @blade-hq/agent-react?

在项目目录运行 npm install @blade-hq/agent-react,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^18.0.0 || ^19.0.0)、react-dom(^18.0.0 || ^19.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@blade-hq/agent-react 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@blade-hq/agent-react 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@blade-hq/agent-react 可以商用吗?

@blade-hq/agent-react 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@blade-hq/agent-react 的下载量有多少?

根据 npm 官方下载统计,@blade-hq/agent-react 在 2026/10/09 – 2026/11/07 这 30 天内被下载 9,759 次,日均约 325 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@blade-hq/agent-react 还在维护吗?

最新版本 2610.0.6 发布于 2026/11/11(今天);至今共发布 246 个版本,其中过去 12 个月发布了 246 个;首次发布于 2026/07/21。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。