@dudousxd/nestjs-agent-react 0.31.5 → 0.33.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/dist/index.js CHANGED
@@ -9,9 +9,9 @@ function readErrorAnswer(text) {
9
9
  }
10
10
  if (body === null || typeof body !== "object")
11
11
  return { body, message: void 0, code: void 0 };
12
- const record = body;
13
- const message = typeof record.message === "string" && record.message !== "" ? record.message : Array.isArray(record.message) && record.message.length > 0 && record.message.every((each) => typeof each === "string") ? record.message.join("; ") : void 0;
14
- const code = typeof record.code === "string" && record.code !== "" ? record.code : void 0;
12
+ const record2 = body;
13
+ const message = typeof record2.message === "string" && record2.message !== "" ? record2.message : Array.isArray(record2.message) && record2.message.length > 0 && record2.message.every((each) => typeof each === "string") ? record2.message.join("; ") : void 0;
14
+ const code = typeof record2.code === "string" && record2.code !== "" ? record2.code : void 0;
15
15
  return { body, message, code };
16
16
  }
17
17
  async function readErrorResponse(response) {
@@ -88,6 +88,12 @@ var AgentClient = class {
88
88
  if (response.status === 202) {
89
89
  return { body: emptyStream(), queued: await response.json() };
90
90
  }
91
+ if (response.ok && response.headers.get("content-type")?.includes("application/json")) {
92
+ const decision = await response.json();
93
+ if (decision.proposalDecision !== void 0 && typeof decision.threadId === "string")
94
+ return { body: emptyStream(), threadId: decision.threadId, proposalDecision: decision };
95
+ throw new Error("Unexpected JSON chat response");
96
+ }
91
97
  if (!response.ok || !response.body) {
92
98
  throw await this.failure(response, "POST", `${this.agentPath()}/chat`);
93
99
  }
@@ -320,6 +326,54 @@ var AgentClient = class {
320
326
  * `remember` approves later calls of the same tool in the same thread; `via` names the surface
321
327
  * the decision came through (the server records `'web'` when omitted).
322
328
  */
329
+ async listActionProposals({ threadId }) {
330
+ const proposals = /* @__PURE__ */ new Map();
331
+ let after;
332
+ for (; ; ) {
333
+ const route = `/threads/${encodeURIComponent(threadId)}/action-proposals${after === void 0 ? "" : `?after=${encodeURIComponent(JSON.stringify(after))}`}`;
334
+ const response = await this.requestResponse("GET", route);
335
+ const page = await this.handleResponse(
336
+ response,
337
+ "GET",
338
+ `${this.agentPath()}${route}`
339
+ );
340
+ if (!Array.isArray(page)) throw new TypeError("Invalid action proposal page");
341
+ for (const proposal of page) proposals.set(proposal.id, proposal);
342
+ const header = response.headers.get("X-Action-Proposals-Next");
343
+ if (header === null) return [...proposals.values()];
344
+ let next;
345
+ try {
346
+ next = JSON.parse(decodeURIComponent(header));
347
+ } catch {
348
+ throw new TypeError("Invalid action proposal cursor");
349
+ }
350
+ if (typeof next !== "object" || next === null || !("createdAt" in next) || typeof next.createdAt !== "number" || !Number.isSafeInteger(next.createdAt) || !("id" in next) || typeof next.id !== "string" || next.id.length === 0 || next.id.length > 255 || after !== void 0 && (next.createdAt < after.createdAt || next.createdAt === after.createdAt && next.id <= after.id))
351
+ throw new TypeError("Invalid or non-advancing action proposal cursor");
352
+ after = { createdAt: next.createdAt, id: next.id };
353
+ }
354
+ }
355
+ approveActionProposal({
356
+ threadId,
357
+ proposalId,
358
+ remember
359
+ }) {
360
+ return this.request(
361
+ "POST",
362
+ `/threads/${encodeURIComponent(threadId)}/action-proposals/${encodeURIComponent(proposalId)}/approve`,
363
+ remember === void 0 ? {} : { remember }
364
+ );
365
+ }
366
+ rejectActionProposal({
367
+ threadId,
368
+ proposalId,
369
+ reason
370
+ }) {
371
+ return this.request(
372
+ "POST",
373
+ `/threads/${encodeURIComponent(threadId)}/action-proposals/${encodeURIComponent(proposalId)}/reject`,
374
+ reason === void 0 ? {} : { reason }
375
+ );
376
+ }
323
377
  approveToolCall(input) {
324
378
  return this.request("POST", "/tool-call/approve", input);
325
379
  }
@@ -376,6 +430,10 @@ var AgentClient = class {
376
430
  return this.options.credentials !== void 0 ? { credentials: this.options.credentials } : {};
377
431
  }
378
432
  async request(method, route, body) {
433
+ const response = await this.requestResponse(method, route, body);
434
+ return this.handleResponse(response, method, `${this.agentPath()}${route}`);
435
+ }
436
+ async requestResponse(method, route, body) {
379
437
  const path = `${this.agentPath()}${route}`;
380
438
  const response = await this.fetchImpl()(`${this.baseUrl()}${path}`, {
381
439
  method,
@@ -387,7 +445,7 @@ var AgentClient = class {
387
445
  ...body !== void 0 ? { body: JSON.stringify(body) } : {},
388
446
  ...this.credentials()
389
447
  });
390
- return this.handleResponse(response, method, path);
448
+ return response;
391
449
  }
392
450
  /** The error for a refused `response`, already reported to `onHttpError`. */
393
451
  async failure(response, method, path) {
@@ -527,6 +585,7 @@ var AgentChatTransport = class {
527
585
  const message = lastMessage ? extractText(lastMessage) : "";
528
586
  const body = {
529
587
  ...this.options.agent !== void 0 ? { agent: this.options.agent } : {},
588
+ ...this.options.persona !== void 0 ? { persona: this.options.persona } : {},
530
589
  ...this.options.getBody?.() ?? {},
531
590
  ...options.body ?? {},
532
591
  message
@@ -542,6 +601,10 @@ var AgentChatTransport = class {
542
601
  return queuedChunkStream(response.queued);
543
602
  }
544
603
  this.captureHeaderMeta(response);
604
+ if (response.proposalDecision !== void 0) {
605
+ this.attemptLive = false;
606
+ return proposalDecisionChunkStream(response.proposalDecision);
607
+ }
545
608
  return this.toChunkStream(response, {
546
609
  ...headers !== void 0 ? { headers } : {},
547
610
  ...options.abortSignal ? { signal: options.abortSignal } : {}
@@ -668,7 +731,7 @@ var AgentChatTransport = class {
668
731
  let reasoningSeq = 0;
669
732
  const announced = /* @__PURE__ */ new Set();
670
733
  const toolMetadata = /* @__PURE__ */ new Map();
671
- const record = (meta) => {
734
+ const record2 = (meta) => {
672
735
  streamRunId = meta.runId;
673
736
  this.recordMeta(meta);
674
737
  };
@@ -956,7 +1019,7 @@ var AgentChatTransport = class {
956
1019
  }
957
1020
  if (frame.event === "meta") {
958
1021
  const meta = parseMeta(frame.data);
959
- if (meta) record(meta);
1022
+ if (meta) record2(meta);
960
1023
  } else if (frame.data) {
961
1024
  const event = parseEvent(frame.data);
962
1025
  if (event) emit(event);
@@ -1024,10 +1087,10 @@ function parseErrorFrame(data) {
1024
1087
  try {
1025
1088
  const parsed = JSON.parse(data);
1026
1089
  if (parsed !== null && typeof parsed === "object") {
1027
- const record = parsed;
1090
+ const record2 = parsed;
1028
1091
  return {
1029
- code: typeof record.code === "string" && record.code !== "" ? record.code : void 0,
1030
- message: typeof record.message === "string" && record.message !== "" ? record.message : fallback2.message
1092
+ code: typeof record2.code === "string" && record2.code !== "" ? record2.code : void 0,
1093
+ message: typeof record2.message === "string" && record2.message !== "" ? record2.message : fallback2.message
1031
1094
  };
1032
1095
  }
1033
1096
  } catch {
@@ -1067,6 +1130,21 @@ function delay(ms, signal) {
1067
1130
  signal?.addEventListener("abort", done, { once: true });
1068
1131
  });
1069
1132
  }
1133
+ function proposalDecisionChunkStream(decision) {
1134
+ return new ReadableStream({
1135
+ start(controller) {
1136
+ controller.enqueue({ type: "start", messageId: `decision:${crypto.randomUUID()}` });
1137
+ controller.enqueue({ type: "data-proposal-decision", data: decision, transient: true });
1138
+ if (decision.text !== void 0 && decision.text.length > 0) {
1139
+ controller.enqueue({ type: "text-start", id: "decision" });
1140
+ controller.enqueue({ type: "text-delta", id: "decision", delta: decision.text });
1141
+ controller.enqueue({ type: "text-end", id: "decision" });
1142
+ }
1143
+ controller.enqueue({ type: "finish", finishReason: "stop" });
1144
+ controller.close();
1145
+ }
1146
+ });
1147
+ }
1070
1148
 
1071
1149
  // src/attachments/files.ts
1072
1150
  function fileKind(mediaType) {
@@ -1205,6 +1283,10 @@ function toGenerativeUIItem(part) {
1205
1283
  return {
1206
1284
  id,
1207
1285
  component: source.component,
1286
+ ...isRecord(source.componentVersions) && Object.values(source.componentVersions).every(
1287
+ (version) => typeof version === "number" && Number.isSafeInteger(version) && version > 0
1288
+ ) ? { componentVersions: { ...source.componentVersions } } : {},
1289
+ ...typeof source.fallbackText === "string" ? { fallbackText: source.fallbackText } : {},
1208
1290
  props: isRecord(source.props) ? source.props : {},
1209
1291
  version: typeof source.version === "number" ? source.version : null,
1210
1292
  toolCallId: typeof source.toolCallId === "string" ? source.toolCallId : null
@@ -1310,20 +1392,29 @@ function useValidatedProps(catalog, name, props) {
1310
1392
  function useGenerativeUIState(part, options, fallbackTree) {
1311
1393
  const item = useMemo(() => toGenerativeUIItem(part), [part]);
1312
1394
  const name = item?.component ?? "";
1395
+ const definition = options.catalog?.get?.(name);
1396
+ const incompatible = item?.fallbackText !== void 0 && item.version !== null && definition !== void 0 && item.version !== (definition.version ?? 1);
1313
1397
  const resolution = useResolvedComponent(
1314
1398
  name,
1315
1399
  item?.version ?? null,
1316
- options.registry,
1400
+ incompatible ? {} : options.registry,
1317
1401
  item === null ? void 0 : options.resolveComponent,
1318
1402
  fallbackTree
1319
1403
  );
1320
1404
  const empty = useMemo(() => ({}), []);
1321
1405
  const validation = useValidatedProps(
1322
- item === null || name === GENUI_TREE_COMPONENT ? void 0 : options.catalog,
1406
+ item === null || name === GENUI_TREE_COMPONENT || incompatible ? void 0 : options.catalog,
1323
1407
  name,
1324
1408
  item?.props ?? empty
1325
1409
  );
1326
1410
  if (item === null) return null;
1411
+ if (name === GENUI_TREE_COMPONENT && item.fallbackText !== void 0 && (!isRecord(item.props.root) || typeof item.props.root.type !== "string"))
1412
+ return {
1413
+ status: "problem",
1414
+ reason: "invalid",
1415
+ item,
1416
+ issues: [{ path: ["root"], message: "Invalid UI tree root" }]
1417
+ };
1327
1418
  if (resolution.status === "loading") return { status: "loading", item };
1328
1419
  if (resolution.status === "unknown") return { status: "problem", reason: "unknown", item };
1329
1420
  if (resolution.status === "error") {
@@ -1341,7 +1432,8 @@ import { jsx } from "react/jsx-runtime";
1341
1432
  var TreeScopeContext = createContext2(null);
1342
1433
  function renderFallback(fallback2, problem) {
1343
1434
  if (typeof fallback2 === "function") return fallback2(problem);
1344
- return fallback2 ?? null;
1435
+ if (fallback2 !== void 0) return fallback2;
1436
+ return problem.item.fallbackText !== void 0 ? /* @__PURE__ */ jsx("span", { style: { whiteSpace: "pre-wrap" }, children: problem.item.fallbackText }) : null;
1345
1437
  }
1346
1438
  var ItemBoundary = class extends Component {
1347
1439
  state = { error: void 0, failed: false };
@@ -1373,28 +1465,42 @@ function nodeItem(node, id) {
1373
1465
  function TreeNode({ node, id }) {
1374
1466
  const scope = useContext2(TreeScopeContext);
1375
1467
  const item = useMemo2(() => nodeItem(node, id), [node, id]);
1468
+ const storedVersion = scope?.componentVersions?.[node.type];
1469
+ const definition = scope?.options.catalog?.get?.(node.type);
1470
+ const incompatible = storedVersion !== void 0 && definition !== void 0 && storedVersion !== (definition.version ?? 1);
1376
1471
  const resolution = useResolvedComponent(
1377
1472
  node.type,
1378
- null,
1379
- scope?.options.registry ?? {},
1473
+ storedVersion ?? null,
1474
+ incompatible ? {} : scope?.options.registry ?? {},
1380
1475
  scope?.options.resolveComponent
1381
1476
  );
1382
- const validation = useValidatedProps(scope?.options.catalog, node.type, item.props);
1477
+ const validation = useValidatedProps(
1478
+ incompatible ? void 0 : scope?.options.catalog,
1479
+ node.type,
1480
+ item.props
1481
+ );
1383
1482
  if (scope === null) return null;
1384
1483
  if (resolution.status === "loading" || validation.status === "pending") return scope.loading;
1385
1484
  if (resolution.status === "unknown") {
1485
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0)
1486
+ throw new Error("Unknown tree component");
1386
1487
  return renderFallback(scope.fallback, { reason: "unknown", item });
1387
1488
  }
1388
1489
  if (resolution.status === "error") {
1490
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0) throw resolution.error;
1389
1491
  return renderFallback(scope.fallback, { reason: "error", item, error: resolution.error });
1390
1492
  }
1391
1493
  if (validation.status === "invalid") {
1494
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0)
1495
+ throw new Error("Invalid tree component");
1392
1496
  return renderFallback(scope.fallback, { reason: "invalid", item, issues: validation.issues });
1393
1497
  }
1394
1498
  const Renderer = resolution.Component;
1395
1499
  const children = Array.isArray(node.children) ? node.children : [];
1396
1500
  return /* @__PURE__ */ jsx(Renderer, { ...validation.props, children: children.length > 0 ? children.map((child, index) => {
1397
1501
  const childId = `${id}.${index}`;
1502
+ if (scope.fallback === void 0 && scope.fallbackText !== void 0)
1503
+ return /* @__PURE__ */ jsx(TreeNode, { node: child, id: childId }, childId);
1398
1504
  return /* @__PURE__ */ jsx(
1399
1505
  ItemBoundary,
1400
1506
  {
@@ -1416,6 +1522,8 @@ function GenerativeUIScope({
1416
1522
  catalog,
1417
1523
  resolveComponent,
1418
1524
  fallback: fallback2,
1525
+ fallbackText,
1526
+ componentVersions,
1419
1527
  loading = null,
1420
1528
  onError,
1421
1529
  children
@@ -1428,10 +1536,21 @@ function GenerativeUIScope({
1428
1536
  ...resolveComponent !== void 0 ? { resolveComponent } : {}
1429
1537
  },
1430
1538
  fallback: fallback2,
1539
+ ...componentVersions !== void 0 ? { componentVersions } : {},
1540
+ ...fallbackText !== void 0 ? { fallbackText } : {},
1431
1541
  loading,
1432
1542
  onError
1433
1543
  }),
1434
- [registry, catalog, resolveComponent, fallback2, loading, onError]
1544
+ [
1545
+ registry,
1546
+ catalog,
1547
+ resolveComponent,
1548
+ fallback2,
1549
+ fallbackText,
1550
+ componentVersions,
1551
+ loading,
1552
+ onError
1553
+ ]
1435
1554
  );
1436
1555
  return /* @__PURE__ */ jsx(TreeScopeContext.Provider, { value: scope, children });
1437
1556
  }
@@ -1491,7 +1610,7 @@ function GenerativeUI({
1491
1610
  onError: ownOnError
1492
1611
  }) {
1493
1612
  const provided = useContext2(GenuiContext);
1494
- const fallback2 = ownFallback ?? provided?.fallback;
1613
+ const fallback2 = ownFallback !== void 0 ? ownFallback : provided?.fallback;
1495
1614
  const loading = ownLoading ?? provided?.loading ?? null;
1496
1615
  const onError = ownOnError ?? provided?.onError;
1497
1616
  const options = useMergedOptions({
@@ -1516,6 +1635,8 @@ function GenerativeUI({
1516
1635
  GenerativeUIScope,
1517
1636
  {
1518
1637
  ...options,
1638
+ ...state.item.componentVersions !== void 0 ? { componentVersions: state.item.componentVersions } : {},
1639
+ ...state.item.fallbackText !== void 0 ? { fallbackText: state.item.fallbackText } : {},
1519
1640
  fallback: fallback2,
1520
1641
  loading,
1521
1642
  ...onError !== void 0 ? { onError } : {},
@@ -1531,6 +1652,7 @@ var AgentBackendContext = sharedContext(
1531
1652
  );
1532
1653
  function AgentProvider({
1533
1654
  backend,
1655
+ uiCapabilities,
1534
1656
  baseUrl,
1535
1657
  path,
1536
1658
  headers,
@@ -1562,7 +1684,7 @@ function AgentProvider({
1562
1684
  [backend, baseUrl, path, credentials, fetch, upload]
1563
1685
  );
1564
1686
  const content = genui !== void 0 ? /* @__PURE__ */ jsx2(GenuiProvider, { ...genui, children }) : children;
1565
- return /* @__PURE__ */ jsx2(AgentBackendContext.Provider, { value, children: content });
1687
+ return /* @__PURE__ */ jsx2(AgentUiCapabilitiesContext.Provider, { value: uiCapabilities ?? null, children: /* @__PURE__ */ jsx2(AgentBackendContext.Provider, { value, children: content }) });
1566
1688
  }
1567
1689
  var fallback;
1568
1690
  function defaultBackend() {
@@ -1573,6 +1695,12 @@ function useAgentBackend(own) {
1573
1695
  const provided = useContext3(AgentBackendContext);
1574
1696
  return own ?? provided ?? defaultBackend();
1575
1697
  }
1698
+ var AgentUiCapabilitiesContext = sharedContext(
1699
+ "@dudousxd/nestjs-agent-react:ui-capabilities"
1700
+ );
1701
+ function useAgentUiCapabilities() {
1702
+ return useContext3(AgentUiCapabilitiesContext) ?? void 0;
1703
+ }
1576
1704
 
1577
1705
  // src/config/use-agent-config.ts
1578
1706
  var shared = /* @__PURE__ */ new WeakMap();
@@ -1838,7 +1966,7 @@ async function defaultRefusal(response) {
1838
1966
  }
1839
1967
  function defaultForwardedProps(body) {
1840
1968
  const out = {};
1841
- for (const key of ["pageContext", "agent", "model"]) {
1969
+ for (const key of ["pageContext", "agent", "model", "persona", "uiCapabilities"]) {
1842
1970
  if (body[key] !== void 0 && body[key] !== null) out[key] = body[key];
1843
1971
  }
1844
1972
  return Object.keys(out).length > 0 ? out : void 0;
@@ -2155,7 +2283,7 @@ function applyPatch(target, patch) {
2155
2283
  }
2156
2284
 
2157
2285
  // src/catalog/use-agents.ts
2158
- import { useMemo as useMemo5 } from "react";
2286
+ import { useCallback as useCallback4, useMemo as useMemo5 } from "react";
2159
2287
 
2160
2288
  // src/catalog/use-resource.ts
2161
2289
  import { useCallback as useCallback3, useEffect as useEffect5, useRef as useRef4, useState as useState5 } from "react";
@@ -2197,16 +2325,24 @@ function useAgents(options = {}) {
2197
2325
  "",
2198
2326
  enabled
2199
2327
  );
2328
+ const find = useCallback4(
2329
+ (name) => (data ?? []).find(
2330
+ (agent) => name === void 0 ? agent.isDefault === true : agent.name === name
2331
+ ),
2332
+ [data]
2333
+ );
2200
2334
  return useMemo5(() => {
2201
2335
  const agents = data ?? [];
2202
2336
  return {
2203
2337
  agents,
2204
2338
  defaultAgent: agents.find((agent) => agent.isDefault === true)?.name ?? null,
2339
+ personasOf: (agent) => find(agent)?.personas ?? [],
2340
+ defaultPersonaOf: (agent) => find(agent)?.defaultPersona ?? null,
2205
2341
  isLoading,
2206
2342
  error,
2207
2343
  refresh
2208
2344
  };
2209
- }, [data, isLoading, error, refresh]);
2345
+ }, [data, find, isLoading, error, refresh]);
2210
2346
  }
2211
2347
 
2212
2348
  // src/catalog/use-models.ts
@@ -2304,7 +2440,7 @@ function useQuota(options = {}) {
2304
2440
  }
2305
2441
 
2306
2442
  // src/feedback/use-message-feedback.ts
2307
- import { useCallback as useCallback4, useRef as useRef5, useState as useState6 } from "react";
2443
+ import { useCallback as useCallback5, useRef as useRef5, useState as useState6 } from "react";
2308
2444
  function useMessageFeedback(options = {}) {
2309
2445
  const backend = useAgentBackend(options.backend);
2310
2446
  const latest = useRef5({ ...options, backend });
@@ -2315,7 +2451,7 @@ function useMessageFeedback(options = {}) {
2315
2451
  const localRef = useRef5(local);
2316
2452
  localRef.current = local;
2317
2453
  const resolved = useRef5(/* @__PURE__ */ new Map());
2318
- const feedbackOf = useCallback4(
2454
+ const feedbackOf = useCallback5(
2319
2455
  (message) => {
2320
2456
  const mine = local.get(message.id);
2321
2457
  if (mine !== void 0) return mine.feedback;
@@ -2323,8 +2459,8 @@ function useMessageFeedback(options = {}) {
2323
2459
  },
2324
2460
  [local]
2325
2461
  );
2326
- const isPending = useCallback4((message) => pending.has(message.id), [pending]);
2327
- const storedIdFor = useCallback4(async (message) => {
2462
+ const isPending = useCallback5((message) => pending.has(message.id), [pending]);
2463
+ const storedIdFor = useCallback5(async (message) => {
2328
2464
  const runId = metadataOf(message).runId;
2329
2465
  if (runId === void 0) return message.id;
2330
2466
  const known = resolved.current.get(message.id);
@@ -2342,7 +2478,7 @@ function useMessageFeedback(options = {}) {
2342
2478
  resolved.current.set(message.id, row.id);
2343
2479
  return row.id;
2344
2480
  }, []);
2345
- const rate = useCallback4(
2481
+ const rate = useCallback5(
2346
2482
  async (message, value, comment) => {
2347
2483
  const setFeedback = requireBackendMethod(latest.current.backend, "setMessageFeedback");
2348
2484
  const had = localRef.current.get(message.id);
@@ -2381,7 +2517,7 @@ function useMessageFeedback(options = {}) {
2381
2517
  },
2382
2518
  [storedIdFor]
2383
2519
  );
2384
- const toggle = useCallback4(
2520
+ const toggle = useCallback5(
2385
2521
  (message, value) => {
2386
2522
  const mine = localRef.current.get(message.id);
2387
2523
  const current = mine !== void 0 ? mine.feedback : metadataOf(message).feedback ?? null;
@@ -2397,7 +2533,7 @@ function metadataOf(message) {
2397
2533
  }
2398
2534
 
2399
2535
  // src/threads/use-threads.ts
2400
- import { useCallback as useCallback5, useEffect as useEffect7, useRef as useRef6, useState as useState7 } from "react";
2536
+ import { useCallback as useCallback6, useEffect as useEffect7, useRef as useRef6, useState as useState7 } from "react";
2401
2537
  function useThreads(options = {}) {
2402
2538
  const { enabled = true } = options;
2403
2539
  const backend = useAgentBackend(options.backend);
@@ -2407,7 +2543,7 @@ function useThreads(options = {}) {
2407
2543
  const threadsRef = useRef6(threads);
2408
2544
  threadsRef.current = threads;
2409
2545
  const generation = useRef6(0);
2410
- const refresh = useCallback5(async () => {
2546
+ const refresh = useCallback6(async () => {
2411
2547
  const mine = ++generation.current;
2412
2548
  setIsLoading(true);
2413
2549
  try {
@@ -2439,7 +2575,7 @@ function useThreads(options = {}) {
2439
2575
  }
2440
2576
  });
2441
2577
  }, [backend, enabled, refresh]);
2442
- const rename = useCallback5(
2578
+ const rename = useCallback6(
2443
2579
  async (id, title) => {
2444
2580
  const before = threadsRef.current;
2445
2581
  setThreads(
@@ -2455,7 +2591,7 @@ function useThreads(options = {}) {
2455
2591
  },
2456
2592
  [backend]
2457
2593
  );
2458
- const remove = useCallback5(
2594
+ const remove = useCallback6(
2459
2595
  async (id) => {
2460
2596
  const before = threadsRef.current;
2461
2597
  setThreads((current) => current.filter((thread) => thread.id !== id));
@@ -2514,7 +2650,7 @@ function backgroundRunsFromThread(messages) {
2514
2650
  import { useEffect as useEffect9, useState as useState9 } from "react";
2515
2651
 
2516
2652
  // src/components/use-speech-recognition.ts
2517
- import { useCallback as useCallback6, useEffect as useEffect8, useMemo as useMemo8, useRef as useRef7, useState as useState8 } from "react";
2653
+ import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo8, useRef as useRef7, useState as useState8 } from "react";
2518
2654
  function getRecognitionCtor() {
2519
2655
  if (typeof window === "undefined") return null;
2520
2656
  const scope = window;
@@ -2569,7 +2705,7 @@ function useSpeechRecognition({
2569
2705
  recognitionRef.current = null;
2570
2706
  };
2571
2707
  }, [ctor, lang]);
2572
- const start = useCallback6(() => {
2708
+ const start = useCallback7(() => {
2573
2709
  const recognition = recognitionRef.current;
2574
2710
  if (!recognition) return;
2575
2711
  try {
@@ -2579,7 +2715,7 @@ function useSpeechRecognition({
2579
2715
  console.warn("SpeechRecognition start failed", error);
2580
2716
  }
2581
2717
  }, []);
2582
- const stop = useCallback6(() => {
2718
+ const stop = useCallback7(() => {
2583
2719
  const recognition = recognitionRef.current;
2584
2720
  if (!recognition) return;
2585
2721
  try {
@@ -2699,7 +2835,7 @@ function ChatInput({
2699
2835
  import React2 from "react";
2700
2836
 
2701
2837
  // src/transcript/use-chat-transcript.ts
2702
- import { useCallback as useCallback8, useEffect as useEffect11, useRef as useRef9, useState as useState11 } from "react";
2838
+ import { useCallback as useCallback9, useEffect as useEffect11, useRef as useRef9, useState as useState11 } from "react";
2703
2839
 
2704
2840
  // src/transcript/model.ts
2705
2841
  import {
@@ -2879,6 +3015,15 @@ function isActionCall(part) {
2879
3015
  }
2880
3016
  function toolCallState(part) {
2881
3017
  const output = "output" in part ? part.output : void 0;
3018
+ if (output !== null && typeof output === "object" && "proposalId" in output && "executed" in output && output.executed === false)
3019
+ return {
3020
+ status: "awaiting-approval",
3021
+ isSettled: false,
3022
+ isFailed: false,
3023
+ isDenied: false,
3024
+ output,
3025
+ error: null
3026
+ };
2882
3027
  const errorText = "errorText" in part ? part.errorText : void 0;
2883
3028
  const outputError = output !== null && typeof output === "object" && "error" in output ? String(output.error) : void 0;
2884
3029
  const isDenied = part.state === "output-denied";
@@ -2923,7 +3068,11 @@ function describeToolCall(part, catalog, options = {}) {
2923
3068
  icon: presentation?.icon ?? null,
2924
3069
  tone: presentation?.tone ?? "neutral",
2925
3070
  detail: presentation?.detail ?? null,
2926
- confirm: confirm === void 0 ? null : {
3071
+ confirm: options.confirmation !== void 0 ? {
3072
+ title: options.confirmation.title,
3073
+ verb: options.confirmation.verb,
3074
+ detail: options.confirmation.detail ?? null
3075
+ } : confirm === void 0 ? null : {
2927
3076
  title: fillTemplate(confirm.title, input),
2928
3077
  verb: confirm.verb,
2929
3078
  detail: confirm.detail !== void 0 ? fillTemplate(confirm.detail, input) : null
@@ -2937,6 +3086,9 @@ function describeToolCall(part, catalog, options = {}) {
2937
3086
  }
2938
3087
  var STATUS_WEIGHT = {
2939
3088
  running: 4,
3089
+ queued: 4,
3090
+ executing: 4,
3091
+ succeeded: 0,
2940
3092
  "awaiting-approval": 3,
2941
3093
  failed: 2,
2942
3094
  denied: 1,
@@ -2956,7 +3108,7 @@ function groupToolActivity(calls, options = {}) {
2956
3108
  if (corrected.has(call.toolCallId)) continue;
2957
3109
  const presentation = options.catalog?.[call.name];
2958
3110
  const key = options.keyOf?.(call, presentation) ?? presentation?.label ?? call.name;
2959
- const status = toolCallState(call.part).status;
3111
+ const status = call.description.status;
2960
3112
  const phrase = phraseFor(
2961
3113
  presentation,
2962
3114
  call.part.input,
@@ -3198,7 +3350,7 @@ function isAncestor(call, candidate, byId) {
3198
3350
  }
3199
3351
  function buildToolCall(part, options, approval, catalog) {
3200
3352
  const toolCallId = part.toolCallId;
3201
- const awaiting = isAwaitingApproval(part);
3353
+ const awaiting = approval?.target?.kind === "proposal" ? approval.status === "pending" : isAwaitingApproval(part);
3202
3354
  const sending = awaiting ? options?.submitting(toolCallId) ?? null : null;
3203
3355
  return {
3204
3356
  part,
@@ -3208,19 +3360,30 @@ function buildToolCall(part, options, approval, catalog) {
3208
3360
  parentId: toolParentId(part),
3209
3361
  children: [],
3210
3362
  approval,
3211
- description: describeToolCall(part, catalog),
3363
+ description: proposalDescription(
3364
+ describeToolCall(
3365
+ part,
3366
+ catalog,
3367
+ approval?.confirmation === void 0 ? {} : { confirmation: approval.confirmation }
3368
+ ),
3369
+ approval
3370
+ ),
3212
3371
  isAwaitingApproval: awaiting,
3213
3372
  approve: {
3214
3373
  available: awaiting && options?.canApprove === true,
3215
3374
  isSubmitting: sending === "approve",
3216
3375
  // Read by field, not passed through: `onClick={call.approve.run}` hands this a click event,
3217
3376
  // and a host's handler must not receive that as its options.
3218
- run: (approveOptions) => approveOptions?.remember === true ? options?.approve(toolCallId, { remember: true }) : options?.approve(toolCallId)
3377
+ run: (approveOptions) => approval?.target === void 0 ? approveOptions?.remember === true ? options?.approve(toolCallId, { remember: true }) : options?.approve(toolCallId) : options?.approve(
3378
+ toolCallId,
3379
+ approveOptions?.remember === true ? { remember: true } : void 0,
3380
+ approval.target
3381
+ )
3219
3382
  },
3220
3383
  reject: {
3221
3384
  available: awaiting && options?.canReject === true,
3222
3385
  isSubmitting: sending === "reject",
3223
- run: () => options?.reject(toolCallId)
3386
+ run: () => approval?.target === void 0 ? options?.reject(toolCallId) : options?.reject(toolCallId, approval.target)
3224
3387
  },
3225
3388
  error: options?.errorOf(toolCallId) ?? null,
3226
3389
  errorCode: options?.errorCodeOf?.(toolCallId) ?? null
@@ -3370,7 +3533,7 @@ function buildElicitationBlock(key, part, request, options) {
3370
3533
  var UI_PART = "data-ui";
3371
3534
  var APPROVAL_PART = "data-approval-requested";
3372
3535
  var SETTLED_PART = "data-approval-settled";
3373
- var SETTLED_STATUSES = ["approved", "rejected", "expired"];
3536
+ var SETTLED_STATUSES = ["approved", "rejected", "expired", "superseded"];
3374
3537
  function readApprovals(parts) {
3375
3538
  const out = /* @__PURE__ */ new Map();
3376
3539
  const entry = (id) => {
@@ -3391,7 +3554,7 @@ function readApprovals(parts) {
3391
3554
  return found;
3392
3555
  };
3393
3556
  for (const part of parts) {
3394
- if (part.type !== APPROVAL_PART && part.type !== SETTLED_PART) {
3557
+ if (part.type !== APPROVAL_PART && part.type !== SETTLED_PART && part.type !== "data-action-proposal") {
3395
3558
  continue;
3396
3559
  }
3397
3560
  const data = part.data;
@@ -3399,12 +3562,35 @@ function readApprovals(parts) {
3399
3562
  continue;
3400
3563
  }
3401
3564
  const approval = entry(data.id);
3402
- if (part.type === APPROVAL_PART) {
3565
+ if (part.type === APPROVAL_PART || part.type === "data-action-proposal") {
3566
+ if (isRecord4(data.target) && data.target.kind === "proposal" && typeof data.target.proposalId === "string") {
3567
+ approval.target = {
3568
+ kind: "proposal",
3569
+ proposalId: data.target.proposalId,
3570
+ ...typeof data.target.threadId === "string" ? { threadId: data.target.threadId } : {}
3571
+ };
3572
+ }
3573
+ if (part.type === "data-action-proposal") {
3574
+ if (typeof data.status === "string" && ["pending", "approved", "rejected", "expired", "superseded"].includes(data.status))
3575
+ approval.status = data.status === "pending" ? null : data.status;
3576
+ if (data.executionStatus === null || data.executionStatus === "queued" || data.executionStatus === "executing" || data.executionStatus === "succeeded" || data.executionStatus === "failed")
3577
+ approval.executionStatus = data.executionStatus;
3578
+ approval.remember = data.remember === true;
3579
+ approval.decidedBy = typeof data.decidedBy === "string" ? data.decidedBy : null;
3580
+ approval.decidedVia = typeof data.decidedVia === "string" ? data.decidedVia : null;
3581
+ }
3403
3582
  if (typeof data.approver !== "string") {
3404
3583
  continue;
3405
3584
  }
3406
3585
  approval.approver = data.approver;
3407
3586
  approval.expiresAt = typeof data.expiresAt === "string" ? data.expiresAt : null;
3587
+ if (isRecord4(data.confirmation) && typeof data.confirmation.title === "string" && typeof data.confirmation.verb === "string" && (data.confirmation.detail === void 0 || typeof data.confirmation.detail === "string")) {
3588
+ approval.confirmation = {
3589
+ title: data.confirmation.title,
3590
+ verb: data.confirmation.verb,
3591
+ ...typeof data.confirmation.detail === "string" ? { detail: data.confirmation.detail } : {}
3592
+ };
3593
+ }
3408
3594
  approval.reason = typeof data.reason === "string" ? data.reason : null;
3409
3595
  continue;
3410
3596
  }
@@ -3428,9 +3614,12 @@ function approvalOf(parts, call) {
3428
3614
  }
3429
3615
  return {
3430
3616
  approver: parts.approver,
3617
+ ...parts.target === void 0 ? {} : { target: parts.target },
3618
+ ...parts.executionStatus === void 0 ? {} : { executionStatus: parts.executionStatus },
3619
+ ...parts.confirmation !== void 0 ? { confirmation: parts.confirmation } : {},
3431
3620
  expiresAt: parts.expiresAt,
3432
3621
  reason: parts.reason,
3433
- status: parts.status ?? statusFromPart(call),
3622
+ status: parts.status ?? (parts.target?.kind === "proposal" ? "pending" : statusFromPart(call)),
3434
3623
  remember: parts.remember,
3435
3624
  decidedBy: parts.decidedBy,
3436
3625
  decidedVia: parts.decidedVia,
@@ -3460,6 +3649,10 @@ function readUiComponent(part) {
3460
3649
  component: data.component,
3461
3650
  props: isRecord4(data.props) ? data.props : {},
3462
3651
  version: typeof data.version === "number" ? data.version : null,
3652
+ ...isRecord4(data.componentVersions) && Object.values(data.componentVersions).every(
3653
+ (version) => typeof version === "number" && Number.isSafeInteger(version) && version > 0
3654
+ ) ? { componentVersions: { ...data.componentVersions } } : {},
3655
+ ...typeof data.fallbackText === "string" ? { fallbackText: data.fallbackText } : {},
3463
3656
  toolCallId: typeof data.toolCallId === "string" ? data.toolCallId : null
3464
3657
  };
3465
3658
  }
@@ -3570,16 +3763,22 @@ function formatCostUsd(cost) {
3570
3763
  }
3571
3764
  return `$${cost.toFixed(2)}`;
3572
3765
  }
3766
+ function proposalDescription(description, approval) {
3767
+ if (approval?.target?.kind !== "proposal") return description;
3768
+ const status = approval.status === "pending" ? "awaiting-approval" : approval.status === "approved" ? approval.executionStatus ?? "queued" : "denied";
3769
+ const phrase = status === "awaiting-approval" ? "Awaiting approval" : status === "queued" ? "Queued" : status === "executing" ? "Executing" : status === "succeeded" ? "Succeeded" : status === "failed" ? "Failed" : approval.status === "expired" ? "Expired" : approval.status === "superseded" ? "Replaced" : "Rejected";
3770
+ return { ...description, status, phrase, result: null };
3771
+ }
3573
3772
 
3574
3773
  // src/transcript/use-stick-to-bottom.ts
3575
- import { useCallback as useCallback7, useEffect as useEffect10, useRef as useRef8, useState as useState10 } from "react";
3774
+ import { useCallback as useCallback8, useEffect as useEffect10, useRef as useRef8, useState as useState10 } from "react";
3576
3775
  function useStickToBottom({
3577
3776
  contentKey,
3578
3777
  threshold = 32
3579
3778
  }) {
3580
3779
  const containerRef = useRef8(null);
3581
3780
  const [isAtBottom, setIsAtBottom] = useState10(true);
3582
- const measure = useCallback7(() => {
3781
+ const measure = useCallback8(() => {
3583
3782
  const element = containerRef.current;
3584
3783
  if (!element) {
3585
3784
  return;
@@ -3587,7 +3786,7 @@ function useStickToBottom({
3587
3786
  const distance = element.scrollHeight - element.scrollTop - element.clientHeight;
3588
3787
  setIsAtBottom(distance <= threshold);
3589
3788
  }, [threshold]);
3590
- const scrollToBottom = useCallback7(() => {
3789
+ const scrollToBottom = useCallback8(() => {
3591
3790
  const element = containerRef.current;
3592
3791
  if (!element) {
3593
3792
  return;
@@ -3595,7 +3794,7 @@ function useStickToBottom({
3595
3794
  element.scrollTop = element.scrollHeight;
3596
3795
  setIsAtBottom(true);
3597
3796
  }, []);
3598
- const setContainer = useCallback7(
3797
+ const setContainer = useCallback8(
3599
3798
  (element) => {
3600
3799
  containerRef.current = element;
3601
3800
  if (element) {
@@ -3609,7 +3808,7 @@ function useStickToBottom({
3609
3808
  scrollToBottom();
3610
3809
  }
3611
3810
  }, [contentKey, isAtBottom, scrollToBottom]);
3612
- const getContainerProps = useCallback7(
3811
+ const getContainerProps = useCallback8(
3613
3812
  () => ({ ref: setContainer, onScroll: measure }),
3614
3813
  [setContainer, measure]
3615
3814
  );
@@ -3656,7 +3855,7 @@ function useChatTranscript(options) {
3656
3855
  streamingMessageId,
3657
3856
  options
3658
3857
  });
3659
- const stop = useCallback8(() => {
3858
+ const stop = useCallback9(() => {
3660
3859
  const current = latest.current;
3661
3860
  if (current.status !== "submitted" && current.status !== "streaming" && current.status !== "reconnecting") {
3662
3861
  return;
@@ -3664,13 +3863,13 @@ function useChatTranscript(options) {
3664
3863
  setStopRequested(true);
3665
3864
  void current.onStop?.();
3666
3865
  }, []);
3667
- const loadEarlier = useCallback8(
3866
+ const loadEarlier = useCallback9(
3668
3867
  () => setVisibleCount(
3669
3868
  (count) => count + (latest.current.loadEarlierStep ?? DEFAULT_LOAD_EARLIER_STEP)
3670
3869
  ),
3671
3870
  []
3672
3871
  );
3673
- const showAll = useCallback8(() => setVisibleCount(latest.current.messages.length), []);
3872
+ const showAll = useCallback9(() => setVisibleCount(latest.current.messages.length), []);
3674
3873
  const scroll = useStickToBottom({
3675
3874
  contentKey: contentSignature(messages),
3676
3875
  ...stickToBottomThreshold !== void 0 ? { threshold: stickToBottomThreshold } : {}
@@ -3708,7 +3907,7 @@ function useQueuedItems(queue, readOnly) {
3708
3907
  const latestQueue = useRef9(queue);
3709
3908
  latestQueue.current = queue;
3710
3909
  const removers = useRef9(/* @__PURE__ */ new Map());
3711
- const removerFor = useCallback8((id) => {
3910
+ const removerFor = useCallback9((id) => {
3712
3911
  let remover = removers.current.get(id);
3713
3912
  if (remover === void 0) {
3714
3913
  remover = () => {
@@ -3784,14 +3983,14 @@ function useTranscriptItems({
3784
3983
  );
3785
3984
  const callbackCache = useRef9(/* @__PURE__ */ new Map());
3786
3985
  const reasoningToggleCache = useRef9(/* @__PURE__ */ new Map());
3787
- const toggleReasoning = useCallback8((key, open) => {
3986
+ const toggleReasoning = useCallback9((key, open) => {
3788
3987
  setOpenReasoning((current) => {
3789
3988
  const next = new Map(current);
3790
3989
  next.set(key, open ?? !(current.get(key) ?? false));
3791
3990
  return next;
3792
3991
  });
3793
3992
  }, []);
3794
- const stableToggle = useCallback8(
3993
+ const stableToggle = useCallback9(
3795
3994
  (key) => {
3796
3995
  let cached = reasoningToggleCache.current.get(key);
3797
3996
  if (!cached) {
@@ -3802,10 +4001,10 @@ function useTranscriptItems({
3802
4001
  },
3803
4002
  [toggleReasoning]
3804
4003
  );
3805
- const pick = useCallback8((toolCallId, questionId, values) => {
4004
+ const pick = useCallback9((toolCallId, questionId, values) => {
3806
4005
  setPicks((current) => new Map(current).set(pickKey(toolCallId, questionId), values));
3807
4006
  }, []);
3808
- const settle = useCallback8(
4007
+ const settle = useCallback9(
3809
4008
  (toolCallId, action, send) => {
3810
4009
  setSettleErrors((current) => {
3811
4010
  if (!current.has(toolCallId)) {
@@ -3837,7 +4036,7 @@ function useTranscriptItems({
3837
4036
  },
3838
4037
  []
3839
4038
  );
3840
- const answer = useCallback8(
4039
+ const answer = useCallback9(
3841
4040
  (toolCallId) => {
3842
4041
  const current = latest.current;
3843
4042
  const onAnswer = current.handlers.onAnswer;
@@ -3852,7 +4051,7 @@ function useTranscriptItems({
3852
4051
  },
3853
4052
  [settle]
3854
4053
  );
3855
- const skip = useCallback8(
4054
+ const skip = useCallback9(
3856
4055
  (toolCallId) => {
3857
4056
  const onSkip = latest.current.handlers.onSkip;
3858
4057
  if (onSkip) {
@@ -3861,29 +4060,37 @@ function useTranscriptItems({
3861
4060
  },
3862
4061
  [settle]
3863
4062
  );
3864
- const approve = useCallback8(
3865
- (toolCallId, options2) => {
4063
+ const approve = useCallback9(
4064
+ (toolCallId, options2, target) => {
3866
4065
  const onApprove = latest.current.handlers.onApprove;
3867
4066
  if (onApprove) {
3868
4067
  settle(
3869
4068
  toolCallId,
3870
4069
  "approve",
3871
- () => onApprove({ toolCallId, ...options2?.remember === true ? { remember: true } : {} })
4070
+ () => onApprove({
4071
+ toolCallId,
4072
+ ...target === void 0 ? {} : { target },
4073
+ ...options2?.remember === true ? { remember: true } : {}
4074
+ })
3872
4075
  );
3873
4076
  }
3874
4077
  },
3875
4078
  [settle]
3876
4079
  );
3877
- const reject = useCallback8(
3878
- (toolCallId) => {
4080
+ const reject = useCallback9(
4081
+ (toolCallId, target) => {
3879
4082
  const onReject = latest.current.handlers.onReject;
3880
4083
  if (onReject) {
3881
- settle(toolCallId, "reject", () => onReject({ toolCallId }));
4084
+ settle(
4085
+ toolCallId,
4086
+ "reject",
4087
+ () => onReject({ toolCallId, ...target === void 0 ? {} : { target } })
4088
+ );
3882
4089
  }
3883
4090
  },
3884
4091
  [settle]
3885
4092
  );
3886
- const callbacksFor = useCallback8((id) => {
4093
+ const callbacksFor = useCallback9((id) => {
3887
4094
  const cached = callbackCache.current.get(id);
3888
4095
  if (cached) {
3889
4096
  return cached;
@@ -4076,14 +4283,36 @@ function settleHandlers(options, backend) {
4076
4283
  ),
4077
4284
  onApprove: pick(
4078
4285
  options.onApprove,
4079
- backend.approveToolCall ? async (input) => {
4080
- await backend.approveToolCall?.(input);
4286
+ backend.approveToolCall || backend.approveActionProposal ? async (input) => {
4287
+ if (input.target?.kind === "proposal") {
4288
+ if (input.target.threadId === void 0)
4289
+ throw new Error("A proposal decision requires its thread scope");
4290
+ await requireBackendMethod(
4291
+ backend,
4292
+ "approveActionProposal"
4293
+ )({
4294
+ threadId: input.target.threadId,
4295
+ proposalId: input.target.proposalId,
4296
+ ...input.remember === void 0 ? {} : { remember: input.remember }
4297
+ });
4298
+ } else await requireBackendMethod(backend, "approveToolCall")(input);
4081
4299
  } : void 0
4082
4300
  ),
4083
4301
  onReject: pick(
4084
4302
  options.onReject,
4085
- backend.rejectToolCall ? async (input) => {
4086
- await backend.rejectToolCall?.(input);
4303
+ backend.rejectToolCall || backend.rejectActionProposal ? async (input) => {
4304
+ if (input.target?.kind === "proposal") {
4305
+ if (input.target.threadId === void 0)
4306
+ throw new Error("A proposal decision requires its thread scope");
4307
+ await requireBackendMethod(
4308
+ backend,
4309
+ "rejectActionProposal"
4310
+ )({
4311
+ threadId: input.target.threadId,
4312
+ proposalId: input.target.proposalId,
4313
+ ...input.reason === void 0 ? {} : { reason: input.reason }
4314
+ });
4315
+ } else await requireBackendMethod(backend, "rejectToolCall")(input);
4087
4316
  } : void 0
4088
4317
  )
4089
4318
  };
@@ -4608,7 +4837,7 @@ function toItem(entry) {
4608
4837
  }
4609
4838
 
4610
4839
  // src/composer/use-composer-autocomplete.ts
4611
- import { useCallback as useCallback9, useEffect as useEffect12, useId, useLayoutEffect, useMemo as useMemo9, useRef as useRef10, useState as useState12 } from "react";
4840
+ import { useCallback as useCallback10, useEffect as useEffect12, useId, useLayoutEffect, useMemo as useMemo9, useRef as useRef10, useState as useState12 } from "react";
4612
4841
  function useComposerAutocomplete(options) {
4613
4842
  const { value, sources, acceptOnTab = true } = options;
4614
4843
  const latest = useRef10(options);
@@ -4683,22 +4912,22 @@ function useComposerAutocomplete(options) {
4683
4912
  const activeItem = isOpen ? items[activeIndex] ?? null : null;
4684
4913
  const reactId = useId();
4685
4914
  const listboxId = options.id ?? `agent-autocomplete-${reactId}`;
4686
- const optionId = useCallback9((index) => `${listboxId}-option-${index}`, [listboxId]);
4687
- const highlight = useCallback9((index) => {
4915
+ const optionId = useCallback10((index) => `${listboxId}-option-${index}`, [listboxId]);
4916
+ const highlight = useCallback10((index) => {
4688
4917
  const total = itemsRef.current.length;
4689
4918
  if (total === 0) return;
4690
4919
  setActiveIndex((index % total + total) % total);
4691
4920
  }, []);
4692
- const moveHighlight = useCallback9(
4921
+ const moveHighlight = useCallback10(
4693
4922
  (delta) => highlight(activeIndexRef.current + delta),
4694
4923
  [highlight]
4695
4924
  );
4696
- const dismiss = useCallback9(() => {
4925
+ const dismiss = useCallback10(() => {
4697
4926
  const current = matchRef.current;
4698
4927
  if (current) setDismissedIndex(current.index);
4699
4928
  }, []);
4700
4929
  const pendingCaret = useRef10(null);
4701
- const accept = useCallback9((item) => {
4930
+ const accept = useCallback10((item) => {
4702
4931
  const current = matchRef.current;
4703
4932
  if (!current) return;
4704
4933
  const chosen = item ?? itemsRef.current[activeIndexRef.current];
@@ -4715,10 +4944,10 @@ function useComposerAutocomplete(options) {
4715
4944
  pendingCaret.current = null;
4716
4945
  inputRef.current?.setSelectionRange(position, position);
4717
4946
  });
4718
- const readCaret = useCallback9((element) => {
4947
+ const readCaret = useCallback10((element) => {
4719
4948
  setCaret(element.selectionStart ?? element.value.length);
4720
4949
  }, []);
4721
- const onKeyDown = useCallback9(
4950
+ const onKeyDown = useCallback10(
4722
4951
  (event) => {
4723
4952
  if (!isOpen) return;
4724
4953
  const hasItems = itemsRef.current.length > 0;
@@ -4753,7 +4982,7 @@ function useComposerAutocomplete(options) {
4753
4982
  },
4754
4983
  [isOpen, acceptOnTab, accept, dismiss, moveHighlight]
4755
4984
  );
4756
- const onChange = useCallback9(
4985
+ const onChange = useCallback10(
4757
4986
  (event) => {
4758
4987
  readCaret(event.target);
4759
4988
  const next = findActiveTrigger(
@@ -4767,18 +4996,18 @@ function useComposerAutocomplete(options) {
4767
4996
  },
4768
4997
  [readCaret]
4769
4998
  );
4770
- const onSelect = useCallback9(
4999
+ const onSelect = useCallback10(
4771
5000
  (event) => readCaret(event.currentTarget),
4772
5001
  [readCaret]
4773
5002
  );
4774
- const onClick = useCallback9(
5003
+ const onClick = useCallback10(
4775
5004
  (event) => readCaret(event.currentTarget),
4776
5005
  [readCaret]
4777
5006
  );
4778
- const setInput = useCallback9((element) => {
5007
+ const setInput = useCallback10((element) => {
4779
5008
  inputRef.current = element;
4780
5009
  }, []);
4781
- const getInputProps = useCallback9(
5010
+ const getInputProps = useCallback10(
4782
5011
  () => ({
4783
5012
  ref: setInput,
4784
5013
  onChange,
@@ -4805,7 +5034,7 @@ function useComposerAutocomplete(options) {
4805
5034
  optionId
4806
5035
  ]
4807
5036
  );
4808
- const getListboxProps = useCallback9(
5037
+ const getListboxProps = useCallback10(
4809
5038
  () => ({
4810
5039
  id: listboxId,
4811
5040
  role: "listbox",
@@ -4813,7 +5042,7 @@ function useComposerAutocomplete(options) {
4813
5042
  }),
4814
5043
  [listboxId, match]
4815
5044
  );
4816
- const getOptionProps = useCallback9(
5045
+ const getOptionProps = useCallback10(
4817
5046
  (index) => ({
4818
5047
  id: optionId(index),
4819
5048
  role: "option",
@@ -4846,7 +5075,7 @@ function isPromise(value) {
4846
5075
 
4847
5076
  // src/presentation/use-tool-catalog.ts
4848
5077
  import { ALL_AGENTS } from "@dudousxd/nestjs-agent-core";
4849
- import { useCallback as useCallback10, useEffect as useEffect13, useMemo as useMemo10, useState as useState13 } from "react";
5078
+ import { useCallback as useCallback11, useEffect as useEffect13, useMemo as useMemo10, useState as useState13 } from "react";
4850
5079
  var shared2 = /* @__PURE__ */ new WeakMap();
4851
5080
  function load2(client, agent) {
4852
5081
  let byAgent = shared2.get(client);
@@ -4898,7 +5127,7 @@ function useToolCatalog(options = {}) {
4898
5127
  cancelled = true;
4899
5128
  };
4900
5129
  }, [client, agent, enabled, generation]);
4901
- const refresh = useCallback10(() => {
5130
+ const refresh = useCallback11(() => {
4902
5131
  shared2.get(client)?.delete(agent ?? "");
4903
5132
  setGeneration((current) => current + 1);
4904
5133
  }, [client, agent]);
@@ -4988,6 +5217,8 @@ function storedMessageToUiMessage(message) {
4988
5217
  id: component.id,
4989
5218
  component: component.component,
4990
5219
  props: component.props,
5220
+ ...component.componentVersions !== void 0 ? { componentVersions: component.componentVersions } : {},
5221
+ ...component.fallbackText !== void 0 ? { fallbackText: component.fallbackText } : {},
4991
5222
  ...component.version !== void 0 ? { version: component.version } : {},
4992
5223
  ...component.toolCallId !== void 0 ? { toolCallId: component.toolCallId } : {}
4993
5224
  }
@@ -5007,6 +5238,8 @@ function storedMessageToUiMessage(message) {
5007
5238
  data: {
5008
5239
  id: approval.toolCallId,
5009
5240
  approver: approval.approver,
5241
+ ...approval.target !== void 0 ? { target: approval.target } : approval.proposalId !== void 0 ? { target: { kind: "proposal", proposalId: approval.proposalId } } : {},
5242
+ ...approval.confirmation !== void 0 ? { confirmation: approval.confirmation } : {},
5010
5243
  ...approval.expiresAt !== void 0 ? { expiresAt: approval.expiresAt } : {}
5011
5244
  }
5012
5245
  });
@@ -5063,8 +5296,11 @@ function storedMessageToUiMessage(message) {
5063
5296
  const metadata = {
5064
5297
  // The host's own facts first: the library's keys win on a clash.
5065
5298
  ...message.metadata,
5299
+ ...message.actionProposalOutcome === void 0 ? {} : { actionProposalOutcomeId: message.actionProposalOutcome.id },
5066
5300
  ...message.feedback !== void 0 ? { feedback: message.feedback } : {},
5067
5301
  ...message.createdAt ? { createdAt: message.createdAt } : {},
5302
+ ...message.agentName !== void 0 ? { agentName: message.agentName } : {},
5303
+ ...message.persona !== void 0 ? { persona: message.persona } : {},
5068
5304
  ...message.usage !== void 0 ? {
5069
5305
  usage: {
5070
5306
  inputTokens: message.usage.inputTokens,
@@ -5096,6 +5332,8 @@ function storedThreadToUiMessages(messages) {
5096
5332
  return turns.map(mergeTurn);
5097
5333
  }
5098
5334
  function sameRun(previous, next) {
5335
+ if (previous.actionProposalOutcome !== void 0 || next.actionProposalOutcome !== void 0)
5336
+ return false;
5099
5337
  return previous.runId === void 0 || next.runId === void 0 || previous.runId === next.runId;
5100
5338
  }
5101
5339
  function mergeTurn(rows) {
@@ -5115,7 +5353,9 @@ function mergeTurn(rows) {
5115
5353
  ...Object.assign({}, ...rows.map((row) => row.metadata ?? {})),
5116
5354
  ...usage !== void 0 ? { usage } : {},
5117
5355
  ...feedback !== void 0 ? { feedback } : {},
5118
- ...createdAt ? { createdAt } : {}
5356
+ ...createdAt ? { createdAt } : {},
5357
+ ...lastRow.agentName !== void 0 ? { agentName: lastRow.agentName } : {},
5358
+ ...lastRow.persona !== void 0 ? { persona: lastRow.persona } : {}
5119
5359
  };
5120
5360
  return {
5121
5361
  id: lastRow.id,
@@ -5141,7 +5381,162 @@ function sumUsage(rows) {
5141
5381
 
5142
5382
  // src/use-agent-chat.ts
5143
5383
  import { useChat } from "@ai-sdk/react";
5144
- import { useCallback as useCallback11, useEffect as useEffect15, useMemo as useMemo11, useRef as useRef12, useState as useState15 } from "react";
5384
+ import { useCallback as useCallback13, useEffect as useEffect16, useMemo as useMemo11, useRef as useRef13, useState as useState16 } from "react";
5385
+
5386
+ // src/approvals/use-action-proposals.ts
5387
+ import { useCallback as useCallback12, useEffect as useEffect15, useRef as useRef12, useState as useState15 } from "react";
5388
+
5389
+ // src/approvals/proposals.ts
5390
+ function proposalNeedsPolling(proposal) {
5391
+ return proposal.decision === "pending" || proposal.execution?.status === "queued" || proposal.execution?.status === "executing" || proposal.outcomeDelivery?.status === "pending";
5392
+ }
5393
+ function reconcileProposalMessages(current, proposals, facts) {
5394
+ const messages = current.map((message) => {
5395
+ const relevant = proposals.filter(
5396
+ (proposal) => message.parts.some((part) => {
5397
+ if ("data" in part && record(part.data) && record(part.data.target) && part.data.target.kind === "proposal")
5398
+ return part.data.target.proposalId === proposal.id;
5399
+ if ("toolCallId" in part && part.toolCallId === proposal.originToolCallId) {
5400
+ if ("output" in part && record(part.output) && typeof part.output.proposalId === "string")
5401
+ return part.output.proposalId === proposal.id;
5402
+ return record(message.metadata) && message.metadata.runId === proposal.originRunId;
5403
+ }
5404
+ return false;
5405
+ })
5406
+ );
5407
+ if (relevant.length === 0) return message;
5408
+ const ids = new Set(relevant.map((proposal) => proposal.originToolCallId));
5409
+ const parts = message.parts.filter(
5410
+ (part) => !(part.type === "data-action-proposal" && "id" in part && typeof part.id === "string" && ids.has(part.id))
5411
+ );
5412
+ for (const proposal of relevant)
5413
+ parts.push({
5414
+ type: "data-action-proposal",
5415
+ id: proposal.originToolCallId,
5416
+ data: {
5417
+ id: proposal.originToolCallId,
5418
+ target: { kind: "proposal", proposalId: proposal.id, threadId: proposal.threadId },
5419
+ approver: proposal.approver,
5420
+ confirmation: proposal.confirmation,
5421
+ expiresAt: proposal.expiresAt === null ? null : Math.abs(proposal.expiresAt) <= 864e13 ? new Date(proposal.expiresAt).toISOString() : null,
5422
+ status: proposal.decision,
5423
+ executionStatus: proposal.execution?.status ?? null,
5424
+ remember: proposal.decisionAudit?.remember === true,
5425
+ decidedBy: proposal.decisionAudit?.actorRef ?? null,
5426
+ decidedVia: proposal.decisionAudit?.via ?? null,
5427
+ reason: proposal.decisionAudit?.reason ?? null
5428
+ }
5429
+ });
5430
+ return { ...message, parts };
5431
+ });
5432
+ const knownMessages = new Set(messages.map((message) => message.id));
5433
+ const knownOutcomes = new Set(
5434
+ messages.map((message) => outcomeId(message.metadata)).filter(Boolean)
5435
+ );
5436
+ for (const fact of facts) {
5437
+ const id = fact.actionProposalOutcome?.id;
5438
+ if (id === void 0 || knownMessages.has(fact.id) || knownOutcomes.has(id)) continue;
5439
+ const message = storedMessageToUiMessage(fact);
5440
+ messages.push({
5441
+ ...message,
5442
+ metadata: {
5443
+ ...record(message.metadata) ? message.metadata : {},
5444
+ actionProposalOutcomeId: id
5445
+ }
5446
+ });
5447
+ knownMessages.add(fact.id);
5448
+ knownOutcomes.add(id);
5449
+ }
5450
+ return messages;
5451
+ }
5452
+ function record(value) {
5453
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5454
+ }
5455
+ function outcomeId(value) {
5456
+ return record(value) && typeof value.actionProposalOutcomeId === "string" ? value.actionProposalOutcomeId : void 0;
5457
+ }
5458
+
5459
+ // src/approvals/use-action-proposals.ts
5460
+ function useActionProposals(backend, threadId, setMessages, refreshKey, pollMs = 1e3) {
5461
+ const [items, setItems] = useState15([]);
5462
+ const [error, setError] = useState15(null);
5463
+ const latest = useRef12({ threadId, setMessages });
5464
+ latest.current = { threadId, setMessages };
5465
+ const mounted = useRef12(true);
5466
+ const requests = useRef12(/* @__PURE__ */ new Map());
5467
+ useEffect15(() => {
5468
+ mounted.current = true;
5469
+ return () => {
5470
+ mounted.current = false;
5471
+ };
5472
+ }, []);
5473
+ const refresh = useCallback12(async () => {
5474
+ if (threadId === void 0 || backend.listActionProposals === void 0) return;
5475
+ const pending = requests.current.get(threadId);
5476
+ if (pending) return pending;
5477
+ const request = (async () => {
5478
+ try {
5479
+ const proposals = await backend.listActionProposals?.({ threadId });
5480
+ if (!mounted.current || latest.current.threadId !== threadId || !Array.isArray(proposals))
5481
+ return;
5482
+ const admitted = proposals.some(
5483
+ (proposal) => proposal.outcomeDelivery?.status === "admitted"
5484
+ );
5485
+ const facts = admitted ? (await backend.getThread(threadId)).messages.filter(
5486
+ (message) => message.actionProposalOutcome !== void 0
5487
+ ) : [];
5488
+ if (!mounted.current || latest.current.threadId !== threadId) return;
5489
+ setItems(proposals);
5490
+ setError(null);
5491
+ latest.current.setMessages(
5492
+ (current) => reconcileProposalMessages(current, proposals, facts)
5493
+ );
5494
+ } catch (cause) {
5495
+ if (mounted.current && latest.current.threadId === threadId)
5496
+ setError(cause instanceof Error ? cause : new Error(String(cause)));
5497
+ }
5498
+ })();
5499
+ requests.current.set(threadId, request);
5500
+ try {
5501
+ await request;
5502
+ } finally {
5503
+ requests.current.delete(threadId);
5504
+ }
5505
+ }, [backend, threadId]);
5506
+ useEffect15(() => {
5507
+ setItems([]);
5508
+ setError(null);
5509
+ }, [threadId]);
5510
+ useEffect15(() => {
5511
+ void refresh();
5512
+ }, [refresh, refreshKey]);
5513
+ useEffect15(() => {
5514
+ if (!items.some(proposalNeedsPolling) || pollMs <= 0) return;
5515
+ const timer = setInterval(() => {
5516
+ void refresh();
5517
+ }, pollMs);
5518
+ return () => clearInterval(timer);
5519
+ }, [items, pollMs, refresh]);
5520
+ useEffect15(() => {
5521
+ const refetch = () => {
5522
+ void refresh();
5523
+ };
5524
+ const visible = () => {
5525
+ if (document.visibilityState === "visible") refetch();
5526
+ };
5527
+ window.addEventListener("focus", refetch);
5528
+ window.addEventListener("online", refetch);
5529
+ document.addEventListener("visibilitychange", visible);
5530
+ return () => {
5531
+ window.removeEventListener("focus", refetch);
5532
+ window.removeEventListener("online", refetch);
5533
+ document.removeEventListener("visibilitychange", visible);
5534
+ };
5535
+ }, [refresh]);
5536
+ return { items, error, refresh };
5537
+ }
5538
+
5539
+ // src/use-agent-chat.ts
5145
5540
  var EMPTY_QUEUE = { items: [], paused: null };
5146
5541
  var localQueueSequence = 0;
5147
5542
  function queuedUserMessage(turn) {
@@ -5196,13 +5591,16 @@ function localChatId() {
5196
5591
  return `local-chat-${localChatSequence}`;
5197
5592
  }
5198
5593
  function useAgentChat(options = {}) {
5199
- const latest = useRef12(options);
5594
+ const providerCapabilities = useAgentUiCapabilities();
5595
+ const capabilitiesRef = useRef13(providerCapabilities);
5596
+ capabilitiesRef.current = providerCapabilities;
5597
+ const latest = useRef13(options);
5200
5598
  latest.current = options;
5201
5599
  const resolved = useAgentBackend(options.backend);
5202
- const client = useRef12(resolved).current;
5203
- const createdThreadId = useRef12(void 0);
5204
- const chatIdRef = useRef12(options.threadId ?? localChatId());
5205
- const boundThreadRef = useRef12(options.threadId);
5600
+ const client = useRef13(resolved).current;
5601
+ const createdThreadId = useRef13(void 0);
5602
+ const chatIdRef = useRef13(options.threadId ?? localChatId());
5603
+ const boundThreadRef = useRef13(options.threadId);
5206
5604
  if (options.threadId !== boundThreadRef.current) {
5207
5605
  boundThreadRef.current = options.threadId;
5208
5606
  const adopting = options.threadId !== void 0 && options.threadId === createdThreadId.current;
@@ -5212,39 +5610,43 @@ function useAgentChat(options = {}) {
5212
5610
  }
5213
5611
  }
5214
5612
  const chatId = chatIdRef.current;
5215
- const [runId, setRunId] = useState15(options.resumeRunId);
5216
- const runIdRef = useRef12(runId);
5613
+ const [runId, setRunId] = useState16(options.resumeRunId);
5614
+ const runIdRef = useRef13(runId);
5217
5615
  runIdRef.current = runId;
5218
- const settlingRunIdRef = useRef12(void 0);
5219
- const autoResumeRunIdRef = useRef12(void 0);
5220
- const [activeRunId, setActiveRunId] = useState15(null);
5221
- const [historyFor, setHistoryFor] = useState15(void 0);
5222
- const [historyError, setHistoryError] = useState15(null);
5223
- const [runError, setRunError] = useState15(null);
5224
- const [threadModel, setThreadModel] = useState15(null);
5225
- const [pickedModel, setPickedModel] = useState15(void 0);
5226
- const pickedModelRef = useRef12(pickedModel);
5616
+ const settlingRunIdRef = useRef13(void 0);
5617
+ const autoResumeRunIdRef = useRef13(void 0);
5618
+ const [activeRunId, setActiveRunId] = useState16(null);
5619
+ const [historyFor, setHistoryFor] = useState16(void 0);
5620
+ const [historyError, setHistoryError] = useState16(null);
5621
+ const [runError, setRunError] = useState16(null);
5622
+ const [threadModel, setThreadModel] = useState16(null);
5623
+ const [threadPersona, setThreadPersona] = useState16(null);
5624
+ const [pickedModel, setPickedModel] = useState16(void 0);
5625
+ const pickedModelRef = useRef13(pickedModel);
5227
5626
  pickedModelRef.current = pickedModel;
5228
- const lockedRef = useRef12(false);
5229
- const pendingPin = useRef12(void 0);
5230
- const regenerateNext = useRef12(false);
5231
- const currentThreadId = useCallback11(
5627
+ const lockedRef = useRef13(false);
5628
+ const pendingPin = useRef13(void 0);
5629
+ const regenerateNext = useRef13(false);
5630
+ const currentThreadId = useCallback13(
5232
5631
  () => latest.current.threadId ?? createdThreadId.current,
5233
5632
  []
5234
5633
  );
5235
- const [queueState, setQueueState] = useState15(EMPTY_QUEUE);
5236
- const queueStateRef = useRef12(queueState);
5237
- const applyQueue = useCallback11((next) => {
5634
+ const [queueState, setQueueState] = useState16(EMPTY_QUEUE);
5635
+ const queueStateRef = useRef13(queueState);
5636
+ const applyQueue = useCallback13((next) => {
5238
5637
  queueStateRef.current = next;
5239
5638
  setQueueState(next);
5240
5639
  }, []);
5241
- const [pendingSends, setPendingSends] = useState15([]);
5242
- const [queueError, setQueueError] = useState15(null);
5243
- const handoff = useRef12(void 0);
5244
- const queuedSend = useRef12(void 0);
5245
- const threadWaiters = useRef12([]);
5246
- const turnBody = useCallback11(() => {
5640
+ const [pendingSends, setPendingSends] = useState16([]);
5641
+ const [queueError, setQueueError] = useState16(null);
5642
+ const handoff = useRef13(void 0);
5643
+ const queuedSend = useRef13(void 0);
5644
+ const proposalRefreshRef = useRef13(async () => {
5645
+ });
5646
+ const threadWaiters = useRef13([]);
5647
+ const turnBody = useCallback13(() => {
5247
5648
  const current = latest.current;
5649
+ if (current.persona !== void 0) setThreadPersona(current.persona);
5248
5650
  const pageContext = current.getPageContext?.() ?? null;
5249
5651
  const threadId = currentThreadId();
5250
5652
  const model = current.model ?? (lockedRef.current ? void 0 : pickedModelRef.current);
@@ -5252,13 +5654,15 @@ function useAgentChat(options = {}) {
5252
5654
  // Read per send: a host that switches agents before the first message (a picker on a new
5253
5655
  // chat) sends the one picked now, not the one this chat mounted with.
5254
5656
  ...current.agent !== void 0 ? { agent: current.agent } : {},
5657
+ ...current.persona !== void 0 ? { persona: current.persona } : {},
5255
5658
  ...threadId !== void 0 ? { threadId } : {},
5256
5659
  ...model !== void 0 ? { model } : {},
5257
- ...pageContext ? { pageContext } : {}
5660
+ ...pageContext ? { pageContext } : {},
5661
+ ...(current.uiCapabilities ?? capabilitiesRef.current) === void 0 ? {} : { uiCapabilities: current.uiCapabilities ?? capabilitiesRef.current }
5258
5662
  };
5259
5663
  }, [currentThreadId]);
5260
- const [connection, setConnection] = useState15({ status: "live" });
5261
- const resyncAfterSettle = useRef12(false);
5664
+ const [connection, setConnection] = useState16({ status: "live" });
5665
+ const resyncAfterSettle = useRef13(false);
5262
5666
  const transport = useMemo11(() => {
5263
5667
  function onMeta(meta) {
5264
5668
  settlingRunIdRef.current = meta.runId;
@@ -5311,7 +5715,7 @@ function useAgentChat(options = {}) {
5311
5715
  }
5312
5716
  });
5313
5717
  }, []);
5314
- const backgroundRef = useRef12(() => Promise.resolve());
5718
+ const backgroundRef = useRef13(() => Promise.resolve());
5315
5719
  const chat = useChat({
5316
5720
  transport,
5317
5721
  id: chatId,
@@ -5319,6 +5723,7 @@ function useAgentChat(options = {}) {
5319
5723
  ...options.initialMessages !== void 0 ? { messages: options.initialMessages } : {},
5320
5724
  onData: (part) => {
5321
5725
  latest.current.onData?.(part);
5726
+ if (part.type === "data-proposal-decision") void proposalRefreshRef.current();
5322
5727
  if (part.type === "data-queue") {
5323
5728
  onQueueData(part.data);
5324
5729
  }
@@ -5365,7 +5770,7 @@ function useAgentChat(options = {}) {
5365
5770
  }
5366
5771
  }
5367
5772
  });
5368
- const chatRef = useRef12(chat);
5773
+ const chatRef = useRef13(chat);
5369
5774
  chatRef.current = chat;
5370
5775
  function onQueueData(data) {
5371
5776
  const frame = data ?? {};
@@ -5413,13 +5818,14 @@ function useAgentChat(options = {}) {
5413
5818
  } catch {
5414
5819
  }
5415
5820
  }
5416
- const firstChatId = useRef12(chatId);
5417
- useEffect15(() => {
5821
+ const firstChatId = useRef13(chatId);
5822
+ useEffect16(() => {
5418
5823
  if (chatId === firstChatId.current) return;
5419
5824
  firstChatId.current = chatId;
5420
5825
  setRunId(void 0);
5421
5826
  setActiveRunId(null);
5422
5827
  setThreadModel(null);
5828
+ setThreadPersona(null);
5423
5829
  setPickedModel(void 0);
5424
5830
  setBackgroundRuns([]);
5425
5831
  setHistoryError(null);
@@ -5432,7 +5838,7 @@ function useAgentChat(options = {}) {
5432
5838
  handoff.current = void 0;
5433
5839
  queuedSend.current = void 0;
5434
5840
  }, [chatId]);
5435
- useEffect15(() => {
5841
+ useEffect16(() => {
5436
5842
  const threadId = options.threadId;
5437
5843
  if (threadId === void 0) return;
5438
5844
  if (threadId === createdThreadId.current) return;
@@ -5448,6 +5854,7 @@ function useAgentChat(options = {}) {
5448
5854
  const stored = Array.isArray(thread.messages) ? thread.messages : [];
5449
5855
  setActiveRunId(active);
5450
5856
  setThreadModel(thread.model ?? null);
5857
+ setThreadPersona(thread.persona ?? null);
5451
5858
  setHistoryError(null);
5452
5859
  const queue2 = thread.queue ?? EMPTY_QUEUE;
5453
5860
  applyQueue(queue2);
@@ -5478,8 +5885,8 @@ function useAgentChat(options = {}) {
5478
5885
  };
5479
5886
  }, [client, chatId, options.threadId]);
5480
5887
  const isLoadingHistory = options.threadId !== void 0 && options.threadId !== createdThreadId.current && options.history !== false && options.initialMessages === void 0 && historyFor !== options.threadId;
5481
- const turnInFlight = useRef12(false);
5482
- const isTurnInFlight = useCallback11(
5888
+ const turnInFlight = useRef13(false);
5889
+ const isTurnInFlight = useCallback13(
5483
5890
  () => turnInFlight.current || transport.isAttemptLive,
5484
5891
  [transport]
5485
5892
  );
@@ -5488,20 +5895,20 @@ function useAgentChat(options = {}) {
5488
5895
  enabled: options.quota !== false && typeof client.getQuota === "function"
5489
5896
  });
5490
5897
  const blocked = options.blocked !== void 0 ? options.blocked : quota.blocked;
5491
- const blockedRef = useRef12(blocked);
5898
+ const blockedRef = useRef13(blocked);
5492
5899
  blockedRef.current = blocked;
5493
5900
  const whileRunning = typeof client.enqueueMessage === "function" ? options.whileRunning ?? "queue" : "block";
5494
- const whileRunningRef = useRef12(whileRunning);
5901
+ const whileRunningRef = useRef13(whileRunning);
5495
5902
  whileRunningRef.current = whileRunning;
5496
- const canQueueRef = useRef12(false);
5903
+ const canQueueRef = useRef13(false);
5497
5904
  canQueueRef.current = typeof client.enqueueMessage === "function";
5498
- const threadForQueue = useCallback11(async () => {
5905
+ const threadForQueue = useCallback13(async () => {
5499
5906
  const known = currentThreadId();
5500
5907
  if (known !== void 0) return known;
5501
5908
  if (!isTurnInFlight()) throw new Error("queue: this chat has no thread yet");
5502
5909
  return new Promise((resolve) => threadWaiters.current.push(resolve));
5503
5910
  }, [currentThreadId, isTurnInFlight]);
5504
- const queueCall = useCallback11(async (run) => {
5911
+ const queueCall = useCallback13(async (run) => {
5505
5912
  try {
5506
5913
  const result = await run();
5507
5914
  setQueueError(null);
@@ -5511,7 +5918,7 @@ function useAgentChat(options = {}) {
5511
5918
  throw error;
5512
5919
  }
5513
5920
  }, []);
5514
- const enqueue = useCallback11(
5921
+ const enqueue = useCallback13(
5515
5922
  async (text2, {
5516
5923
  attachments = [],
5517
5924
  mode = "queue"
@@ -5559,7 +5966,7 @@ function useAgentChat(options = {}) {
5559
5966
  },
5560
5967
  [client, queueCall, threadForQueue, turnBody, applyQueue, isTurnInFlight]
5561
5968
  );
5562
- const sendMessage = useCallback11(
5969
+ const sendMessage = useCallback13(
5563
5970
  async (message, options2) => {
5564
5971
  const block = blockedRef.current;
5565
5972
  if (block != null) throw new QuotaBlockedError(block);
@@ -5583,7 +5990,7 @@ function useAgentChat(options = {}) {
5583
5990
  },
5584
5991
  [isTurnInFlight, enqueue]
5585
5992
  );
5586
- const removeQueued = useCallback11(
5993
+ const removeQueued = useCallback13(
5587
5994
  async (id) => {
5588
5995
  const previous = queueStateRef.current;
5589
5996
  applyQueue({ ...previous, items: previous.items.filter((item) => item.id !== id) });
@@ -5596,7 +6003,7 @@ function useAgentChat(options = {}) {
5596
6003
  },
5597
6004
  [client, queueCall, applyQueue]
5598
6005
  );
5599
- const interruptQueued = useCallback11(
6006
+ const interruptQueued = useCallback13(
5600
6007
  async (id) => {
5601
6008
  const previous = queueStateRef.current;
5602
6009
  const moving = previous.items.find((item) => item.id === id);
@@ -5631,7 +6038,7 @@ function useAgentChat(options = {}) {
5631
6038
  },
5632
6039
  [client, queueCall, applyQueue, isTurnInFlight]
5633
6040
  );
5634
- const editQueued = useCallback11(
6041
+ const editQueued = useCallback13(
5635
6042
  async (id, text2) => {
5636
6043
  applyQueue(
5637
6044
  await queueCall(
@@ -5641,7 +6048,7 @@ function useAgentChat(options = {}) {
5641
6048
  },
5642
6049
  [client, queueCall, applyQueue]
5643
6050
  );
5644
- const moveQueued = useCallback11(
6051
+ const moveQueued = useCallback13(
5645
6052
  async (id, index) => {
5646
6053
  const previous = queueStateRef.current;
5647
6054
  const moving = previous.items.find((item) => item.id === id);
@@ -5663,12 +6070,12 @@ function useAgentChat(options = {}) {
5663
6070
  },
5664
6071
  [client, queueCall, applyQueue]
5665
6072
  );
5666
- const clearQueue = useCallback11(async () => {
6073
+ const clearQueue = useCallback13(async () => {
5667
6074
  const threadId = currentThreadId();
5668
6075
  if (threadId === void 0) return;
5669
6076
  applyQueue(await queueCall(() => requireBackendMethod(client, "clearQueue")(threadId)));
5670
6077
  }, [client, queueCall, currentThreadId, applyQueue]);
5671
- const resumeQueue = useCallback11(async () => {
6078
+ const resumeQueue = useCallback13(async () => {
5672
6079
  const threadId = currentThreadId();
5673
6080
  if (threadId === void 0) return;
5674
6081
  const before = queueStateRef.current;
@@ -5686,7 +6093,7 @@ function useAgentChat(options = {}) {
5686
6093
  else attachTo(turn);
5687
6094
  }
5688
6095
  }, [client, queueCall, currentThreadId, applyQueue, isTurnInFlight]);
5689
- const resumeQueueRef = useRef12(resumeQueue);
6096
+ const resumeQueueRef = useRef13(resumeQueue);
5690
6097
  resumeQueueRef.current = resumeQueue;
5691
6098
  const queuedItems = [
5692
6099
  ...pendingSends.filter((item) => item.interrupt),
@@ -5716,7 +6123,7 @@ function useAgentChat(options = {}) {
5716
6123
  resume: resumeQueue,
5717
6124
  error: queueError
5718
6125
  };
5719
- const addToolResult = useCallback11(
6126
+ const addToolResult = useCallback13(
5720
6127
  ({ tool, toolCallId, output }) => chatRef.current.addToolResult({
5721
6128
  tool,
5722
6129
  toolCallId,
@@ -5724,8 +6131,8 @@ function useAgentChat(options = {}) {
5724
6131
  }),
5725
6132
  []
5726
6133
  );
5727
- const [backgroundRuns, setBackgroundRuns] = useState15([]);
5728
- const refreshBackground = useCallback11(async () => {
6134
+ const [backgroundRuns, setBackgroundRuns] = useState16([]);
6135
+ const refreshBackground = useCallback13(async () => {
5729
6136
  const threadId = currentThreadId();
5730
6137
  if (threadId === void 0) {
5731
6138
  return;
@@ -5745,13 +6152,13 @@ function useAgentChat(options = {}) {
5745
6152
  }, [client, currentThreadId]);
5746
6153
  backgroundRef.current = refreshBackground;
5747
6154
  const isWorking = backgroundRuns.some((run) => run.status === "running");
5748
- useEffect15(() => {
6155
+ useEffect16(() => {
5749
6156
  if (options.background !== true || options.threadId === void 0) {
5750
6157
  return;
5751
6158
  }
5752
6159
  void refreshBackground();
5753
6160
  }, [options.background, options.threadId, chatId, refreshBackground]);
5754
- useEffect15(() => {
6161
+ useEffect16(() => {
5755
6162
  const every = latest.current.backgroundPollMs ?? 5e3;
5756
6163
  if (latest.current.background !== true || !isWorking || every <= 0) {
5757
6164
  return;
@@ -5759,7 +6166,15 @@ function useAgentChat(options = {}) {
5759
6166
  const timer = setInterval(() => void refreshBackground(), every);
5760
6167
  return () => clearInterval(timer);
5761
6168
  }, [isWorking, refreshBackground]);
5762
- const cancel = useCallback11(async () => {
6169
+ const proposals = useActionProposals(
6170
+ client,
6171
+ options.proposals === false || options.proposals !== true && options.history === false && options.resume === false ? void 0 : currentThreadId(),
6172
+ (update) => chatRef.current.setMessages(update),
6173
+ `${chat.status}:${connection.status}`,
6174
+ options.proposalPollMs
6175
+ );
6176
+ proposalRefreshRef.current = proposals.refresh;
6177
+ const cancel = useCallback13(async () => {
5763
6178
  chatRef.current.stop();
5764
6179
  const active = runIdRef.current;
5765
6180
  if (active) {
@@ -5776,8 +6191,23 @@ function useAgentChat(options = {}) {
5776
6191
  }
5777
6192
  }
5778
6193
  }, [client, currentThreadId, applyQueue]);
5779
- const approve = useCallback11(
5780
- async ({ toolCallId, remember, via }) => {
6194
+ const approve = useCallback13(
6195
+ async ({ toolCallId, target, remember, via }) => {
6196
+ if (target?.kind === "proposal") {
6197
+ const threadId = target.threadId ?? currentThreadId();
6198
+ if (threadId === void 0)
6199
+ throw new Error("A proposal decision requires its thread scope");
6200
+ await requireBackendMethod(
6201
+ client,
6202
+ "approveActionProposal"
6203
+ )({
6204
+ threadId,
6205
+ proposalId: target.proposalId,
6206
+ ...remember === true ? { remember: true } : {}
6207
+ });
6208
+ await proposals.refresh();
6209
+ return;
6210
+ }
5781
6211
  await requireBackendMethod(
5782
6212
  client,
5783
6213
  "approveToolCall"
@@ -5787,10 +6217,25 @@ function useAgentChat(options = {}) {
5787
6217
  ...via !== void 0 ? { via } : {}
5788
6218
  });
5789
6219
  },
5790
- [client]
6220
+ [client, currentThreadId, proposals.refresh]
5791
6221
  );
5792
- const reject = useCallback11(
5793
- async ({ toolCallId, reason, via }) => {
6222
+ const reject = useCallback13(
6223
+ async ({ toolCallId, target, reason, via }) => {
6224
+ if (target?.kind === "proposal") {
6225
+ const threadId = target.threadId ?? currentThreadId();
6226
+ if (threadId === void 0)
6227
+ throw new Error("A proposal decision requires its thread scope");
6228
+ await requireBackendMethod(
6229
+ client,
6230
+ "rejectActionProposal"
6231
+ )({
6232
+ threadId,
6233
+ proposalId: target.proposalId,
6234
+ ...reason === void 0 ? {} : { reason }
6235
+ });
6236
+ await proposals.refresh();
6237
+ return;
6238
+ }
5794
6239
  await requireBackendMethod(
5795
6240
  client,
5796
6241
  "rejectToolCall"
@@ -5800,9 +6245,9 @@ function useAgentChat(options = {}) {
5800
6245
  ...via !== void 0 ? { via } : {}
5801
6246
  });
5802
6247
  },
5803
- [client]
6248
+ [client, currentThreadId, proposals.refresh]
5804
6249
  );
5805
- const answer = useCallback11(
6250
+ const answer = useCallback13(
5806
6251
  async ({ toolCallId, answers, via }) => {
5807
6252
  await requireBackendMethod(
5808
6253
  client,
@@ -5815,7 +6260,7 @@ function useAgentChat(options = {}) {
5815
6260
  },
5816
6261
  [client]
5817
6262
  );
5818
- const skip = useCallback11(
6263
+ const skip = useCallback13(
5819
6264
  async ({ toolCallId, via }) => {
5820
6265
  await requireBackendMethod(
5821
6266
  client,
@@ -5824,7 +6269,7 @@ function useAgentChat(options = {}) {
5824
6269
  },
5825
6270
  [client]
5826
6271
  );
5827
- const regenerate = useCallback11(
6272
+ const regenerate = useCallback13(
5828
6273
  (_input) => {
5829
6274
  const block = blockedRef.current;
5830
6275
  if (block != null) throw new QuotaBlockedError(block);
@@ -5837,7 +6282,7 @@ function useAgentChat(options = {}) {
5837
6282
  },
5838
6283
  [isTurnInFlight]
5839
6284
  );
5840
- const storedIdOf = useCallback11(
6285
+ const storedIdOf = useCallback13(
5841
6286
  async (messageId, threadId) => {
5842
6287
  const message = chatRef.current.messages.find((candidate) => candidate.id === messageId);
5843
6288
  const runOf = message?.metadata?.runId;
@@ -5848,7 +6293,7 @@ function useAgentChat(options = {}) {
5848
6293
  },
5849
6294
  [client]
5850
6295
  );
5851
- const fork = useCallback11(
6296
+ const fork = useCallback13(
5852
6297
  async ({
5853
6298
  messageId,
5854
6299
  threadId
@@ -5863,7 +6308,7 @@ function useAgentChat(options = {}) {
5863
6308
  },
5864
6309
  [client, currentThreadId, storedIdOf]
5865
6310
  );
5866
- const truncateFrom = useCallback11(
6311
+ const truncateFrom = useCallback13(
5867
6312
  async ({ messageId, threadId }) => {
5868
6313
  const target = threadId ?? currentThreadId();
5869
6314
  if (target === void 0) throw new Error("truncateFrom: this chat has no thread yet");
@@ -5872,7 +6317,7 @@ function useAgentChat(options = {}) {
5872
6317
  },
5873
6318
  [client, currentThreadId, storedIdOf]
5874
6319
  );
5875
- const promote = useCallback11(
6320
+ const promote = useCallback13(
5876
6321
  async ({ threadId } = {}) => {
5877
6322
  const target = threadId ?? currentThreadId();
5878
6323
  if (target === void 0) throw new Error("promote: this chat has no thread yet");
@@ -5881,24 +6326,24 @@ function useAgentChat(options = {}) {
5881
6326
  },
5882
6327
  [client, currentThreadId]
5883
6328
  );
5884
- const [modelsWanted, setModelsWanted] = useState15(false);
5885
- const modelsWantedRef = useRef12(modelsWanted);
6329
+ const [modelsWanted, setModelsWanted] = useState16(false);
6330
+ const modelsWantedRef = useRef13(modelsWanted);
5886
6331
  modelsWantedRef.current = modelsWanted;
5887
6332
  const catalog = useModels({
5888
6333
  backend: client,
5889
6334
  ...options.agent !== void 0 ? { agent: options.agent } : {},
5890
6335
  enabled: modelsWanted && typeof client.listModels === "function"
5891
6336
  });
5892
- const wantModels = useCallback11(() => {
6337
+ const wantModels = useCallback13(() => {
5893
6338
  if (modelsWantedRef.current) return;
5894
6339
  modelsWantedRef.current = true;
5895
6340
  queueMicrotask(() => setModelsWanted(true));
5896
6341
  }, []);
5897
6342
  lockedRef.current = catalog.locked !== null;
5898
- const selectModel = useCallback11((id) => {
6343
+ const selectModel = useCallback13((id) => {
5899
6344
  if (!lockedRef.current) setPickedModel(id);
5900
6345
  }, []);
5901
- const pinToThread = useCallback11(
6346
+ const pinToThread = useCallback13(
5902
6347
  async (model) => {
5903
6348
  const threadId = currentThreadId();
5904
6349
  setThreadModel(model);
@@ -5938,14 +6383,14 @@ function useAgentChat(options = {}) {
5938
6383
  };
5939
6384
  const status = connection.status === "reconnecting" ? "reconnecting" : chat.status;
5940
6385
  const isBusy = status === "submitted" || status === "streaming" || status === "reconnecting";
5941
- const isBusyRef = useRef12(isBusy);
6386
+ const isBusyRef = useRef13(isBusy);
5942
6387
  isBusyRef.current = isBusy;
5943
6388
  const files = useAttachments({ ...options.composer, backend: client });
5944
- const [text, setText] = useState15("");
6389
+ const [text, setText] = useState16("");
5945
6390
  const blockedBy = blocked != null ? "quota" : files.isUploading ? "uploading" : isBusy && whileRunning === "block" ? "busy" : text.trim().length === 0 && files.refs.length === 0 ? "empty" : null;
5946
- const composerRef = useRef12({ text, files, blockedBy });
6391
+ const composerRef = useRef13({ text, files, blockedBy });
5947
6392
  composerRef.current = { text, files, blockedBy };
5948
- const submit = useCallback11(
6393
+ const submit = useCallback13(
5949
6394
  async (options2) => {
5950
6395
  const current = composerRef.current;
5951
6396
  const asked = canQueueRef.current ? options2?.mode : void 0;
@@ -6027,6 +6472,7 @@ function useAgentChat(options = {}) {
6027
6472
  addToolResult,
6028
6473
  /** Sub-agents this conversation started and did not wait for. */
6029
6474
  background,
6475
+ proposals,
6030
6476
  runId,
6031
6477
  /** The loaded thread's running turn, or `null` once read with none. */
6032
6478
  activeRunId,
@@ -6059,6 +6505,11 @@ function useAgentChat(options = {}) {
6059
6505
  queue,
6060
6506
  /** The model picker: `list`, `selected`, `select(id)`, `pinToThread(id)`. */
6061
6507
  models,
6508
+ /**
6509
+ * The persona pinned on this thread (`ThreadSummary.persona`), or `null` when none is — what a
6510
+ * persona picker on a reopened thread starts from (`?? agent.defaultPersona`).
6511
+ */
6512
+ threadPersona,
6062
6513
  /** The caller's budget (`GET <base>/quota`); `quota.blocked` gates sends. */
6063
6514
  quota,
6064
6515
  /** The window blocking sends right now (the `blocked` option, else the quota report's). */
@@ -6145,8 +6596,10 @@ export {
6145
6596
  onThreadsEvent,
6146
6597
  persistSpeechLang,
6147
6598
  phraseFor,
6599
+ proposalNeedsPolling,
6148
6600
  readPath,
6149
6601
  readReasoningMs,
6602
+ reconcileProposalMessages,
6150
6603
  reframeAgUiStream,
6151
6604
  requireBackendMethod,
6152
6605
  resolveResultView,
@@ -6154,6 +6607,7 @@ export {
6154
6607
  storedThreadToUiMessages,
6155
6608
  toolCallState,
6156
6609
  toolCatalogFrom,
6610
+ useActionProposals,
6157
6611
  useAgentBackend,
6158
6612
  useAgentChat,
6159
6613
  useAgentConfig,