@springbrand/chat-client 0.1.3-alpha.3 → 0.1.3-alpha.31

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@springbrand/chat-client",
3
- "version": "0.1.3-alpha.3",
3
+ "version": "0.1.3-alpha.31",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src",
@@ -13,7 +13,7 @@
13
13
  ".": "./src/index.ts"
14
14
  },
15
15
  "peerDependencies": {
16
- "agents": "^0.19.0",
16
+ "agents": "^0.20.1",
17
17
  "ai": "^7.0.0",
18
18
  "react": "^19.0.0"
19
19
  },
@@ -25,7 +25,7 @@
25
25
  "@types/react-dom": "^19.2.3",
26
26
  "react-dom": "^19.2.7",
27
27
  "typescript": "^7.0.2",
28
- "@springbrand/agent-runtime": "0.1.3-alpha.4"
28
+ "@springbrand/agent-runtime": "0.2.0-alpha.42"
29
29
  },
30
30
  "scripts": {
31
31
  "typecheck": "tsc --noEmit"
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { useAgent } from "agents/react";
3
3
 
4
- interface InboxState<Session> {
4
+ export interface InboxState<Session> {
5
5
  chats: Session[];
6
6
  workspaceRev?: number;
7
7
  }
@@ -33,8 +33,11 @@ export async function loadChatSessions<Session>(
33
33
  }
34
34
 
35
35
  /** Browser client for the user Inbox. Routing and presentation stay with the host UI. */
36
- export function useChatSessions<Session>(options: ChatSessionsOptions = {}) {
37
- const inbox = useAgent<InboxState<Session>>({
36
+ export function useChatSessions<
37
+ Session,
38
+ State extends InboxState<Session> = InboxState<Session>,
39
+ >(options: ChatSessionsOptions = {}) {
40
+ const inbox = useAgent<State>({
38
41
  agent: "Inbox",
39
42
  basePath: CURRENT_INBOX_AGENT_PATH,
40
43
  ...(options.protocols === undefined ? {} : { protocols: options.protocols }),
@@ -93,6 +96,7 @@ export function useChatSessions<Session>(options: ChatSessionsOptions = {}) {
93
96
  synced: inbox.state !== undefined || snapshot !== undefined,
94
97
  connected: inbox.identified,
95
98
  workspaceRev: inbox.state?.workspaceRev ?? 0,
99
+ inboxState: inbox.state,
96
100
  createChat,
97
101
  forkChat,
98
102
  renameChat,
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export {
2
+ UniversalAgentChatProvider,
2
3
  useUniversalAgentChat,
3
4
  type AgentToolRun,
5
+ type ChatConnectionStatus,
4
6
  type ChatConnectionOptions,
5
7
  type ChatRuntime,
6
8
  } from "./use-universal-agent-chat";
@@ -21,4 +23,5 @@ export {
21
23
  useChatSessions,
22
24
  type ChatSessionsOptions,
23
25
  type ChatSessions,
26
+ type InboxState,
24
27
  } from "./chat-sessions";
@@ -1,13 +1,27 @@
1
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1
+ import {
2
+ createContext,
3
+ createElement,
4
+ Suspense,
5
+ useCallback,
6
+ useContext,
7
+ useEffect,
8
+ useMemo,
9
+ useRef,
10
+ useState,
11
+ type ReactNode,
12
+ } from "react";
2
13
  import { nanoid } from "nanoid";
3
14
  import type { ChatStatus, FileUIPart, UIMessage } from "ai";
4
15
  import type { AgentToolRunState } from "agents";
16
+ import type { AgentConnectionError } from "agents/client";
5
17
  import { MessageType } from "agents/chat";
6
18
  import { getAgentMessages, useAgentChat } from "agents/chat/react";
7
19
  import { useAgent, useAgentToolEvents } from "agents/react";
8
20
  import {
21
+ type ApprovalDecision,
9
22
  type MessageDelivery,
10
23
  type MessageDispatchReceipt,
24
+ type RequestedCapability,
11
25
  type RuntimeAssemblyView,
12
26
  type RuntimeConfigUpdateResult,
13
27
  type RuntimeQueuedSubmission,
@@ -17,9 +31,17 @@ import { createSafeUIMessageAgentConnection } from "./ui-message-stream-guard";
17
31
  import { CURRENT_INBOX_AGENT_PATH } from "./chat-sessions";
18
32
 
19
33
  type ChatMessageMetadata = Record<string, unknown> & {
34
+ error?: string;
35
+ requestedCapabilities?: readonly RequestedCapability[];
36
+ turnId?: string;
20
37
  turnStatus?: string;
21
38
  };
22
39
  type ChatMessage = UIMessage<ChatMessageMetadata>;
40
+ export type ChatConnectionStatus =
41
+ | "connecting"
42
+ | "connected"
43
+ | "rejected"
44
+ | "error";
23
45
  type FailedDispatch = {
24
46
  message: ChatMessage;
25
47
  delivery: MessageDelivery;
@@ -82,12 +104,26 @@ type ChatTurn = {
82
104
  export interface ChatRuntime {
83
105
  messages: ChatMessage[];
84
106
  status: ChatStatus;
107
+ connectionStatus: ChatConnectionStatus;
108
+ connectionError: AgentConnectionError | undefined;
85
109
  runtimeLoad: ChatRuntimeLoadState | undefined;
86
110
  isStreaming: boolean;
87
111
  error: string | Error | undefined;
88
- sendText: (text: string, files?: readonly FileUIPart[]) => Promise<boolean>;
89
- steerText: (text: string, files?: readonly FileUIPart[]) => Promise<boolean>;
90
- enqueueText: (text: string, files?: readonly FileUIPart[]) => Promise<boolean>;
112
+ sendText: (
113
+ text: string,
114
+ files?: readonly FileUIPart[],
115
+ capabilities?: readonly RequestedCapability[],
116
+ ) => Promise<boolean>;
117
+ steerText: (
118
+ text: string,
119
+ files?: readonly FileUIPart[],
120
+ capabilities?: readonly RequestedCapability[],
121
+ ) => Promise<boolean>;
122
+ enqueueText: (
123
+ text: string,
124
+ files?: readonly FileUIPart[],
125
+ capabilities?: readonly RequestedCapability[],
126
+ ) => Promise<boolean>;
91
127
  steerQueued: (submissionId: string) => Promise<boolean>;
92
128
  cancelQueued: (submissionId: string) => Promise<boolean>;
93
129
  stop: () => Promise<void>;
@@ -101,6 +137,10 @@ export interface ChatRuntime {
101
137
  toolCallId: string,
102
138
  response: unknown,
103
139
  ) => Promise<boolean>;
140
+ decideApproval: (
141
+ executionId: string,
142
+ decision: ApprovalDecision,
143
+ ) => Promise<{ ok: boolean }>;
104
144
  /**
105
145
  * 读取该 Session facet 当前真正装配出来的 Runtime。
106
146
  *
@@ -110,6 +150,10 @@ export interface ChatRuntime {
110
150
  * 装配未就绪还是连接断了,收敛成 `undefined` 会让调试面板无从显示原因。
111
151
  */
112
152
  getRuntimeAssembly: () => Promise<RuntimeAssemblyView>;
153
+ getRuntimeBinding: <Binding = unknown>() => Promise<Binding>;
154
+ readChatMetadata: <Metadata = unknown>() => Promise<Metadata>;
155
+ renameChat: <Metadata = unknown>(title: string) => Promise<Metadata>;
156
+ rebindRuntime: <Input = unknown, Result = unknown>(input: Input) => Promise<Result>;
113
157
  updateConfig: <Change = unknown>(
114
158
  command: unknown,
115
159
  ) => Promise<RuntimeConfigUpdateResult<Change>>;
@@ -131,8 +175,13 @@ type SharedChatConnectionOptions = {
131
175
  host?: string;
132
176
  credentials?: RequestCredentials;
133
177
  protocols?: string | string[];
178
+ onMessage?: (message: MessageEvent) => void;
134
179
  inboxAgent?: string;
135
180
  sessionAgent?: string;
181
+ /** Skip history loading when the caller has just created this Chat. */
182
+ skipInitialMessages?: boolean;
183
+ /** Host projection used only until the selected Session sends its first state. */
184
+ initialTurnActive?: boolean;
136
185
  };
137
186
 
138
187
  export type ChatConnectionOptions = SharedChatConnectionOptions & (
@@ -140,6 +189,57 @@ export type ChatConnectionOptions = SharedChatConnectionOptions & (
140
189
  | { inboxName?: never; basePath?: string }
141
190
  );
142
191
 
192
+ type SessionAgentConnection = ReturnType<typeof useAgent<RuntimeState>>;
193
+ type UniversalAgentChatContextValue = {
194
+ chatId: string;
195
+ connection: ChatConnectionOptions;
196
+ agent: SessionAgentConnection;
197
+ chatAgent: SessionAgentConnection;
198
+ };
199
+
200
+ const UniversalAgentChatContext = createContext<UniversalAgentChatContextValue | null>(null);
201
+
202
+ export function UniversalAgentChatProvider({
203
+ chatId,
204
+ connection = {},
205
+ fallback = null,
206
+ children,
207
+ }: {
208
+ chatId: string;
209
+ connection?: ChatConnectionOptions;
210
+ fallback?: ReactNode;
211
+ children: ReactNode;
212
+ }) {
213
+ const inboxAgent = connection.inboxAgent ?? "Inbox";
214
+ const agent = useAgent<RuntimeState>({
215
+ agent: inboxAgent,
216
+ ...(connection.inboxName === undefined
217
+ ? { basePath: connection.basePath ?? CURRENT_INBOX_AGENT_PATH }
218
+ : { name: connection.inboxName }),
219
+ ...(connection.host === undefined ? {} : { host: connection.host }),
220
+ ...(connection.protocols === undefined ? {} : { protocols: connection.protocols }),
221
+ ...(connection.onMessage === undefined ? {} : { onMessage: connection.onMessage }),
222
+ connectionTimeout: 6_000,
223
+ sub: [{
224
+ agent: connection.sessionAgent ?? "UniversalAgent",
225
+ name: chatId,
226
+ }],
227
+ });
228
+ const chatAgent = useMemo(
229
+ () => createSafeUIMessageAgentConnection(agent),
230
+ [agent],
231
+ );
232
+ const value = useMemo(
233
+ () => ({ chatId, connection, agent, chatAgent }),
234
+ [agent, chatAgent, chatId, connection],
235
+ );
236
+ return createElement(
237
+ UniversalAgentChatContext.Provider,
238
+ { value },
239
+ createElement(Suspense, { fallback }, children),
240
+ );
241
+ }
242
+
143
243
  type ChatTurnTiming = {
144
244
  chatId: string;
145
245
  messageId: string;
@@ -195,10 +295,18 @@ function markChatTurnPhase(
195
295
  function createChatMessage(
196
296
  text: string,
197
297
  files?: readonly FileUIPart[],
298
+ capabilities: readonly RequestedCapability[] = [],
198
299
  ): ChatMessage | null {
199
300
  const normalizedText = text.trim();
200
301
  if (!normalizedText && !files?.length) return null;
201
302
  const createdAt = Date.now();
303
+ const seen = new Set<string>();
304
+ const requestedCapabilities = capabilities.filter((capability) => {
305
+ const key = `${capability.kind}:${capability.name}`;
306
+ if (seen.has(key)) return false;
307
+ seen.add(key);
308
+ return true;
309
+ });
202
310
  return {
203
311
  id: nanoid(),
204
312
  role: "user",
@@ -212,6 +320,7 @@ function createChatMessage(
212
320
  createdAt,
213
321
  authorDisplayName: "You",
214
322
  messageSource: "Web",
323
+ ...(requestedCapabilities.length > 0 ? { requestedCapabilities } : {}),
215
324
  },
216
325
  };
217
326
  }
@@ -258,10 +367,12 @@ function loadInitialMessages(
258
367
  * sub 数组由客户端 kebab 化;服务端按 ctx.exports 反解回 CamelCase className,
259
368
  * 与 Inbox.onBeforeSubAgent 的严格门卫(hasSubAgent)对齐。
260
369
  */
261
- export function useUniversalAgentChat(
262
- chatId: string,
263
- connection: ChatConnectionOptions = {},
264
- ): ChatRuntime {
370
+ export function useUniversalAgentChat(): ChatRuntime {
371
+ const context = useContext(UniversalAgentChatContext);
372
+ if (!context) {
373
+ throw new Error("useUniversalAgentChat must be used within UniversalAgentChatProvider");
374
+ }
375
+ const { chatId, connection, agent, chatAgent } = context;
265
376
  const [optimisticMessages, setOptimisticMessages] =
266
377
  useState<ChatMessage[]>([]);
267
378
  const [optimisticQueued, setOptimisticQueued] =
@@ -270,24 +381,6 @@ export function useUniversalAgentChat(
270
381
  const [canRetry, setCanRetry] = useState(false);
271
382
  const failedDispatchRef = useRef<FailedDispatch | undefined>(undefined);
272
383
  const activeSubmissionIdRef = useRef<string | undefined>(undefined);
273
- const inboxAgent = connection.inboxAgent ?? "Inbox";
274
- const agent = useAgent<RuntimeState>({
275
- agent: inboxAgent,
276
- ...(connection.inboxName === undefined
277
- ? { basePath: connection.basePath ?? CURRENT_INBOX_AGENT_PATH }
278
- : { name: connection.inboxName }),
279
- ...(connection.host === undefined ? {} : { host: connection.host }),
280
- ...(connection.protocols === undefined ? {} : { protocols: connection.protocols }),
281
- sub: [{
282
- agent: connection.sessionAgent ?? "UniversalAgent",
283
- name: chatId,
284
- }],
285
- });
286
- const chatAgent = useMemo(
287
- () => createSafeUIMessageAgentConnection(agent),
288
- [agent],
289
- );
290
-
291
384
  // 轮询→推送:这条 chat 连接连的是 UniversalAgent facet,`agent.state` 即 facet 的
292
385
  // AgentState 广播(useAgent 内部 useState,收到 cf_agent_state 即 re-render)。待批项
293
386
  // 随它推来 —— 审批读侧复用这条已开的连接,不另开第二条 WebSocket。三态(原则 VII):
@@ -303,22 +396,27 @@ export function useUniversalAgentChat(
303
396
  isStreaming,
304
397
  error,
305
398
  clearError,
399
+ stop: stopChat,
306
400
  // autonomous responses 纪律(CF 文档采纳 2026-07-05):框架已提供三个流态标志,
307
401
  // 透出供 UI 区分「服务端主动流 vs 用户发起流」+「恢复态」(与 stall 看门狗配套)。
308
402
  isServerStreaming,
309
- isRecovering,
403
+ isRecovering: sdkIsRecovering,
310
404
  isToolContinuation,
311
- connectionError,
405
+ connectionError: sdkConnectionError,
312
406
  } = useAgentChat<RuntimeState, ChatMessage>({
313
407
  agent: chatAgent,
314
- getInitialMessages: ({ url }) =>
315
- loadInitialMessages(url, connection.credentials),
408
+ getInitialMessages: connection.skipInitialMessages
409
+ ? null
410
+ : ({ url }) => loadInitialMessages(url, connection.credentials),
316
411
  ...(connection.credentials === undefined
317
412
  ? {}
318
413
  : { credentials: connection.credentials }),
319
414
  syncMessagesToServer: false,
320
415
  throttle: 100,
321
416
  });
417
+ // The SDK keeps its advisory recovery flag until terminal settlement; a
418
+ // live server stream proves the recovered Turn is executing again.
419
+ const isRecovering = sdkIsRecovering && !isServerStreaming;
322
420
  const visibleMessages = useMemo(
323
421
  () => mergeOptimisticMessages(messages, optimisticMessages),
324
422
  [messages, optimisticMessages],
@@ -338,6 +436,18 @@ export function useUniversalAgentChat(
338
436
  ? "ready"
339
437
  : sdkStatus;
340
438
  const sdkError = stoppedByUser || hasTerminalMessage ? undefined : error;
439
+ const connectionError = sdkConnectionError ?? undefined;
440
+ const connectionStatus: ChatConnectionStatus = connectionError
441
+ ? connectionError.code === 1008 ? "rejected" : "error"
442
+ : agent.identified ? "connected" : "connecting";
443
+ const runtimeError = dispatchError ?? sdkError ?? connectionError;
444
+ const runtimeErrorMessage = runtimeError instanceof Error
445
+ ? runtimeError.message
446
+ : runtimeError;
447
+ const presentationError = latestTurnStatus === "error" &&
448
+ latestMessage?.metadata?.error === runtimeErrorMessage
449
+ ? undefined
450
+ : runtimeError;
341
451
  // RPC admissions bypass useChat's request lifecycle. Project the durable
342
452
  // Turn here so presentation stays correct without a second send path.
343
453
  const authoritativeStatus =
@@ -349,9 +459,12 @@ export function useUniversalAgentChat(
349
459
  ? "streaming"
350
460
  : "submitted"
351
461
  : normalizedSdkStatus;
462
+ const hydratingTurnActive = facetState === undefined &&
463
+ connection.initialTurnActive === true;
352
464
  const status = dispatchError
353
465
  ? "error"
354
- : optimisticMessages.length > 0 && authoritativeStatus === "ready"
466
+ : (optimisticMessages.length > 0 || hydratingTurnActive) &&
467
+ authoritativeStatus === "ready"
355
468
  ? "submitted"
356
469
  : authoritativeStatus;
357
470
  const messagesRef = useRef(messages);
@@ -443,7 +556,8 @@ export function useUniversalAgentChat(
443
556
  isToolContinuation;
444
557
  const pendingLocalTurn = optimisticMessages.length > 0 &&
445
558
  (status === "submitted" || status === "streaming");
446
- const turnActive = Boolean(turn?.activeSubmissionId) ||
559
+ const turnActive = hydratingTurnActive ||
560
+ Boolean(turn?.activeSubmissionId) ||
447
561
  pendingLocalTurn ||
448
562
  (authoritativeTurn === undefined && sdkTurnActive);
449
563
  const canSteer = turnActive &&
@@ -462,11 +576,25 @@ export function useUniversalAgentChat(
462
576
  const dispatchMessage = useCallback(async (
463
577
  message: ChatMessage,
464
578
  delivery: MessageDelivery,
465
- ): Promise<MessageDispatchReceipt> =>
466
- agentRef.current.call<MessageDispatchReceipt>(
579
+ ): Promise<MessageDispatchReceipt> => {
580
+ const dispatch = () => agentRef.current.call<MessageDispatchReceipt>(
467
581
  "dispatchMessage",
468
582
  [message, delivery],
469
- ), []);
583
+ );
584
+ try {
585
+ return await dispatch();
586
+ } catch (error) {
587
+ // The server deduplicates dispatches by message.id, so replay only the
588
+ // transport failures that mean admission may already have succeeded.
589
+ const retryable = error instanceof Error && (
590
+ /^RPC call to dispatchMessage timed out after \d+ms$/u.test(error.message) ||
591
+ (error.message === "Connection closed" && agentRef.current.shouldReconnect)
592
+ );
593
+ if (!retryable) throw error;
594
+ await agentRef.current.ready;
595
+ return dispatch();
596
+ }
597
+ }, []);
470
598
 
471
599
  const submitMessage = useCallback(async (
472
600
  message: ChatMessage,
@@ -572,8 +700,9 @@ export function useUniversalAgentChat(
572
700
  const sendText = useCallback(async (
573
701
  text: string,
574
702
  files?: readonly FileUIPart[],
703
+ capabilities?: readonly RequestedCapability[],
575
704
  ): Promise<boolean> => {
576
- const message = createChatMessage(text, files);
705
+ const message = createChatMessage(text, files, capabilities);
577
706
  return message
578
707
  ? submitMessage(message, "enqueue", "message")
579
708
  : false;
@@ -582,21 +711,28 @@ export function useUniversalAgentChat(
582
711
  const dispatchText = useCallback(async (
583
712
  text: string,
584
713
  files: readonly FileUIPart[] | undefined,
714
+ capabilities: readonly RequestedCapability[] | undefined,
585
715
  delivery: MessageDelivery,
586
716
  projection: "message" | "queue",
587
717
  ): Promise<boolean> => {
588
- const message = createChatMessage(text, files);
718
+ const message = createChatMessage(text, files, capabilities);
589
719
  return message ? submitMessage(message, delivery, projection) : false;
590
720
  }, [submitMessage]);
591
721
 
592
722
  const steerText = useCallback(
593
- (text: string, files?: readonly FileUIPart[]) =>
594
- dispatchText(text, files, "steer", "message"),
723
+ (
724
+ text: string,
725
+ files?: readonly FileUIPart[],
726
+ capabilities?: readonly RequestedCapability[],
727
+ ) => dispatchText(text, files, capabilities, "steer", "message"),
595
728
  [dispatchText],
596
729
  );
597
730
  const enqueueText = useCallback(
598
- (text: string, files?: readonly FileUIPart[]) =>
599
- dispatchText(text, files, "enqueue", "queue"),
731
+ (
732
+ text: string,
733
+ files?: readonly FileUIPart[],
734
+ capabilities?: readonly RequestedCapability[],
735
+ ) => dispatchText(text, files, capabilities, "enqueue", "queue"),
600
736
  [dispatchText],
601
737
  );
602
738
  const steerQueued = useCallback(async (submissionId: string) => {
@@ -657,18 +793,18 @@ export function useUniversalAgentChat(
657
793
  type: MessageType.CF_AGENT_CHAT_REQUEST_CANCEL,
658
794
  id: requestId,
659
795
  }));
796
+ await stopChat();
660
797
  return;
661
798
  }
662
- const result = submissionId
663
- ? await agentRef.current.call<{ ok: boolean }>(
799
+ await (submissionId
800
+ ? agentRef.current.call<{ ok: boolean }>(
664
801
  "cancelSubmissionById",
665
802
  [submissionId, USER_STOP_REASON],
666
803
  )
667
- : await agentRef.current.call<{ ok: boolean }>(
804
+ : agentRef.current.call<{ ok: boolean }>(
668
805
  "stopTurn",
669
806
  [undefined, USER_STOP_REASON],
670
- );
671
- if (!result.ok) setDispatchError("The active Turn could not be stopped");
807
+ ));
672
808
  } catch (cause) {
673
809
  setDispatchError(
674
810
  cause instanceof Error ? cause.message : String(cause),
@@ -677,6 +813,7 @@ export function useUniversalAgentChat(
677
813
  }, [
678
814
  authoritativeTurn?.activeRequestId,
679
815
  authoritativeTurn?.activeSubmissionId,
816
+ stopChat,
680
817
  ]);
681
818
 
682
819
  const retry = useCallback(() => {
@@ -705,6 +842,14 @@ export function useUniversalAgentChat(
705
842
  return false;
706
843
  }
707
844
  }, []);
845
+ const decideApproval = useCallback(
846
+ (executionId: string, decision: ApprovalDecision) =>
847
+ agentRef.current.call<{ ok: boolean }>(
848
+ "decideApproval",
849
+ [executionId, decision],
850
+ ),
851
+ [],
852
+ );
708
853
 
709
854
  // 只读控制面:Runtime 尚未装配时由 Agent 侧 `ensureRuntimeReady` 负责等待,
710
855
  // 这里不缓存结果 —— 换模型、加技能或 reload 之后调用方要拿到的是新装配。
@@ -712,6 +857,24 @@ export function useUniversalAgentChat(
712
857
  () => agentRef.current.call<RuntimeAssemblyView>("getRuntimeAssembly", []),
713
858
  [],
714
859
  );
860
+ const getRuntimeBinding = useCallback(
861
+ <Binding,>() => agentRef.current.call<Binding>("getRuntimeBinding", []),
862
+ [],
863
+ );
864
+ const readChatMetadata = useCallback(
865
+ <Metadata,>() => agentRef.current.call<Metadata>("readChatMetadata", []),
866
+ [],
867
+ );
868
+ const renameChat = useCallback(
869
+ <Metadata,>(title: string) =>
870
+ agentRef.current.call<Metadata>("renameChat", [title]),
871
+ [],
872
+ );
873
+ const rebindRuntime = useCallback(
874
+ <Input, Result,>(input: Input) =>
875
+ agentRef.current.call<Result>("rebindRuntime", [input]),
876
+ [],
877
+ );
715
878
  const updateConfig = useCallback(
716
879
  <Change,>(command: unknown) =>
717
880
  agentRef.current.call<RuntimeConfigUpdateResult<Change>>(
@@ -730,15 +893,21 @@ export function useUniversalAgentChat(
730
893
 
731
894
  return {
732
895
  respondToolInteraction,
896
+ decideApproval,
733
897
  getRuntimeAssembly,
898
+ getRuntimeBinding,
899
+ readChatMetadata,
900
+ renameChat,
901
+ rebindRuntime,
734
902
  updateConfig,
735
903
  reloadRuntime,
736
904
  messages: visibleMessages,
737
905
  status,
906
+ connectionStatus,
907
+ connectionError,
738
908
  runtimeLoad: facetState?.runtimeLoad,
739
909
  isStreaming,
740
- error: dispatchError ?? sdkError ??
741
- connectionError ?? undefined,
910
+ error: presentationError,
742
911
  sendText,
743
912
  steerText,
744
913
  enqueueText,