@blade-hq/agent-react 2610.0.0-beta.8 → 2610.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,10 @@ Blade Agent 的 React 绑定:`BladeProvider` + `useAgentSession` + 开箱即
6
6
  pnpm add @blade-hq/agent-client @blade-hq/agent-react
7
7
  ```
8
8
 
9
+ 默认装到的是当前长期支持版(LTS),厂内离线交付按它开发。要跟两周一发的公网版本,改用 `@next`。
10
+
11
+ 最稳妥的做法是先读目标 Server 的 `GET /api/version`,按返回的版本号在 `package.json` 里钉死——NPM 的 `latest` 不一定和目标环境跑的版本一致。
12
+
9
13
  > 注意两个包都要装:`agent-client` 是运行时依赖,只装 `agent-react` 在 pnpm 下会直接报错。
10
14
 
11
15
  ## 快速开始
@@ -28,7 +32,6 @@ export function App() {
28
32
 
29
33
  - 不传 `sessionId` 自动创建新会话;未登录时 `ChatView` 自己渲染登录按钮(弹窗授权,见 agent-client 的 `client.auth.login()`)。
30
34
  - 同一 `BladeProvider` 下可以放多个 `ChatView`,各自独立会话、互不干扰。
31
- - 宿主只需要完成态消息时,可在 `BladeClient` 构造参数中传 `streamTokens: false`,不订阅逐 token 增量。
32
35
 
33
36
  ## BladeProvider
34
37
 
@@ -54,6 +57,7 @@ const client = useBladeClient() // 当前 Provider 的 BladeClient
54
57
  const { session, state, error } = useAgentSession(sessionId, {
55
58
  createOptions, // 不传 sessionId 时的建会话配置(UseAgentSessionOptions)
56
59
  onSessionCreated: (id) => saveSomewhere(id),
60
+ onSessionConnected: (session) => session.on("toolResult", handleResult),
57
61
  })
58
62
 
59
63
  state?.messages // ChatMessage[],直接渲染
@@ -67,8 +71,29 @@ await session?.send("你好")
67
71
 
68
72
  - 优先级:`sessionId`(连接既有会话)> `createOptions`(按配置新建)> 默认新建。
69
73
  - 自动创建只发生一次(含 React StrictMode 双跑);创建出的 id 通过 `onSessionCreated` 交还,重挂载时把它作为 `sessionId` 传回即可复用会话。
74
+ - 必须覆盖连接窗口内事件时,用 `onSessionConnected` 在历史加载和房间订阅开始前完成订阅,并返回取消订阅函数。
70
75
  - 同一 `sessionId` 的多次调用复用同一 `AgentSession` 实例(引用计数);卸载后延迟释放,路由抖动不会断连。
71
76
 
77
+ ## SessionMemoryToggle
78
+
79
+ 把当前会话是否使用记忆放进宿主自己的会话设置面板:
80
+
81
+ ```tsx
82
+ const [memoryEnabled, setMemoryEnabled] = useState(session.memory_enabled !== false)
83
+
84
+ <SessionMemoryToggle
85
+ sessionId={session.id}
86
+ enabled={memoryEnabled}
87
+ onSaved={(_sessionId, enabled) => setMemoryEnabled(enabled)}
88
+ onError={(error) => toast.error(String(error))}
89
+ />
90
+ ```
91
+
92
+ 组件默认使用 `BladeProvider` 的 client;已有独立 client 装配层的宿主也可以显式传
93
+ `client={client}`。同一 client、同一会话的多个开关共享保存锁,即使切换会话或同时存在多个
94
+ 设置入口,也不会并发提交相互覆盖。服务端确认后的权威值通过 `onSaved` 返回。
95
+ 全部属性见 `SessionMemoryToggleProps`。
96
+
72
97
  ## useReplay
73
98
 
74
99
  会话回放(演示 / 彩排):拿一个已经聊完的会话当素材,重现当时的回复和工具调用,
@@ -133,6 +158,9 @@ await replay.exitToAutonomous() // 退出回放,之后真的运
133
158
 
134
159
  完整聊天界面:消息列表(Markdown、代码高亮、工具调用、提问卡片)+ 输入框 + 连接状态条 + 登录引导。
135
160
 
161
+ `ChatView` 固定使用普通展示模式。产品内置 Web 的精简/开发者模式不属于 SDK 公共能力,
162
+ 因此没有 `renderMode` 等切换属性。
163
+
136
164
  ```tsx
137
165
  <ChatView
138
166
  sessionId={id} // 可选;不传自动建会话
@@ -150,7 +178,17 @@ await replay.exitToAutonomous() // 退出回放,之后真的运
150
178
 
151
179
  全部 props 见 `ChatViewProps`。样式说明:
152
180
 
181
+ 智能体调用 `update_plan` 新增或更新任务时,`ChatView` 会在输入框上方自动展开最新任务进度,
182
+ 5 秒后平滑收起;连续更新会重新计时,用户手动展开或收起后以用户选择为准。自建聊天布局可直接
183
+ 使用 `CurrentPlanPanel` / `PlanUpdateBlock`,或用 `getPlanUpdateDisplayState`、
184
+ `parsePlanUpdate` 和 `pickCurrentPlanStep` 复用同一套判定。
185
+
186
+ `ChatView` 的普通展示模式不显示只供诊断的项目说明、可用能力和当前工作环境。需要自建诊断界面时,可直接使用独立的 `ContextCard`,属性见 `ContextCardProps`;状态到文案的映射来自 agent-client 的 `getContextDisplayState`,不需要接入方重复判断。一次输入通常会同时注入十几项上下文,把渲染序列里连续的几项用 `ContextGroupCard`(属性见 `ContextGroupCardProps`)折成一行更合适;哪几项算一组由 agent-client 的 `groupAdjacentContextRuns` 判定,折叠行文案来自 `getContextGroupDisplayState` / `ContextGroupDisplayState`。底层数据类型为 `ContextProjectionData` / `ContextProjectionFields`,展示结果为 `ContextDisplayState`,并保留 `ContextAction`、`ContextSourceInfo` 和 `contextProjectionData` 给 headless 消费方。
187
+
188
+ 记忆引用提示可通过 `MemoryRefsHint` 展示,`collectMemoryRefs` 用于从一轮消息中聚合引用,保证宿主自定义消息布局与 SDK 默认界面保持一致。
189
+
153
190
  `onFollowupInteraction` 使用 `FollowupInteractionEvent`,覆盖下一步建议展示/采纳、成果展示/打开/下载以及结果评分。SDK 不内置 PostHog 等分析厂商;宿主回调抛错也不会中断用户点击或下载。
191
+ 文件成果以文件名为文本的标准下载链接展示;Vue / 纯 HTML 使用的 `<blade-chat>` 与 `ChatView` 行为一致。
154
192
 
155
193
  - 必须引入一份样式,按宿主有没有 Tailwind 二选一:
156
194
  - **`style.full.css`**(默认选它):布局兜底 + 编译好的 Tailwind 产物,自包含,宿主没装 Tailwind 也是完整视觉。
@@ -261,6 +299,43 @@ models.filter((m) => m.serviceModelId)
261
299
  <MarkdownContent sessionId={sessionId}>{markdownText}</MarkdownContent>
262
300
  ```
263
301
 
302
+ `normalizeAdjacentUrlFormatting` 可在复用其他 Markdown 渲染器前,保护紧贴行内格式标记的 URL。
303
+
304
+ ## Canonical 引用消息
305
+
306
+ `ChatView` 会自动识别 ship-attack 会话中的 `[引用]` / `[用户输入]` canonical
307
+ 消息,并按原顺序展示引用来源、快照和用户输入。自建消息列表时可用
308
+ `parseWhatIfPrompt` 解析同一格式,再交给 `WhatIfUserBubble` 渲染;
309
+ `onQuoteClick` 可接入宿主的步骤跳转。
310
+
311
+ ```tsx
312
+ const parsed = parseWhatIfPrompt(messageText)
313
+ return parsed ? (
314
+ <WhatIfUserBubble parsed={parsed} onQuoteClick={jumpToStep} />
315
+ ) : null
316
+ ```
317
+
318
+ ## isAgentComputerCommand / isAgentComputerToolCall / classifyAgentComputerLaunchOutcome
319
+
320
+ 判断智能体是否在沙盒里启动了通用 GUI 应用镜像(`computer launch`)——后端 blade-agent 通过 `computer` CLI 驱动一块虚拟屏幕运行任意 GUI 软件,这些函数用于自行接入方判断要不要展示对应的实时画面入口(比如自建一个类似「电脑镜像」的标签页)。当前 SDK 侧还没有内置这块 UI,只导出判定规则,避免各接入方重新猜一遍匹配逻辑。
321
+
322
+ `isAgentComputerCommand`/`isAgentComputerToolCall` 只回答"这条命令是不是一次 launch 尝试",不回答"这次 launch 最终成不成功"——待处理、已失败的 launch 也会命中。要判断真实结果(比如决定要不要展示画面入口),用 `classifyAgentComputerLaunchOutcome`:它会解析工具调用的 `status`/`result`,返回 `"pending" | "succeeded" | "failed" | "unknown"`。`"unknown"` 表示 `status` 已经是终态但 `result` 字段本身缺失(常见于会话整理/compaction 之后的历史投影,归档时不会重新序列化完整 result),跟"result 回来了、里面确实没有成功标记"的 `"failed"` 是两种不同性质的证据——接入方要按自己的场景决定怎么处理(展示类场景可以偏宽松,当作足够展示;自动跳转/替用户做决定的场景应该偏保守,当作跟 `"failed"` 一样处理)。
323
+
324
+ ```ts
325
+ import {
326
+ isAgentComputerCommand,
327
+ isAgentComputerToolCall,
328
+ classifyAgentComputerLaunchOutcome,
329
+ } from "@blade-hq/agent-react"
330
+
331
+ isAgentComputerCommand("computer launch --exec /opt/apps/foo/bar") // true
332
+ isAgentComputerCommand("computer status") // false(只认 launch,不认查询类子命令)
333
+
334
+ isAgentComputerToolCall(toolCall.arguments) // 工具调用的 JSON 参数版本
335
+
336
+ classifyAgentComputerLaunchOutcome(toolCall) // "pending" | "succeeded" | "failed" | "unknown"
337
+ ```
338
+
264
339
  ## 选择模型
265
340
 
266
341
  SDK 的 ChatView / `<blade-chat>` 不内置模型选择器(默认用后端配置的模型)。指定模型有两种方式:
@@ -296,6 +371,10 @@ blade-chat { --primary: 262 83% 58%; height: 640px; }
296
371
  </blade-chat>
297
372
  ```
298
373
 
374
+ ## 自定义消息列表滚动
375
+
376
+ `ChatView`、`AgentChat` 和 `LlmChat` 已经处理好用户发言顶置,不用额外配置。自建消息列表时可用 `useMessagePin` 复用同一套触发、补白、视口 resize 和手动滚动接管逻辑;调用方只需提供滚动容器、目标消息与 spacer 的读写函数。完整参数见 [public-api.md](./public-api.md#usemessagepin-function)。
377
+
299
378
  ## 纯静态 HTML / Vue?
300
379
 
301
380
  不需要本包——用后端托管的单文件产物 `<script src=".../sdk/blade-agent.js">` + `<blade-chat>` 自定义元素,行为与 `ChatView` 一致;详见 agent-client README 与接入文档。
@@ -304,4 +383,4 @@ blade-chat { --primary: 262 83% 58%; height: 640px; }
304
383
 
305
384
  为方便单包引入,本包 re-export 了 agent-client 的全部公开声明(`BladeClient`、`AgentSession`、`SessionState`、协议类型等),文档一律见 [agent-client 的 README](../agent-client/README.md)。完整签名见 [public-api.md](./public-api.md)。
306
385
 
307
- 本包自有声明:`BladeProvider`、`BladeProviderProps`、`useBladeClient`、`useAgentSession`、`UseAgentSessionOptions`、`UseAgentSessionResult`、`useReplay`、`UseReplayResult`、`ReplayMismatch`、`ReplayBar`、`ReplayBarProps`、`ReplayMismatchPrompt`、`ReplayMismatchPromptProps`、`ChatView`、`ChatViewProps`、`ChatViewClassNames`、`ChatViewRenderers`、`ChatViewSlots`、`FollowupInteractionEvent`、`ToolCallRenderer`、`MarkdownContent`、`MarkdownContentProps`。
386
+ 本包自有声明:`BladeProvider`、`BladeProviderProps`、`useBladeClient`、`useAgentSession`、`UseAgentSessionOptions`、`UseAgentSessionResult`、`useMessagePin`、`UseMessagePinOptions`、`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`、`parseWhatIfPrompt`、`ParsedWhatIfPrompt`、`WhatIfQuote`、`WhatIfUserBubble`、`WhatIfUserBubbleProps`。
@@ -2,9 +2,6 @@ import type { ToolCallInfo } from "@blade-hq/agent-client";
2
2
  interface Props {
3
3
  toolCall: ToolCallInfo;
4
4
  }
5
- /**
6
- * 子智能体(fork Agent)状态块的 SDK 简化版:只展示描述、状态与最终结果,
7
- * 不再嵌套渲染子循环的完整消息流(那是第一方界面的能力)。
8
- */
5
+ /** SDK 没有第一方子循环消息流,只保留轻量意图行和可展开的最终结果。 */
9
6
  export declare function AgentLoopBlock({ toolCall }: Props): import("react/jsx-runtime").JSX.Element;
10
7
  export {};
@@ -28,4 +28,13 @@ interface Props {
28
28
  export declare function AskUserQuestionBlock({ data, answered, toolCallId, sessionStatus, answerData, onAnswer, }: Props): import("react/jsx-runtime").JSX.Element;
29
29
  /** 解析 AskUserQuestion 的工具结果/参数,容错 LLM 把 questions 输出为字符串的情况。 */
30
30
  export declare function parseAskUserQuestion(toolResult: string | null | undefined): AskUserQuestionData | null;
31
+ export interface AskUserQuestionError {
32
+ message: string;
33
+ detail: string | null;
34
+ }
35
+ /**
36
+ * Keep this parser synchronized with apps/web/AskUserQuestionBlock.tsx.
37
+ * The built-in Web cannot import agent-react, and this is not a public SDK protocol API.
38
+ */
39
+ export declare function parseAskUserQuestionError(toolResult: string | null | undefined): AskUserQuestionError | null;
31
40
  export {};
@@ -1,4 +1,4 @@
1
- import type { AskUserAnswerData, ChatMessage } from "@blade-hq/agent-client";
1
+ import type { AskUserAnswerData, ChatMessage, MemoryRefInfo } from "@blade-hq/agent-client";
2
2
  import { type ToolCallRenderer } from "./ToolCallBlock";
3
3
  interface Props {
4
4
  messages: ChatMessage[];
@@ -7,12 +7,26 @@ interface Props {
7
7
  onAnswer?: (answer: string, toolCallId: string, answerData: AskUserAnswerData) => void;
8
8
  sessionStatus?: string;
9
9
  toolCallRenderer?: ToolCallRenderer;
10
+ /** Agent 模式把 update_plan 收口到输入框上方的任务面板;纯 LLM 同名工具照常展示。 */
11
+ hidePlanUpdateTools?: boolean;
10
12
  /** 会话 id;传给 Markdown 渲染的统一上下文。 */
11
13
  sessionId?: string;
12
14
  }
13
- /**
14
- * 一轮助手回复:按消息顺序渲染 thinking 折叠块、正文 Markdown 与工具调用。
15
- * 相比第一方版本去掉了紧凑/详细模式切换、文件交付卡片与内联资源 iframe。
16
- */
17
- export declare function AssistantTurnBlock({ messages, isStreaming, askAnswers, onAnswer, sessionStatus, toolCallRenderer, sessionId, }: Props): import("react/jsx-runtime").JSX.Element;
15
+ export type TurnDisplayMode = "compact" | "detail";
16
+ export declare function resolveTurnDisplayMode({ isStreaming: _isStreaming, displayMode, }: {
17
+ isStreaming: boolean;
18
+ displayMode: TurnDisplayMode;
19
+ }): TurnDisplayMode;
20
+ export declare function formatExecutionDuration(durationMs: number): string;
21
+ export declare function getExecutionDurationMs({ messages, isStreaming, now, }: {
22
+ messages: ChatMessage[];
23
+ isStreaming: boolean;
24
+ now?: number;
25
+ }): number;
26
+ /** 一轮助手回复:执行过程默认折叠,最终正文始终显示在摘要下方。 */
27
+ export declare function AssistantTurnBlock({ messages, isStreaming, askAnswers, onAnswer, sessionStatus, toolCallRenderer, hidePlanUpdateTools, sessionId, }: Props): import("react/jsx-runtime").JSX.Element;
28
+ export declare function collectMemoryRefs(messages: ChatMessage[]): MemoryRefInfo[];
29
+ export declare function MemoryRefsHint({ refs }: {
30
+ refs: MemoryRefInfo[];
31
+ }): import("react/jsx-runtime").JSX.Element;
18
32
  export {};
@@ -3,16 +3,19 @@ interface Props {
3
3
  value: string;
4
4
  onValueChange: (value: string) => void;
5
5
  onSend: (text: string) => boolean | Promise<boolean>;
6
+ /** Optional direct insertion path used while another response streams. */
7
+ onAppend?: (text: string) => void;
6
8
  onStop: () => void;
7
9
  isStreaming: boolean;
8
10
  isStopping?: boolean;
9
11
  placeholder?: string;
10
12
  className?: string;
13
+ queueKey?: string;
11
14
  }
12
15
  /**
13
16
  * 轻量聊天输入框:textarea(Enter 发送 / Shift+Enter 换行)+ 发送/停止按钮。
14
17
  * 不含第一方版本的富文本编辑、@ 补全、技能菜单、语音输入与模型选择。
15
18
  * 内容受控,session.attach() / insertText() 通过 ChatView 注入到这里。
16
19
  */
17
- export declare function ChatInput({ value, onValueChange, onSend, onStop, isStreaming, isStopping, placeholder, className, }: Props): import("react/jsx-runtime").JSX.Element;
20
+ export declare function ChatInput({ value, onValueChange, onSend, onAppend, onStop, isStreaming, isStopping, placeholder, className, queueKey, }: Props): import("react/jsx-runtime").JSX.Element;
18
21
  export {};
@@ -45,6 +45,7 @@ interface ChatSurfaceProps extends ChatPresentationProps {
45
45
  onInputChange: (value: string) => void;
46
46
  onSuggestion?: (value: string) => void;
47
47
  onSend: (text: string) => Promise<boolean>;
48
+ onAppend?: (text: string) => void;
48
49
  onStop: () => void;
49
50
  sessionStatus?: string;
50
51
  askAnswers?: Record<string, AskUserAnswerData>;
@@ -55,6 +56,10 @@ interface ChatSurfaceProps extends ChatPresentationProps {
55
56
  onResultFeedbackSaved?: (feedback: ResultFeedback) => void;
56
57
  /** 输入框上方的内部区域(纯 LLM 模式的高级设置用)。不传时不渲染任何节点。 */
57
58
  beforeInput?: ReactNode;
59
+ /** 仅 Agent 模式启用 Blade 的 update_plan 面板。 */
60
+ showPlanUpdates?: boolean;
61
+ /** 仅实时成功的 update_plan 递增;历史 hydration 不得改变。 */
62
+ planRevealRevision?: number;
58
63
  /** 连接状态条下方的横幅(回放模式条用)。不传时不渲染任何节点。 */
59
64
  banner?: ReactNode;
60
65
  }
@@ -64,5 +69,5 @@ interface ChatSurfaceProps extends ChatPresentationProps {
64
69
  * 这里的 DOM 结构受 tests/chat-view-compat.test.tsx 的快照保护——接入方拿
65
70
  * .blade-chat-* 类名写了自己的样式,结构一动他们的页面就歪。
66
71
  */
67
- export declare function ChatSurface({ theme, classNames, renderers, slots, placeholder, connection, errorMessage, messages, postChatFollowup, isStreaming, isStopping, inputText, onInputChange, onSuggestion, onSend, onStop, sessionStatus, askAnswers, onAnswer, sessionId, isViewer, resultFeedbackByEntry, onResultFeedbackSaved, onFollowupInteraction, beforeInput, banner, }: ChatSurfaceProps): import("react/jsx-runtime").JSX.Element;
72
+ export declare function ChatSurface({ theme, classNames, renderers, slots, placeholder, connection, errorMessage, messages, postChatFollowup, isStreaming, isStopping, inputText, onInputChange, onSuggestion, onSend, onAppend, onStop, sessionStatus, askAnswers, onAnswer, sessionId, isViewer, resultFeedbackByEntry, onResultFeedbackSaved, onFollowupInteraction, beforeInput, showPlanUpdates, planRevealRevision, banner, }: ChatSurfaceProps): import("react/jsx-runtime").JSX.Element;
68
73
  export {};
@@ -0,0 +1,20 @@
1
+ import { type ContextProjectionData } from "@blade-hq/agent-client";
2
+ export interface ContextCardProps {
3
+ context: ContextProjectionData;
4
+ className?: string;
5
+ }
6
+ /** Business-facing Context summary. Native details keeps every card collapsed by default. */
7
+ export declare function ContextCard({ context, className }: ContextCardProps): import("react/jsx-runtime").JSX.Element;
8
+ export interface ContextGroupCardProps {
9
+ contexts: readonly ContextProjectionData[];
10
+ className?: string;
11
+ }
12
+ /**
13
+ * 一批上下文注入折叠成一行。会话首轮会一次注入十几个原子,一条一张卡片会把
14
+ * 真正的对话挤出屏幕。
15
+ *
16
+ * 单条和多条**共用同一个 details 外壳**,不在数量变化时切换组件:流式时第二条
17
+ * 到达会让这一行从单条变成多条,组件类型一换 React 就重挂 <details>,
18
+ * 用户刚展开的行会被收起来。同理,key 由调用方按组内第一条给,不能带数量。
19
+ */
20
+ export declare function ContextGroupCard({ contexts, className }: ContextGroupCardProps): import("react/jsx-runtime").JSX.Element | null;
@@ -1,3 +1,5 @@
1
+ /** Keep inline formatting markers from being consumed by URL autolinking. */
2
+ export declare function normalizeAdjacentUrlFormatting(value: string): string;
1
3
  export interface MarkdownContentProps {
2
4
  children: string;
3
5
  className?: string;
@@ -11,6 +11,7 @@ interface Props {
11
11
  askAnswers?: Record<string, AskUserAnswerData>;
12
12
  onAnswer?: (answer: string, toolCallId: string, answerData: AskUserAnswerData) => void;
13
13
  toolCallRenderer?: ToolCallRenderer;
14
+ hidePlanUpdateTools?: boolean;
14
15
  emptyState?: ReactNode;
15
16
  className?: string;
16
17
  /** 会话 id;传给助手消息的统一上下文。 */
@@ -24,5 +25,5 @@ interface Props {
24
25
  * 消息列表:use-stick-to-bottom 自动滚动 + 按轮次分组渲染。
25
26
  * 相比第一方版本去掉了轮次导航栏、吸顶状态条与规划摘要卡片。
26
27
  */
27
- export declare function MessageList({ messages, postChatFollowup, onSuggestion, isStreaming, sessionStatus, askAnswers, onAnswer, toolCallRenderer, emptyState, className, sessionId, isViewer, onFollowupInteraction, resultFeedbackByEntry, onResultFeedbackSaved, }: Props): import("react/jsx-runtime").JSX.Element;
28
+ export declare function MessageList({ messages, postChatFollowup, onSuggestion, isStreaming, sessionStatus, askAnswers, onAnswer, toolCallRenderer, hidePlanUpdateTools, emptyState, className, sessionId, isViewer, onFollowupInteraction, resultFeedbackByEntry, onResultFeedbackSaved, }: Props): import("react/jsx-runtime").JSX.Element;
28
29
  export {};
@@ -0,0 +1,31 @@
1
+ import type { ChatMessage, ToolCallInfo } from "@blade-hq/agent-client";
2
+ export type PlanStepStatus = "pending" | "in_progress" | "completed";
3
+ export interface PlanUpdateData {
4
+ plan: Array<{
5
+ step: string;
6
+ status: PlanStepStatus;
7
+ }>;
8
+ }
9
+ export declare const PLAN_AUTO_COLLAPSE_MS = 5000;
10
+ export declare function isPlanUpdateTool(toolCall: Pick<ToolCallInfo, "name">): boolean;
11
+ export interface PlanUpdateDisplayState {
12
+ current: ToolCallInfo | null;
13
+ updating: boolean;
14
+ }
15
+ export declare function getPlanUpdateDisplayState(messages: ChatMessage[]): PlanUpdateDisplayState;
16
+ export declare function parsePlanUpdate(argumentsJson: string): PlanUpdateData | null;
17
+ export declare function pickCurrentPlanStep(plan: PlanUpdateData["plan"]): PlanUpdateData["plan"][number] | null;
18
+ export declare function PlanUpdateBlock({ toolCall, running, autoReveal, }: {
19
+ toolCall: ToolCallInfo;
20
+ running?: boolean;
21
+ /** 实时收到的新进度短暂展开;加载历史时保持默认收起。 */
22
+ autoReveal?: boolean;
23
+ }): import("react/jsx-runtime").JSX.Element | null;
24
+ export declare function CurrentPlanPanel({ messages, running, revealRevision, sessionId, className, }: {
25
+ messages: ChatMessage[];
26
+ running?: boolean;
27
+ /** 仅实时成功的 update_plan 递增;历史 hydration 不得改变。 */
28
+ revealRevision?: number;
29
+ sessionId?: string;
30
+ className?: string;
31
+ }): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,19 @@
1
+ import type { BladeClient } from "@blade-hq/agent-client";
2
+ export interface SessionMemoryToggleProps {
3
+ sessionId: string;
4
+ enabled: boolean;
5
+ /** 不在 BladeProvider 内使用时显式传入,与宿主现有 BladeClient 复用连接和鉴权。 */
6
+ client?: BladeClient;
7
+ disabled?: boolean;
8
+ label?: string;
9
+ className?: string;
10
+ labelClassName?: string;
11
+ inputClassName?: string;
12
+ onSaved?: (sessionId: string, enabled: boolean) => void;
13
+ onError?: (error: unknown) => void;
14
+ }
15
+ /**
16
+ * 当前会话记忆开关。同一 client + sessionId 的所有实例共享保存锁,避免多个聊天入口
17
+ * 或会话切换时发出乱序更新;服务端成功后才通过 onSaved 交还权威值。
18
+ */
19
+ export declare function SessionMemoryToggle({ sessionId, enabled, client: clientProp, disabled, label, className, labelClassName, inputClassName, onSaved, onError, }: SessionMemoryToggleProps): import("react/jsx-runtime").JSX.Element;
@@ -8,6 +8,7 @@ interface Props {
8
8
  answered?: boolean;
9
9
  answerData?: AskUserAnswerData;
10
10
  sessionStatus?: string;
11
+ isActiveQuestion?: boolean;
11
12
  renderer?: ToolCallRenderer;
12
13
  }
13
14
  /**
@@ -19,10 +20,11 @@ interface Props {
19
20
  * 会同时把已经回答过的旧提问重新变成可作答(历史加载不会带回 answerData),
20
21
  * 而且会盖住投影层的错误——这个 bug 就是这么藏住的。
21
22
  */
22
- export declare function resolveAskQuestionState({ toolStatus, hasAnswerData, fallbackAnswered, }: {
23
+ export declare function resolveAskQuestionState({ toolStatus, hasAnswerData, fallbackAnswered, fallbackAwaiting, }: {
23
24
  toolStatus: ToolCallInfo["status"];
24
25
  hasAnswerData: boolean;
25
26
  fallbackAnswered?: boolean;
27
+ fallbackAwaiting?: boolean;
26
28
  }): {
27
29
  awaitingAnswer: boolean;
28
30
  answered: boolean;
@@ -31,5 +33,5 @@ export declare function resolveAskQuestionState({ toolStatus, hasAnswerData, fal
31
33
  * 极简工具调用块:状态 + 展示名一行,点开后是参数/结果的 <pre> 折叠展示。
32
34
  * AskUserQuestion 特殊处理为提问选项卡片。
33
35
  */
34
- export declare function ToolCallBlock({ toolCall, onAnswer, answered, answerData, sessionStatus, renderer, }: Props): import("react/jsx-runtime").JSX.Element;
36
+ export declare function ToolCallBlock({ toolCall, onAnswer, answered, answerData, sessionStatus, isActiveQuestion, renderer, }: Props): import("react/jsx-runtime").JSX.Element;
35
37
  export {};
@@ -11,7 +11,7 @@ interface UserMessageProps {
11
11
  message: UserChatMessage;
12
12
  className?: string;
13
13
  }
14
- /** 用户消息气泡:文本 + 图片/文件附件展示(不含第一方的预览弹窗与 what-if 徽章)。 */
14
+ /** 用户消息气泡:文本、canonical 引用消息与图片/文件附件。 */
15
15
  export declare function UserMessageBubble({ message, className }: UserMessageProps): import("react/jsx-runtime").JSX.Element;
16
16
  /** 错误消息:居中红色提示条。 */
17
17
  export declare function ErrorMessageBlock({ message, className, }: {
@@ -0,0 +1,7 @@
1
+ import type { ParsedWhatIfPrompt } from "../lib/whatif-prompt";
2
+ export interface WhatIfUserBubbleProps {
3
+ parsed: ParsedWhatIfPrompt;
4
+ onQuoteClick?: (stepNumber: number) => void;
5
+ }
6
+ /** SDK 与内置 Web 共用的 canonical 引用消息渲染。 */
7
+ export declare function WhatIfUserBubble({ parsed, onQuoteClick }: WhatIfUserBubbleProps): import("react/jsx-runtime").JSX.Element;
@@ -1,7 +1,17 @@
1
1
  import type { ToolCallInfo } from "@blade-hq/agent-client";
2
2
  export type ToolTone = "emerald" | "blue" | "amber" | "red";
3
+ /**
4
+ * 技能入口文件的所在目录就是技能名(`.agent/skills/<技能名>/SKILL.md`)。
5
+ * 只显示 "SKILL.md" 用户看不出读的是哪个技能。
6
+ *
7
+ * 改这里必须同步 apps/web/src/agent/components/chat/display-utils.ts 的同名函数。
8
+ */
9
+ export declare function getSkillNameFromFilePath(filePath: string | null | undefined): string | null;
3
10
  /** 归一化工具名:去掉命名空间前缀并映射到别名表(如 "fork:Agent" → "Agent")。 */
4
11
  export declare function formatToolName(name: string): string;
12
+ export declare function extractToolFilePath(toolCall: ToolCallInfo): string | null;
13
+ /** 从文件类工具参数中提取全部可定位的文件路径(按出现顺序去重)。 */
14
+ export declare function extractToolFilePaths(toolCall: ToolCallInfo): string[];
5
15
  /**
6
16
  * 返回工具调用的中文展示标签:优先取参数里的 description,
7
17
  * 其次取后端下发的 display_name / 系统工具中文名。
package/dist/context.d.ts CHANGED
@@ -11,3 +11,4 @@ export interface BladeProviderProps {
11
11
  export declare function BladeProvider({ client, children }: BladeProviderProps): import("react/jsx-runtime").JSX.Element;
12
12
  /** 读取当前 BladeProvider 提供的 client。不在 Provider 内使用时抛错。 */
13
13
  export declare function useBladeClient(): BladeClient;
14
+ export declare function useOptionalBladeClient(): BladeClient | null;
@@ -18,6 +18,9 @@ declare function getChat(element: Element | null): Promise<AgentSession>;
18
18
  declare const BladeAgent: {
19
19
  BladeClient: typeof agentClient.BladeClient;
20
20
  BladeApiError: typeof agentClient.BladeApiError;
21
+ EMPTY_PLATFORM_ENDPOINTS: agentClient.PlatformEndpoints;
22
+ loadPlatformEndpoints: typeof agentClient.loadPlatformEndpoints;
23
+ resolveServiceUrl: typeof agentClient.resolveServiceUrl;
21
24
  SDK_NAME: "agent-client";
22
25
  SDK_VERSION: string;
23
26
  AgentSession: typeof AgentSession;
@@ -29,8 +32,13 @@ declare const BladeAgent: {
29
32
  connectEmbedded: typeof agentClient.connectEmbedded;
30
33
  isCommandEnvelope: typeof agentClient.isCommandEnvelope;
31
34
  isInboundEnvelope: typeof agentClient.isInboundEnvelope;
35
+ ComputersResource: typeof agentClient.ComputersResource;
36
+ canToggleComputer: typeof agentClient.canToggleComputer;
37
+ computerState: typeof agentClient.computerState;
38
+ sortComputers: typeof agentClient.sortComputers;
32
39
  ModelsResource: typeof agentClient.ModelsResource;
33
40
  buildMessageContent: typeof agentClient.buildMessageContent;
41
+ chatErrorForDisplay: typeof agentClient.chatErrorForDisplay;
34
42
  contentPreview: typeof agentClient.contentPreview;
35
43
  extractTextAttachments: typeof agentClient.extractTextAttachments;
36
44
  getFileParts: typeof agentClient.getFileParts;
@@ -40,6 +48,10 @@ declare const BladeAgent: {
40
48
  isHiddenInternalMessage: typeof agentClient.isHiddenInternalMessage;
41
49
  normalizeMessageContent: typeof agentClient.normalizeMessageContent;
42
50
  transformSlashCommand: typeof agentClient.transformSlashCommand;
51
+ contextProjectionData: typeof agentClient.contextProjectionData;
52
+ getContextDisplayState: typeof agentClient.getContextDisplayState;
53
+ getContextGroupDisplayState: typeof agentClient.getContextGroupDisplayState;
54
+ groupAdjacentContextRuns: typeof agentClient.groupAdjacentContextRuns;
43
55
  latestPostChatFollowup: typeof agentClient.latestPostChatFollowup;
44
56
  DEFAULT_REPLAY_SPEED: agentClient.ReplaySpeed;
45
57
  SessionInfo: import("arktype/internal/variants/object.ts").ObjectType<{
@@ -71,6 +83,7 @@ declare const BladeAgent: {
71
83
  disable_tools?: string[] | undefined;
72
84
  runtime_type?: string | null | undefined;
73
85
  daemon_id?: string | null | undefined;
86
+ agent_runtime_id?: string | null | undefined;
74
87
  workspace_path?: string | null | undefined;
75
88
  match?: unknown;
76
89
  }, {}>;
@@ -4,6 +4,11 @@ export interface UseAgentSessionOptions {
4
4
  createOptions?: CreateSessionRequest;
5
5
  /** 自动创建出的 sessionId 通过此回调交还调用方持有(重挂载复用的依据)。 */
6
6
  onSessionCreated?: (sessionId: string) => void;
7
+ /**
8
+ * 会话进入实时事件路由后、历史加载与房间订阅开始前同步执行。
9
+ * 返回的清理函数会在切换会话或卸载时执行。
10
+ */
11
+ onSessionConnected?: (session: AgentSession) => () => void;
7
12
  }
8
13
  export interface UseAgentSessionResult {
9
14
  /** 连接/创建完成前为 null。 */
@@ -0,0 +1,22 @@
1
+ export interface UseMessagePinOptions {
2
+ targetKey: string | null;
3
+ pinTarget: boolean;
4
+ layoutKey?: string;
5
+ getScrollElement: () => HTMLElement | null;
6
+ getContentElement?: () => HTMLElement | null;
7
+ getTargetElement: () => HTMLElement | null;
8
+ getTargetScrollTop?: (scroll: HTMLElement) => number | null;
9
+ getSpacerHeight: () => number;
10
+ setSpacerHeight: (height: number) => void;
11
+ stopAutoScroll: () => void;
12
+ scrollToBottom: () => void;
13
+ margin?: number;
14
+ }
15
+ /**
16
+ * 用户发言顶置的共享状态机。SDK 和内置轻量列表只提供 DOM/虚拟列表适配器,
17
+ * 触发、调度、resize、手动接管和跟随交还的决策只在这里维护。
18
+ */
19
+ export declare function useMessagePin({ targetKey, pinTarget, layoutKey, getScrollElement, getContentElement, getTargetElement, getTargetScrollTop, getSpacerHeight, setSpacerHeight, stopAutoScroll, scrollToBottom, margin, }: UseMessagePinOptions): {
20
+ release: () => void;
21
+ isActive: () => boolean;
22
+ };
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export type { UseAgentSessionOptions, UseAgentSessionResult } from "./hooks/use-
5
5
  export { useReplay } from "./hooks/use-replay";
6
6
  export type { ReplayMismatch, UseReplayResult } from "./hooks/use-replay";
7
7
  export { useLlmChat } from "./hooks/use-llm-chat";
8
+ export { useMessagePin } from "./hooks/use-message-pin";
9
+ export type { UseMessagePinOptions } from "./hooks/use-message-pin";
8
10
  export type { ChatCompletionTool, LlmChatOptions, LlmToolCall, UseLlmChatResult, } from "./hooks/use-llm-chat";
9
11
  export { ChatView } from "./components/ChatView";
10
12
  export type { ChatViewClassNames, ChatViewProps, ChatViewRenderers, ChatViewSlots, FollowupInteractionEvent, } from "./components/ChatView";
@@ -14,10 +16,24 @@ export { LlmChat } from "./components/LlmChat";
14
16
  export type { LlmChatHandle, LlmChatProps } from "./components/LlmChat";
15
17
  export type { LlmAdvancedSettings, LlmOverride } from "./components/LlmAdvancedSettings";
16
18
  export { MarkdownContent } from "./components/MarkdownContent";
19
+ export { normalizeAdjacentUrlFormatting } from "./components/MarkdownContent";
17
20
  export type { MarkdownContentProps } from "./components/MarkdownContent";
21
+ export { WhatIfUserBubble } from "./components/WhatIfUserBubble";
22
+ export type { WhatIfUserBubbleProps } from "./components/WhatIfUserBubble";
23
+ export { parseWhatIfPrompt } from "./lib/whatif-prompt";
24
+ export type { ParsedWhatIfPrompt, WhatIfQuote } from "./lib/whatif-prompt";
18
25
  export { ReplayBar } from "./components/ReplayBar";
19
26
  export type { ReplayBarProps } from "./components/ReplayBar";
20
27
  export { ReplayMismatchPrompt } from "./components/ReplayMismatchPrompt";
21
28
  export type { ReplayMismatchPromptProps } from "./components/ReplayMismatchPrompt";
22
29
  export type { ToolCallRenderer } from "./components/ToolCallBlock";
30
+ export { CurrentPlanPanel, getPlanUpdateDisplayState, isPlanUpdateTool, PLAN_AUTO_COLLAPSE_MS, parsePlanUpdate, pickCurrentPlanStep, PlanUpdateBlock, } from "./components/PlanUpdateBlock";
31
+ export type { PlanStepStatus, PlanUpdateData, PlanUpdateDisplayState, } from "./components/PlanUpdateBlock";
32
+ export { ContextCard, ContextGroupCard } from "./components/ContextCard";
33
+ export type { ContextCardProps, ContextGroupCardProps } from "./components/ContextCard";
34
+ export { collectMemoryRefs, MemoryRefsHint } from "./components/AssistantTurnBlock";
35
+ export { SessionMemoryToggle } from "./components/SessionMemoryToggle";
36
+ export type { SessionMemoryToggleProps } from "./components/SessionMemoryToggle";
37
+ export { isAgentComputerCommand, isAgentComputerToolCall, classifyAgentComputerLaunchOutcome, } from "./lib/agent-computer-command";
38
+ export type { AgentComputerLaunchOutcome } from "./lib/agent-computer-command";
23
39
  export * from "@blade-hq/agent-client";