@agents24/chat-react 0.2.0 → 0.3.1

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.cjs CHANGED
@@ -81,12 +81,13 @@ __export(index_exports, {
81
81
  useMessageScroller: () => import_message_scroller2.useMessageScroller,
82
82
  useMessageScrollerScrollable: () => import_message_scroller2.useMessageScrollerScrollable,
83
83
  useMessageScrollerVisibility: () => import_message_scroller2.useMessageScrollerVisibility,
84
- useStreamingText: () => useStreamingText
84
+ useStreamingText: () => useStreamingText,
85
+ waitForMcpOauthComplete: () => waitForMcpOauthComplete
85
86
  });
86
87
  module.exports = __toCommonJS(index_exports);
87
88
 
88
89
  // src/controller.ts
89
- var import_react2 = require("react");
90
+ var import_react4 = require("react");
90
91
 
91
92
  // src/controller-actions.ts
92
93
  var import_react = require("react");
@@ -160,6 +161,47 @@ function useControllerMessageActions(input) {
160
161
  return { handleCopy, handleDislike, handleLike, handleRetry, startNewThread, upsertLiveVoiceMessage };
161
162
  }
162
163
 
164
+ // src/controller-hitl.ts
165
+ var import_react2 = require("react");
166
+ function useControllerHitl({
167
+ messages,
168
+ transport,
169
+ activeRunId,
170
+ activeRunIdRef,
171
+ activeThreadIdRef,
172
+ runStream
173
+ }) {
174
+ const [isResolvingHitl, setIsResolvingHitl] = (0, import_react2.useState)(false);
175
+ const pendingHitl = (0, import_react2.useMemo)(() => {
176
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
177
+ const parts = messages[messageIndex]?.parts || [];
178
+ for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) {
179
+ const part = parts[partIndex];
180
+ if (part?.kind === "hitl" && part.status === "pending") return part;
181
+ }
182
+ }
183
+ return null;
184
+ }, [messages]);
185
+ const resumeHitl = (0, import_react2.useCallback)(async (input) => {
186
+ if (!transport.resumeHitl) throw new Error("HITL resume is not configured.");
187
+ const runId = input.runId || activeRunIdRef.current || activeRunId;
188
+ if (!runId) throw new Error("No paused run is available to resume.");
189
+ setIsResolvingHitl(true);
190
+ try {
191
+ const result = await transport.resumeHitl({ ...input, runId });
192
+ const threadId = result.thread_id || activeThreadIdRef.current;
193
+ if (threadId) await runStream({ mode: "attach", runId: result.run_id || runId, threadId });
194
+ return result;
195
+ } finally {
196
+ setIsResolvingHitl(false);
197
+ }
198
+ }, [activeRunId, activeRunIdRef, activeThreadIdRef, runStream, transport]);
199
+ return { pendingHitl, isResolvingHitl, resumeHitl };
200
+ }
201
+
202
+ // src/controller-thread-events.ts
203
+ var import_react3 = require("react");
204
+
163
205
  // src/context-window.ts
164
206
  var SOURCE_PRIORITY = {
165
207
  unknown: 0,
@@ -344,6 +386,22 @@ var assistantTextFromEvents = (events) => {
344
386
  };
345
387
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
346
388
  var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
389
+ var HITL_ACTIONS_BY_KIND = {
390
+ tool_review: ["approve", "reject"],
391
+ mcp_auth: ["connect", "skip"],
392
+ user_approval: ["approve", "reject"],
393
+ app_data_permission: ["approve", "reject"]
394
+ };
395
+ var HITL_STATUSES = ["pending", "resolved", "expired", "cancelled", "invalidated"];
396
+ function exactHitlActions(kind, value) {
397
+ if (!Array.isArray(value)) throw new Error("Invalid V2 HITL response block.");
398
+ const actions = value.map((item) => String(item));
399
+ const expected = HITL_ACTIONS_BY_KIND[kind];
400
+ if (actions.length !== expected.length || actions.some((action, index) => action !== expected[index])) {
401
+ throw new Error("Invalid V2 HITL response block.");
402
+ }
403
+ return actions;
404
+ }
347
405
  var toolStateFromStatus = (status) => {
348
406
  const normalized = String(status || "").trim().toLowerCase();
349
407
  if (["failed", "error"].includes(normalized)) return "output-error";
@@ -444,13 +502,39 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
444
502
  }
445
503
  if (block.kind === "hitl_request") {
446
504
  const hitl = asRecord(block.hitl) || block;
505
+ if (hitl.schema_version !== "agents24.hitl.interrupt.v2") {
506
+ throw new Error("Invalid V2 HITL response block.");
507
+ }
508
+ const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
509
+ const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
510
+ const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
511
+ if (!interruptId || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind)) || !HITL_STATUSES.includes(status)) {
512
+ throw new Error("Invalid V2 HITL response block.");
513
+ }
514
+ const kind = hitlKind;
515
+ const allowedActions = exactHitlActions(kind, hitl.allowed_actions);
516
+ const resolution = asRecord(block.resolution);
517
+ const resolver = asRecord(resolution?.resolver);
447
518
  parts.push({
448
519
  id,
449
520
  type: "hitl",
450
521
  kind: "hitl",
451
- hitl,
452
- interruptId: optionalString(block.interruptId) || optionalString(hitl.interrupt_id) || null,
453
- hitlKind: optionalString(block.hitlKind) || optionalString(hitl.kind) || null,
522
+ interruptId,
523
+ hitlKind: kind,
524
+ message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
525
+ allowedActions,
526
+ status,
527
+ presentation: asRecord(hitl.presentation) || {},
528
+ resolution: resolution ? {
529
+ action: ["approve", "reject", "connect", "skip"].includes(String(resolution.action)) ? String(resolution.action) : null,
530
+ outcome: optionalString(resolution.outcome),
531
+ reason: optionalString(resolution.reason),
532
+ resolvedAt: optionalString(resolution.resolved_at),
533
+ resolver: resolver ? {
534
+ principalType: optionalString(resolver.principal_type) || optionalString(resolver.principalType) || null,
535
+ principalId: optionalString(resolver.principal_id) || optionalString(resolver.principalId) || null
536
+ } : null
537
+ } : null,
454
538
  raw: block
455
539
  });
456
540
  return;
@@ -690,6 +774,47 @@ var applyThreadSummaryEvent = (currentThreads, event) => {
690
774
  return sortByActivity(next);
691
775
  };
692
776
 
777
+ // src/controller-thread-events.ts
778
+ function useControllerThreadEvents({
779
+ transport,
780
+ storage,
781
+ refresh,
782
+ setThreads
783
+ }) {
784
+ const cursorRef = (0, import_react3.useRef)(null);
785
+ (0, import_react3.useEffect)(() => {
786
+ if (!transport.subscribeThreadEvents) return;
787
+ let cancelled = false;
788
+ let retryTimeout = null;
789
+ let controller = null;
790
+ const connect = () => {
791
+ if (cancelled) return;
792
+ controller = new AbortController();
793
+ transport.subscribeThreadEvents?.(
794
+ { cursor: cursorRef.current, signal: controller.signal },
795
+ async (event) => {
796
+ if (typeof event.cursor === "number") cursorRef.current = event.cursor;
797
+ if (event.event === "snapshot_required") {
798
+ await refresh().catch(() => void 0);
799
+ return;
800
+ }
801
+ storage.setThreads(applyThreadSummaryEvent(storage.listThreads(), event));
802
+ setThreads(storage.listThreads());
803
+ }
804
+ ).catch((error) => {
805
+ if (cancelled || isAbortError(error)) return;
806
+ retryTimeout = setTimeout(connect, 1500);
807
+ });
808
+ };
809
+ connect();
810
+ return () => {
811
+ cancelled = true;
812
+ if (retryTimeout) clearTimeout(retryTimeout);
813
+ controller?.abort();
814
+ };
815
+ }, [refresh, setThreads, storage, transport]);
816
+ }
817
+
693
818
  // src/message-lifecycle.ts
694
819
  function findStableAssistantMessageIndex(messages, input) {
695
820
  return messages.findIndex(
@@ -728,83 +853,83 @@ function upsertStableAssistantMessage(messages, input) {
728
853
  }
729
854
 
730
855
  // src/controller.ts
731
- function useAgents24ChatController({
732
- transport,
733
- storage,
734
- activeThreadId: controlledActiveThreadId,
735
- pageSize = DEFAULT_THREAD_PAGE_SIZE,
736
- storageKey,
737
- createId = createChatId,
738
- onActiveThreadIdChange,
739
- onSourceClick,
740
- onStreamErrorMessage,
741
- onRuntimeEvent,
742
- onThreadDetailLoaded
743
- }) {
856
+ function useAgents24ChatController(options) {
857
+ const {
858
+ transport,
859
+ storage,
860
+ activeThreadId: controlledActiveThreadId,
861
+ pageSize = DEFAULT_THREAD_PAGE_SIZE,
862
+ storageKey,
863
+ createId = createChatId,
864
+ onActiveThreadIdChange,
865
+ onSourceClick,
866
+ onStreamErrorMessage,
867
+ onRuntimeEvent,
868
+ onThreadDetailLoaded
869
+ } = options;
744
870
  const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
745
871
  const initialCached = activeThreadId ? storage.getThread(activeThreadId) : void 0;
746
- const [messages, setMessages] = (0, import_react2.useState)(() => initialCached?.messages || []);
747
- const [isLoading, setIsLoading] = (0, import_react2.useState)(false);
748
- const [isLoadingHistory, setIsLoadingHistory] = (0, import_react2.useState)(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
749
- const [isLoadingOlder, setIsLoadingOlder] = (0, import_react2.useState)(false);
750
- const [hasOlderTurns, setHasOlderTurns] = (0, import_react2.useState)(Boolean(initialCached?.hasOlderTurns));
751
- const [streamingContent, setStreamingContent] = (0, import_react2.useState)("");
752
- const [streamingMessageId, setStreamingMessageId] = (0, import_react2.useState)(null);
753
- const [contextStatus, setContextStatus] = (0, import_react2.useState)(null);
754
- const [currentReasoning, setCurrentReasoning] = (0, import_react2.useState)([]);
755
- const [liked, setLiked] = (0, import_react2.useState)({});
756
- const [disliked, setDisliked] = (0, import_react2.useState)({});
757
- const [copiedMessageId, setCopiedMessageId] = (0, import_react2.useState)(null);
758
- const [lastThinkingDurationMs, setLastThinkingDurationMs] = (0, import_react2.useState)(null);
759
- const [threads, setThreads] = (0, import_react2.useState)(() => storage.listThreads());
760
- const [isRefreshingThreads, setIsRefreshingThreads] = (0, import_react2.useState)(false);
761
- const [isSelectingThread, setIsSelectingThread] = (0, import_react2.useState)(false);
762
- const [activeRunId, setActiveRunId] = (0, import_react2.useState)(null);
763
- const textareaRef = (0, import_react2.useRef)(null);
764
- const activeThreadIdRef = (0, import_react2.useRef)(activeThreadId);
765
- const messagesRef = (0, import_react2.useRef)(messages);
766
- const nextBeforeTurnIndexRef = (0, import_react2.useRef)(initialCached?.nextBeforeTurnIndex ?? null);
767
- const hasOlderTurnsRef = (0, import_react2.useRef)(Boolean(initialCached?.hasOlderTurns));
768
- const isLoadingOlderRef = (0, import_react2.useRef)(false);
769
- const loadedThreadIdRef = (0, import_react2.useRef)(initialCached?.messages?.length ? activeThreadId : null);
770
- const requestSeqRef = (0, import_react2.useRef)(0);
771
- const isLoadingHistoryRef = (0, import_react2.useRef)(isLoadingHistory);
772
- const activeRunIdRef = (0, import_react2.useRef)(null);
773
- const reattachedRunIdRef = (0, import_react2.useRef)(null);
774
- const abortControllerRef = (0, import_react2.useRef)(null);
775
- const [lifecycleAbortController] = (0, import_react2.useState)(() => new AbortController());
776
- const streamingContentRef = (0, import_react2.useRef)("");
777
- const streamingMessageIdRef = (0, import_react2.useRef)(null);
778
- const reasoningRef = (0, import_react2.useRef)([]);
779
- const liveVoiceIdsRef = (0, import_react2.useRef)({});
780
- const refreshSeqRef = (0, import_react2.useRef)(0);
781
- const threadEventsCursorRef = (0, import_react2.useRef)(null);
782
- const setActiveRunIdValue = (0, import_react2.useCallback)((runId) => {
872
+ const [messages, setMessages] = (0, import_react4.useState)(() => initialCached?.messages || []);
873
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
874
+ const [isLoadingHistory, setIsLoadingHistory] = (0, import_react4.useState)(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
875
+ const [isLoadingOlder, setIsLoadingOlder] = (0, import_react4.useState)(false);
876
+ const [hasOlderTurns, setHasOlderTurns] = (0, import_react4.useState)(Boolean(initialCached?.hasOlderTurns));
877
+ const [streamingContent, setStreamingContent] = (0, import_react4.useState)("");
878
+ const [streamingMessageId, setStreamingMessageId] = (0, import_react4.useState)(null);
879
+ const [contextStatus, setContextStatus] = (0, import_react4.useState)(null);
880
+ const [currentReasoning, setCurrentReasoning] = (0, import_react4.useState)([]);
881
+ const [liked, setLiked] = (0, import_react4.useState)({});
882
+ const [disliked, setDisliked] = (0, import_react4.useState)({});
883
+ const [copiedMessageId, setCopiedMessageId] = (0, import_react4.useState)(null);
884
+ const [lastThinkingDurationMs, setLastThinkingDurationMs] = (0, import_react4.useState)(null);
885
+ const [threads, setThreads] = (0, import_react4.useState)(() => storage.listThreads());
886
+ const [isRefreshingThreads, setIsRefreshingThreads] = (0, import_react4.useState)(false);
887
+ const [isSelectingThread, setIsSelectingThread] = (0, import_react4.useState)(false);
888
+ const [activeRunId, setActiveRunId] = (0, import_react4.useState)(null);
889
+ const textareaRef = (0, import_react4.useRef)(null);
890
+ const activeThreadIdRef = (0, import_react4.useRef)(activeThreadId);
891
+ const messagesRef = (0, import_react4.useRef)(messages);
892
+ const nextBeforeTurnIndexRef = (0, import_react4.useRef)(initialCached?.nextBeforeTurnIndex ?? null);
893
+ const hasOlderTurnsRef = (0, import_react4.useRef)(Boolean(initialCached?.hasOlderTurns));
894
+ const isLoadingOlderRef = (0, import_react4.useRef)(false);
895
+ const loadedThreadIdRef = (0, import_react4.useRef)(initialCached?.messages?.length ? activeThreadId : null);
896
+ const requestSeqRef = (0, import_react4.useRef)(0);
897
+ const isLoadingHistoryRef = (0, import_react4.useRef)(isLoadingHistory);
898
+ const activeRunIdRef = (0, import_react4.useRef)(null);
899
+ const reattachedRunIdRef = (0, import_react4.useRef)(null);
900
+ const abortControllerRef = (0, import_react4.useRef)(null);
901
+ const [lifecycleAbortController] = (0, import_react4.useState)(() => new AbortController());
902
+ const streamingContentRef = (0, import_react4.useRef)("");
903
+ const streamingMessageIdRef = (0, import_react4.useRef)(null);
904
+ const reasoningRef = (0, import_react4.useRef)([]);
905
+ const liveVoiceIdsRef = (0, import_react4.useRef)({});
906
+ const refreshSeqRef = (0, import_react4.useRef)(0);
907
+ const setActiveRunIdValue = (0, import_react4.useCallback)((runId) => {
783
908
  activeRunIdRef.current = runId;
784
909
  setActiveRunId(runId);
785
910
  }, []);
786
- const syncThreadsFromStorage = (0, import_react2.useCallback)(() => {
911
+ const syncThreadsFromStorage = (0, import_react4.useCallback)(() => {
787
912
  setThreads(storage.listThreads());
788
913
  }, [storage]);
789
- const upsertStoredThread = (0, import_react2.useCallback)(
914
+ const upsertStoredThread = (0, import_react4.useCallback)(
790
915
  (thread) => {
791
916
  storage.upsertThread(thread);
792
917
  syncThreadsFromStorage();
793
918
  },
794
919
  [storage, syncThreadsFromStorage]
795
920
  );
796
- (0, import_react2.useEffect)(() => {
921
+ (0, import_react4.useEffect)(() => {
797
922
  messagesRef.current = messages;
798
923
  }, [messages]);
799
- const persistThread = (0, import_react2.useCallback)(
800
- (threadId, nextMessages, paging, options) => {
924
+ const persistThread = (0, import_react4.useCallback)(
925
+ (threadId, nextMessages, paging, options2) => {
801
926
  const existing = storage.getThread(threadId);
802
927
  const firstUser = nextMessages.find((message) => message.role === "user");
803
928
  upsertStoredThread({
804
929
  ...existing || {},
805
930
  id: threadId,
806
931
  title: existing?.title || (firstUser ? titleFromMessage(firstUser.content, firstUser.attachments || []) : "New chat"),
807
- updated_at: options?.updatedAt || (options?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
932
+ updated_at: options2?.updatedAt || (options2?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
808
933
  messages: nextMessages,
809
934
  isHydrated: true,
810
935
  hasOlderTurns: paging?.hasOlderTurns ?? hasOlderTurnsRef.current,
@@ -813,7 +938,7 @@ function useAgents24ChatController({
813
938
  },
814
939
  [storage, upsertStoredThread]
815
940
  );
816
- const markThreadRunStatus = (0, import_react2.useCallback)(
941
+ const markThreadRunStatus = (0, import_react4.useCallback)(
817
942
  (threadId, runId, status, lastEventSeq) => {
818
943
  const existing = storage.getThread(threadId);
819
944
  if (!existing || !runId) return;
@@ -843,7 +968,7 @@ function useAgents24ChatController({
843
968
  },
844
969
  [storage, upsertStoredThread]
845
970
  );
846
- const refresh = (0, import_react2.useCallback)(async () => {
971
+ const refresh = (0, import_react4.useCallback)(async () => {
847
972
  const seq = ++refreshSeqRef.current;
848
973
  setIsRefreshingThreads(true);
849
974
  try {
@@ -857,7 +982,7 @@ function useAgents24ChatController({
857
982
  if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
858
983
  }
859
984
  }, [lifecycleAbortController, storage, transport]);
860
- const applyThreadId = (0, import_react2.useCallback)(
985
+ const applyThreadId = (0, import_react4.useCallback)(
861
986
  (threadId, baseMessages) => {
862
987
  if (!threadId || activeThreadIdRef.current === threadId) return;
863
988
  activeThreadIdRef.current = threadId;
@@ -871,15 +996,15 @@ function useAgents24ChatController({
871
996
  streamingContentRef.current = value;
872
997
  setStreamingContent(value);
873
998
  };
874
- const setLoadingHistory = (0, import_react2.useCallback)((value) => {
999
+ const setLoadingHistory = (0, import_react4.useCallback)((value) => {
875
1000
  isLoadingHistoryRef.current = value;
876
1001
  setIsLoadingHistory(value);
877
1002
  }, []);
878
- const setReasoningSteps = (0, import_react2.useCallback)((value) => {
1003
+ const setReasoningSteps = (0, import_react4.useCallback)((value) => {
879
1004
  reasoningRef.current = value || [];
880
1005
  setCurrentReasoning(value || []);
881
1006
  }, []);
882
- const detachActiveStream = (0, import_react2.useCallback)(() => {
1007
+ const detachActiveStream = (0, import_react4.useCallback)(() => {
883
1008
  const controller = abortControllerRef.current;
884
1009
  abortControllerRef.current = null;
885
1010
  abortDetachedStream(controller);
@@ -893,7 +1018,7 @@ function useAgents24ChatController({
893
1018
  setStreamingContent("");
894
1019
  setCurrentReasoning([]);
895
1020
  }, [setActiveRunIdValue]);
896
- const clearMissingThread = (0, import_react2.useCallback)(
1021
+ const clearMissingThread = (0, import_react4.useCallback)(
897
1022
  (threadId) => {
898
1023
  storage.deleteThread?.(threadId);
899
1024
  syncThreadsFromStorage();
@@ -915,7 +1040,7 @@ function useAgents24ChatController({
915
1040
  },
916
1041
  [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
917
1042
  );
918
- const setLiveAssistantMessage = (0, import_react2.useCallback)(
1043
+ const setLiveAssistantMessage = (0, import_react4.useCallback)(
919
1044
  (input) => {
920
1045
  setMessages((prev) => {
921
1046
  const next = upsertStableAssistantMessage(prev, {
@@ -947,7 +1072,7 @@ function useAgents24ChatController({
947
1072
  },
948
1073
  []
949
1074
  );
950
- const finalizeAssistantMessage = (0, import_react2.useCallback)(
1075
+ const finalizeAssistantMessage = (0, import_react4.useCallback)(
951
1076
  (input) => {
952
1077
  const content = input.error || input.assistantText.trim();
953
1078
  if (!content) return input.baseMessages;
@@ -1001,7 +1126,7 @@ function useAgents24ChatController({
1001
1126
  },
1002
1127
  [createId, persistThread, storage, upsertStoredThread]
1003
1128
  );
1004
- const loadThread = (0, import_react2.useCallback)(
1129
+ const loadThread = (0, import_react4.useCallback)(
1005
1130
  async (threadId) => {
1006
1131
  const seq = ++requestSeqRef.current;
1007
1132
  setLoadingHistory(true);
@@ -1046,7 +1171,7 @@ function useAgents24ChatController({
1046
1171
  },
1047
1172
  [clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
1048
1173
  );
1049
- const loadOlderTurns = (0, import_react2.useCallback)(async () => {
1174
+ const loadOlderTurns = (0, import_react4.useCallback)(async () => {
1050
1175
  const threadId = activeThreadIdRef.current;
1051
1176
  const beforeTurnIndex = nextBeforeTurnIndexRef.current;
1052
1177
  if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
@@ -1082,7 +1207,7 @@ function useAgents24ChatController({
1082
1207
  isLoadingOlderRef.current = false;
1083
1208
  }
1084
1209
  }, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
1085
- const handleStreamEvent = (0, import_react2.useCallback)(
1210
+ const handleStreamEvent = (0, import_react4.useCallback)(
1086
1211
  (input) => {
1087
1212
  const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
1088
1213
  const payload = event.payload || {};
@@ -1143,7 +1268,7 @@ function useAgents24ChatController({
1143
1268
  },
1144
1269
  [applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
1145
1270
  );
1146
- const runStream = (0, import_react2.useCallback)(
1271
+ const runStream = (0, import_react4.useCallback)(
1147
1272
  async (input) => {
1148
1273
  const startedAt = Date.now();
1149
1274
  const controller = new AbortController();
@@ -1266,18 +1391,26 @@ function useAgents24ChatController({
1266
1391
  },
1267
1392
  [createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
1268
1393
  );
1269
- const handleSubmit = (0, import_react2.useCallback)(
1394
+ const handleSubmit = (0, import_react4.useCallback)(
1270
1395
  async (message) => {
1271
1396
  if (!message.text.trim() && !(message.files || []).length) return;
1272
1397
  await runStream({ mode: "submit", message });
1273
1398
  },
1274
1399
  [runStream]
1275
1400
  );
1276
- const attachRun = (0, import_react2.useCallback)(async (runId, threadId) => {
1401
+ const attachRun = (0, import_react4.useCallback)(async (runId, threadId) => {
1277
1402
  const resolvedThreadId = threadId ?? activeThreadIdRef.current;
1278
1403
  if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
1279
1404
  }, [runStream]);
1280
- const handleStop = (0, import_react2.useCallback)(() => {
1405
+ const { pendingHitl, isResolvingHitl, resumeHitl } = useControllerHitl({
1406
+ messages,
1407
+ transport,
1408
+ activeRunId,
1409
+ activeRunIdRef,
1410
+ activeThreadIdRef,
1411
+ runStream
1412
+ });
1413
+ const handleStop = (0, import_react4.useCallback)(() => {
1281
1414
  const runId = activeRunIdRef.current;
1282
1415
  const partial = streamingContentRef.current;
1283
1416
  const liveMessageId = streamingMessageIdRef.current;
@@ -1301,56 +1434,25 @@ function useAgents24ChatController({
1301
1434
  });
1302
1435
  }
1303
1436
  }, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
1304
- (0, import_react2.useEffect)(() => {
1437
+ (0, import_react4.useEffect)(() => {
1305
1438
  refresh().catch((error) => {
1306
1439
  if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
1307
1440
  setThreads(storage.listThreads());
1308
1441
  }
1309
1442
  });
1310
1443
  }, [lifecycleAbortController, refresh, storage]);
1311
- (0, import_react2.useEffect)(() => {
1444
+ (0, import_react4.useEffect)(() => {
1312
1445
  return () => {
1313
1446
  lifecycleAbortController.abort();
1314
1447
  abortDetachedStream(abortControllerRef.current);
1315
1448
  abortControllerRef.current = null;
1316
1449
  };
1317
1450
  }, [lifecycleAbortController]);
1318
- (0, import_react2.useEffect)(() => {
1319
- if (!transport.subscribeThreadEvents) return;
1320
- let cancelled = false;
1321
- let retryTimeout = null;
1322
- let controller = null;
1323
- const connect = () => {
1324
- if (cancelled) return;
1325
- controller = new AbortController();
1326
- transport.subscribeThreadEvents?.(
1327
- { cursor: threadEventsCursorRef.current, signal: controller.signal },
1328
- async (event) => {
1329
- if (typeof event.cursor === "number") threadEventsCursorRef.current = event.cursor;
1330
- if (event.event === "snapshot_required") {
1331
- await refresh().catch(() => void 0);
1332
- return;
1333
- }
1334
- const next = applyThreadSummaryEvent(storage.listThreads(), event);
1335
- storage.setThreads(next);
1336
- setThreads(storage.listThreads());
1337
- }
1338
- ).catch((error) => {
1339
- if (cancelled || isAbortError(error)) return;
1340
- retryTimeout = setTimeout(connect, 1500);
1341
- });
1342
- };
1343
- connect();
1344
- return () => {
1345
- cancelled = true;
1346
- if (retryTimeout) clearTimeout(retryTimeout);
1347
- controller?.abort();
1348
- };
1349
- }, [refresh, storage, transport]);
1350
- (0, import_react2.useEffect)(() => {
1451
+ useControllerThreadEvents({ transport, storage, refresh, setThreads });
1452
+ (0, import_react4.useEffect)(() => {
1351
1453
  syncThreadsFromStorage();
1352
1454
  }, [storageKey, syncThreadsFromStorage]);
1353
- (0, import_react2.useEffect)(() => {
1455
+ (0, import_react4.useEffect)(() => {
1354
1456
  const previous = activeThreadIdRef.current;
1355
1457
  activeThreadIdRef.current = activeThreadId;
1356
1458
  if (activeThreadId && previous === activeThreadId && (activeRunIdRef.current || streamingMessageIdRef.current)) {
@@ -1394,14 +1496,14 @@ function useAgents24ChatController({
1394
1496
  }
1395
1497
  void loadThread(activeThreadId).catch(() => setLoadingHistory(false));
1396
1498
  }, [activeThreadId, detachActiveStream, loadThread, setLoadingHistory, storage]);
1397
- (0, import_react2.useEffect)(() => {
1499
+ (0, import_react4.useEffect)(() => {
1398
1500
  const threadId = activeThreadId;
1399
1501
  if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
1400
1502
  const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
1401
1503
  if (!runId || reattachedRunIdRef.current === runId) return;
1402
1504
  void runStream({ mode: "attach", threadId, runId });
1403
1505
  }, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
1404
- (0, import_react2.useEffect)(() => {
1506
+ (0, import_react4.useEffect)(() => {
1405
1507
  const threadId = activeThreadId;
1406
1508
  if (!threadId || isLoadingHistoryRef.current || activeRunIdRef.current || streamingMessageIdRef.current) {
1407
1509
  return;
@@ -1442,7 +1544,7 @@ function useAgents24ChatController({
1442
1544
  setMessages,
1443
1545
  storage
1444
1546
  });
1445
- const loadThreadById = (0, import_react2.useCallback)(
1547
+ const loadThreadById = (0, import_react4.useCallback)(
1446
1548
  async (threadId) => {
1447
1549
  if (!threadId) return;
1448
1550
  setIsSelectingThread(true);
@@ -1461,7 +1563,7 @@ function useAgents24ChatController({
1461
1563
  [detachActiveStream, loadThread, onActiveThreadIdChange, storage]
1462
1564
  );
1463
1565
  const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
1464
- return (0, import_react2.useMemo)(() => ({
1566
+ return (0, import_react4.useMemo)(() => ({
1465
1567
  threads,
1466
1568
  activeThreadId,
1467
1569
  activeThread,
@@ -1481,8 +1583,11 @@ function useAgents24ChatController({
1481
1583
  copiedMessageId,
1482
1584
  lastThinkingDurationMs,
1483
1585
  activeRunId,
1586
+ pendingHitl,
1587
+ isResolvingHitl,
1484
1588
  handleSubmit,
1485
1589
  attachRun,
1590
+ resumeHitl,
1486
1591
  handleStop,
1487
1592
  handleCopy,
1488
1593
  handleLike,
@@ -1521,8 +1626,11 @@ function useAgents24ChatController({
1521
1626
  loadThreadById,
1522
1627
  loadOlderTurns,
1523
1628
  messages,
1629
+ isResolvingHitl,
1524
1630
  onSourceClick,
1631
+ pendingHitl,
1525
1632
  refresh,
1633
+ resumeHitl,
1526
1634
  streamingContent,
1527
1635
  streamingMessageId,
1528
1636
  startNewThread,
@@ -1875,6 +1983,59 @@ var LatestThreadScroller = {
1875
1983
  Outline: LatestThreadScrollerOutline
1876
1984
  };
1877
1985
 
1986
+ // src/mcp-oauth.ts
1987
+ var CONNECTION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1988
+ function normalizedOrigin(value) {
1989
+ try {
1990
+ return new URL(value).origin;
1991
+ } catch {
1992
+ throw new Error("MCP authorization returned an invalid callback origin.");
1993
+ }
1994
+ }
1995
+ function waitForMcpOauthComplete({
1996
+ popup,
1997
+ callbackOrigin,
1998
+ serverId,
1999
+ timeoutMs = 12e4
2000
+ }) {
2001
+ if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
2002
+ const expectedOrigin = normalizedOrigin(callbackOrigin);
2003
+ const expectedServerId = String(serverId || "").trim();
2004
+ if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
2005
+ return new Promise((resolve, reject) => {
2006
+ let settled = false;
2007
+ const finish = (result) => {
2008
+ if (settled) return;
2009
+ settled = true;
2010
+ window.clearTimeout(timeout);
2011
+ window.clearInterval(closeTimer);
2012
+ window.removeEventListener("message", onMessage);
2013
+ if (result instanceof Error) reject(result);
2014
+ else resolve(result);
2015
+ };
2016
+ const onMessage = (event) => {
2017
+ if (event.source !== popup || event.origin !== expectedOrigin) return;
2018
+ const data = event.data;
2019
+ if (data?.type !== "mcp-oauth-complete" || data.server_id !== expectedServerId) return;
2020
+ if (!data.success) {
2021
+ finish(new Error("MCP authorization failed."));
2022
+ return;
2023
+ }
2024
+ const connectionId = String(data.connection_id || "").trim();
2025
+ if (!CONNECTION_ID_PATTERN.test(connectionId)) return;
2026
+ finish({ connectionId });
2027
+ };
2028
+ const timeout = window.setTimeout(
2029
+ () => finish(new Error("MCP authorization timed out.")),
2030
+ Math.max(1, timeoutMs)
2031
+ );
2032
+ const closeTimer = window.setInterval(() => {
2033
+ if (popup.closed) finish(new Error("MCP authorization window was closed before connecting."));
2034
+ }, 500);
2035
+ window.addEventListener("message", onMessage);
2036
+ });
2037
+ }
2038
+
1878
2039
  // src/renderers.tsx
1879
2040
  var import_jsx_runtime2 = require("react/jsx-runtime");
1880
2041
  var DefaultToolPart = ({ part }) => {
@@ -1992,7 +2153,7 @@ var consumeSseResponse = async (response, onEvent, options = {}) => {
1992
2153
  };
1993
2154
 
1994
2155
  // src/streaming-text.ts
1995
- var import_react3 = require("react");
2156
+ var import_react5 = require("react");
1996
2157
  var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
1997
2158
  var defaultCompletedTextCache = /* @__PURE__ */ new Map();
1998
2159
  var defaultStreamingTextCache = {
@@ -2020,24 +2181,24 @@ function useStreamingText({
2020
2181
  maxCatchupChars = 20
2021
2182
  }) {
2022
2183
  const cacheAdapter = cache === false ? null : cache;
2023
- const [displayedText, setDisplayedText] = (0, import_react3.useState)(() => {
2184
+ const [displayedText, setDisplayedText] = (0, import_react5.useState)(() => {
2024
2185
  const cachedText = cacheAdapter?.get(id);
2025
2186
  return isStreaming && cachedText !== text ? "" : text;
2026
2187
  });
2027
- const targetRef = (0, import_react3.useRef)(text);
2028
- const displayedRef = (0, import_react3.useRef)(displayedText);
2029
- const rafRef = (0, import_react3.useRef)(null);
2030
- const lastFrameAtRef = (0, import_react3.useRef)(null);
2031
- const idRef = (0, import_react3.useRef)(id);
2032
- const shouldAnimateRef = (0, import_react3.useRef)(isStreaming);
2033
- (0, import_react3.useEffect)(() => {
2188
+ const targetRef = (0, import_react5.useRef)(text);
2189
+ const displayedRef = (0, import_react5.useRef)(displayedText);
2190
+ const rafRef = (0, import_react5.useRef)(null);
2191
+ const lastFrameAtRef = (0, import_react5.useRef)(null);
2192
+ const idRef = (0, import_react5.useRef)(id);
2193
+ const shouldAnimateRef = (0, import_react5.useRef)(isStreaming);
2194
+ (0, import_react5.useEffect)(() => {
2034
2195
  if (isStreaming || !text) return;
2035
2196
  cacheAdapter?.set(id, text);
2036
2197
  }, [cacheAdapter, id, isStreaming, text]);
2037
- (0, import_react3.useEffect)(() => {
2198
+ (0, import_react5.useEffect)(() => {
2038
2199
  targetRef.current = text;
2039
2200
  }, [text]);
2040
- (0, import_react3.useEffect)(() => {
2201
+ (0, import_react5.useEffect)(() => {
2041
2202
  if (idRef.current === id) return;
2042
2203
  idRef.current = id;
2043
2204
  const cachedText = cacheAdapter?.get(id);
@@ -2051,7 +2212,7 @@ function useStreamingText({
2051
2212
  }
2052
2213
  lastFrameAtRef.current = null;
2053
2214
  }, [cacheAdapter, id, isStreaming, text]);
2054
- (0, import_react3.useEffect)(() => {
2215
+ (0, import_react5.useEffect)(() => {
2055
2216
  if (typeof window === "undefined") {
2056
2217
  displayedRef.current = text;
2057
2218
  setDisplayedText(text);
@@ -2229,9 +2390,10 @@ var createFetchChatTransport = ({
2229
2390
  method: "POST",
2230
2391
  headers: jsonHeaders(await loadHeaders()),
2231
2392
  body: JSON.stringify({
2232
- schema_version: "agents24.hitl.resume.v1",
2393
+ schema_version: "agents24.hitl.resume.v2",
2233
2394
  interrupt_id: input.interruptId,
2234
- decisions: input.decisions,
2395
+ action: input.action,
2396
+ comment: input.comment,
2235
2397
  client: input.client
2236
2398
  })
2237
2399
  });
@@ -2311,6 +2473,7 @@ var import_message_scroller2 = require("@shadcn/react/message-scroller");
2311
2473
  useMessageScroller,
2312
2474
  useMessageScrollerScrollable,
2313
2475
  useMessageScrollerVisibility,
2314
- useStreamingText
2476
+ useStreamingText,
2477
+ waitForMcpOauthComplete
2315
2478
  });
2316
2479
  //# sourceMappingURL=index.cjs.map