@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.d.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./controller";
2
2
  export * from "./context-window";
3
3
  export * from "./latest-thread-scroller";
4
4
  export * from "./message-lifecycle";
5
+ export * from "./mcp-oauth";
5
6
  export * from "./model";
6
7
  export * from "./renderers";
7
8
  export * from "./sse";
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-EWZO4QJI.js";
16
16
 
17
17
  // src/controller.ts
18
- import { useCallback as useCallback2, useEffect, useMemo, useRef, useState } from "react";
18
+ import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
19
19
 
20
20
  // src/controller-actions.ts
21
21
  import { useCallback } from "react";
@@ -89,6 +89,47 @@ function useControllerMessageActions(input) {
89
89
  return { handleCopy, handleDislike, handleLike, handleRetry, startNewThread, upsertLiveVoiceMessage };
90
90
  }
91
91
 
92
+ // src/controller-hitl.ts
93
+ import { useCallback as useCallback2, useMemo, useState } from "react";
94
+ function useControllerHitl({
95
+ messages,
96
+ transport,
97
+ activeRunId,
98
+ activeRunIdRef,
99
+ activeThreadIdRef,
100
+ runStream
101
+ }) {
102
+ const [isResolvingHitl, setIsResolvingHitl] = useState(false);
103
+ const pendingHitl = useMemo(() => {
104
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
105
+ const parts = messages[messageIndex]?.parts || [];
106
+ for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) {
107
+ const part = parts[partIndex];
108
+ if (part?.kind === "hitl" && part.status === "pending") return part;
109
+ }
110
+ }
111
+ return null;
112
+ }, [messages]);
113
+ const resumeHitl = useCallback2(async (input) => {
114
+ if (!transport.resumeHitl) throw new Error("HITL resume is not configured.");
115
+ const runId = input.runId || activeRunIdRef.current || activeRunId;
116
+ if (!runId) throw new Error("No paused run is available to resume.");
117
+ setIsResolvingHitl(true);
118
+ try {
119
+ const result = await transport.resumeHitl({ ...input, runId });
120
+ const threadId = result.thread_id || activeThreadIdRef.current;
121
+ if (threadId) await runStream({ mode: "attach", runId: result.run_id || runId, threadId });
122
+ return result;
123
+ } finally {
124
+ setIsResolvingHitl(false);
125
+ }
126
+ }, [activeRunId, activeRunIdRef, activeThreadIdRef, runStream, transport]);
127
+ return { pendingHitl, isResolvingHitl, resumeHitl };
128
+ }
129
+
130
+ // src/controller-thread-events.ts
131
+ import { useEffect, useRef } from "react";
132
+
92
133
  // src/context-window.ts
93
134
  var SOURCE_PRIORITY = {
94
135
  unknown: 0,
@@ -111,16 +152,16 @@ var STAGE_PRIORITY = {
111
152
  function numberOrNull(value) {
112
153
  return typeof value === "number" && Number.isFinite(value) ? value : null;
113
154
  }
114
- function stagePriority(window) {
115
- return STAGE_PRIORITY[window?.stage || "sent_prompt"] ?? STAGE_PRIORITY.sent_prompt;
155
+ function stagePriority(window2) {
156
+ return STAGE_PRIORITY[window2?.stage || "sent_prompt"] ?? STAGE_PRIORITY.sent_prompt;
116
157
  }
117
- function hasRenderableContextWindow(window) {
118
- const maxTokens = window?.max_tokens;
158
+ function hasRenderableContextWindow(window2) {
159
+ const maxTokens = window2?.max_tokens;
119
160
  return typeof maxTokens === "number" && Number.isFinite(maxTokens) && maxTokens > 0;
120
161
  }
121
- function windowWeight(window) {
122
- if (!window) return [0, 0];
123
- return [stagePriority(window), SOURCE_PRIORITY[window.source] || 0];
162
+ function windowWeight(window2) {
163
+ if (!window2) return [0, 0];
164
+ return [stagePriority(window2), SOURCE_PRIORITY[window2.source] || 0];
124
165
  }
125
166
  function normalizeContextCompression(value) {
126
167
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
@@ -273,6 +314,22 @@ var assistantTextFromEvents = (events) => {
273
314
  };
274
315
  var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
275
316
  var optionalString = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
317
+ var HITL_ACTIONS_BY_KIND = {
318
+ tool_review: ["approve", "reject"],
319
+ mcp_auth: ["connect", "skip"],
320
+ user_approval: ["approve", "reject"],
321
+ app_data_permission: ["approve", "reject"]
322
+ };
323
+ var HITL_STATUSES = ["pending", "resolved", "expired", "cancelled", "invalidated"];
324
+ function exactHitlActions(kind, value) {
325
+ if (!Array.isArray(value)) throw new Error("Invalid V2 HITL response block.");
326
+ const actions = value.map((item) => String(item));
327
+ const expected = HITL_ACTIONS_BY_KIND[kind];
328
+ if (actions.length !== expected.length || actions.some((action, index) => action !== expected[index])) {
329
+ throw new Error("Invalid V2 HITL response block.");
330
+ }
331
+ return actions;
332
+ }
276
333
  var toolStateFromStatus = (status) => {
277
334
  const normalized = String(status || "").trim().toLowerCase();
278
335
  if (["failed", "error"].includes(normalized)) return "output-error";
@@ -373,13 +430,39 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
373
430
  }
374
431
  if (block.kind === "hitl_request") {
375
432
  const hitl = asRecord(block.hitl) || block;
433
+ if (hitl.schema_version !== "agents24.hitl.interrupt.v2") {
434
+ throw new Error("Invalid V2 HITL response block.");
435
+ }
436
+ const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
437
+ const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
438
+ const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
439
+ if (!interruptId || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind)) || !HITL_STATUSES.includes(status)) {
440
+ throw new Error("Invalid V2 HITL response block.");
441
+ }
442
+ const kind = hitlKind;
443
+ const allowedActions = exactHitlActions(kind, hitl.allowed_actions);
444
+ const resolution = asRecord(block.resolution);
445
+ const resolver = asRecord(resolution?.resolver);
376
446
  parts.push({
377
447
  id,
378
448
  type: "hitl",
379
449
  kind: "hitl",
380
- hitl,
381
- interruptId: optionalString(block.interruptId) || optionalString(hitl.interrupt_id) || null,
382
- hitlKind: optionalString(block.hitlKind) || optionalString(hitl.kind) || null,
450
+ interruptId,
451
+ hitlKind: kind,
452
+ message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
453
+ allowedActions,
454
+ status,
455
+ presentation: asRecord(hitl.presentation) || {},
456
+ resolution: resolution ? {
457
+ action: ["approve", "reject", "connect", "skip"].includes(String(resolution.action)) ? String(resolution.action) : null,
458
+ outcome: optionalString(resolution.outcome),
459
+ reason: optionalString(resolution.reason),
460
+ resolvedAt: optionalString(resolution.resolved_at),
461
+ resolver: resolver ? {
462
+ principalType: optionalString(resolver.principal_type) || optionalString(resolver.principalType) || null,
463
+ principalId: optionalString(resolver.principal_id) || optionalString(resolver.principalId) || null
464
+ } : null
465
+ } : null,
383
466
  raw: block
384
467
  });
385
468
  return;
@@ -619,6 +702,47 @@ var applyThreadSummaryEvent = (currentThreads, event) => {
619
702
  return sortByActivity(next);
620
703
  };
621
704
 
705
+ // src/controller-thread-events.ts
706
+ function useControllerThreadEvents({
707
+ transport,
708
+ storage,
709
+ refresh,
710
+ setThreads
711
+ }) {
712
+ const cursorRef = useRef(null);
713
+ useEffect(() => {
714
+ if (!transport.subscribeThreadEvents) return;
715
+ let cancelled = false;
716
+ let retryTimeout = null;
717
+ let controller = null;
718
+ const connect = () => {
719
+ if (cancelled) return;
720
+ controller = new AbortController();
721
+ transport.subscribeThreadEvents?.(
722
+ { cursor: cursorRef.current, signal: controller.signal },
723
+ async (event) => {
724
+ if (typeof event.cursor === "number") cursorRef.current = event.cursor;
725
+ if (event.event === "snapshot_required") {
726
+ await refresh().catch(() => void 0);
727
+ return;
728
+ }
729
+ storage.setThreads(applyThreadSummaryEvent(storage.listThreads(), event));
730
+ setThreads(storage.listThreads());
731
+ }
732
+ ).catch((error) => {
733
+ if (cancelled || isAbortError(error)) return;
734
+ retryTimeout = setTimeout(connect, 1500);
735
+ });
736
+ };
737
+ connect();
738
+ return () => {
739
+ cancelled = true;
740
+ if (retryTimeout) clearTimeout(retryTimeout);
741
+ controller?.abort();
742
+ };
743
+ }, [refresh, setThreads, storage, transport]);
744
+ }
745
+
622
746
  // src/message-lifecycle.ts
623
747
  function findStableAssistantMessageIndex(messages, input) {
624
748
  return messages.findIndex(
@@ -657,83 +781,83 @@ function upsertStableAssistantMessage(messages, input) {
657
781
  }
658
782
 
659
783
  // src/controller.ts
660
- function useAgents24ChatController({
661
- transport,
662
- storage,
663
- activeThreadId: controlledActiveThreadId,
664
- pageSize = DEFAULT_THREAD_PAGE_SIZE,
665
- storageKey,
666
- createId = createChatId,
667
- onActiveThreadIdChange,
668
- onSourceClick,
669
- onStreamErrorMessage,
670
- onRuntimeEvent,
671
- onThreadDetailLoaded
672
- }) {
784
+ function useAgents24ChatController(options) {
785
+ const {
786
+ transport,
787
+ storage,
788
+ activeThreadId: controlledActiveThreadId,
789
+ pageSize = DEFAULT_THREAD_PAGE_SIZE,
790
+ storageKey,
791
+ createId = createChatId,
792
+ onActiveThreadIdChange,
793
+ onSourceClick,
794
+ onStreamErrorMessage,
795
+ onRuntimeEvent,
796
+ onThreadDetailLoaded
797
+ } = options;
673
798
  const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
674
799
  const initialCached = activeThreadId ? storage.getThread(activeThreadId) : void 0;
675
- const [messages, setMessages] = useState(() => initialCached?.messages || []);
676
- const [isLoading, setIsLoading] = useState(false);
677
- const [isLoadingHistory, setIsLoadingHistory] = useState(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
678
- const [isLoadingOlder, setIsLoadingOlder] = useState(false);
679
- const [hasOlderTurns, setHasOlderTurns] = useState(Boolean(initialCached?.hasOlderTurns));
680
- const [streamingContent, setStreamingContent] = useState("");
681
- const [streamingMessageId, setStreamingMessageId] = useState(null);
682
- const [contextStatus, setContextStatus] = useState(null);
683
- const [currentReasoning, setCurrentReasoning] = useState([]);
684
- const [liked, setLiked] = useState({});
685
- const [disliked, setDisliked] = useState({});
686
- const [copiedMessageId, setCopiedMessageId] = useState(null);
687
- const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState(null);
688
- const [threads, setThreads] = useState(() => storage.listThreads());
689
- const [isRefreshingThreads, setIsRefreshingThreads] = useState(false);
690
- const [isSelectingThread, setIsSelectingThread] = useState(false);
691
- const [activeRunId, setActiveRunId] = useState(null);
692
- const textareaRef = useRef(null);
693
- const activeThreadIdRef = useRef(activeThreadId);
694
- const messagesRef = useRef(messages);
695
- const nextBeforeTurnIndexRef = useRef(initialCached?.nextBeforeTurnIndex ?? null);
696
- const hasOlderTurnsRef = useRef(Boolean(initialCached?.hasOlderTurns));
697
- const isLoadingOlderRef = useRef(false);
698
- const loadedThreadIdRef = useRef(initialCached?.messages?.length ? activeThreadId : null);
699
- const requestSeqRef = useRef(0);
700
- const isLoadingHistoryRef = useRef(isLoadingHistory);
701
- const activeRunIdRef = useRef(null);
702
- const reattachedRunIdRef = useRef(null);
703
- const abortControllerRef = useRef(null);
704
- const [lifecycleAbortController] = useState(() => new AbortController());
705
- const streamingContentRef = useRef("");
706
- const streamingMessageIdRef = useRef(null);
707
- const reasoningRef = useRef([]);
708
- const liveVoiceIdsRef = useRef({});
709
- const refreshSeqRef = useRef(0);
710
- const threadEventsCursorRef = useRef(null);
711
- const setActiveRunIdValue = useCallback2((runId) => {
800
+ const [messages, setMessages] = useState2(() => initialCached?.messages || []);
801
+ const [isLoading, setIsLoading] = useState2(false);
802
+ const [isLoadingHistory, setIsLoadingHistory] = useState2(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
803
+ const [isLoadingOlder, setIsLoadingOlder] = useState2(false);
804
+ const [hasOlderTurns, setHasOlderTurns] = useState2(Boolean(initialCached?.hasOlderTurns));
805
+ const [streamingContent, setStreamingContent] = useState2("");
806
+ const [streamingMessageId, setStreamingMessageId] = useState2(null);
807
+ const [contextStatus, setContextStatus] = useState2(null);
808
+ const [currentReasoning, setCurrentReasoning] = useState2([]);
809
+ const [liked, setLiked] = useState2({});
810
+ const [disliked, setDisliked] = useState2({});
811
+ const [copiedMessageId, setCopiedMessageId] = useState2(null);
812
+ const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState2(null);
813
+ const [threads, setThreads] = useState2(() => storage.listThreads());
814
+ const [isRefreshingThreads, setIsRefreshingThreads] = useState2(false);
815
+ const [isSelectingThread, setIsSelectingThread] = useState2(false);
816
+ const [activeRunId, setActiveRunId] = useState2(null);
817
+ const textareaRef = useRef2(null);
818
+ const activeThreadIdRef = useRef2(activeThreadId);
819
+ const messagesRef = useRef2(messages);
820
+ const nextBeforeTurnIndexRef = useRef2(initialCached?.nextBeforeTurnIndex ?? null);
821
+ const hasOlderTurnsRef = useRef2(Boolean(initialCached?.hasOlderTurns));
822
+ const isLoadingOlderRef = useRef2(false);
823
+ const loadedThreadIdRef = useRef2(initialCached?.messages?.length ? activeThreadId : null);
824
+ const requestSeqRef = useRef2(0);
825
+ const isLoadingHistoryRef = useRef2(isLoadingHistory);
826
+ const activeRunIdRef = useRef2(null);
827
+ const reattachedRunIdRef = useRef2(null);
828
+ const abortControllerRef = useRef2(null);
829
+ const [lifecycleAbortController] = useState2(() => new AbortController());
830
+ const streamingContentRef = useRef2("");
831
+ const streamingMessageIdRef = useRef2(null);
832
+ const reasoningRef = useRef2([]);
833
+ const liveVoiceIdsRef = useRef2({});
834
+ const refreshSeqRef = useRef2(0);
835
+ const setActiveRunIdValue = useCallback3((runId) => {
712
836
  activeRunIdRef.current = runId;
713
837
  setActiveRunId(runId);
714
838
  }, []);
715
- const syncThreadsFromStorage = useCallback2(() => {
839
+ const syncThreadsFromStorage = useCallback3(() => {
716
840
  setThreads(storage.listThreads());
717
841
  }, [storage]);
718
- const upsertStoredThread = useCallback2(
842
+ const upsertStoredThread = useCallback3(
719
843
  (thread) => {
720
844
  storage.upsertThread(thread);
721
845
  syncThreadsFromStorage();
722
846
  },
723
847
  [storage, syncThreadsFromStorage]
724
848
  );
725
- useEffect(() => {
849
+ useEffect2(() => {
726
850
  messagesRef.current = messages;
727
851
  }, [messages]);
728
- const persistThread = useCallback2(
729
- (threadId, nextMessages, paging, options) => {
852
+ const persistThread = useCallback3(
853
+ (threadId, nextMessages, paging, options2) => {
730
854
  const existing = storage.getThread(threadId);
731
855
  const firstUser = nextMessages.find((message) => message.role === "user");
732
856
  upsertStoredThread({
733
857
  ...existing || {},
734
858
  id: threadId,
735
859
  title: existing?.title || (firstUser ? titleFromMessage(firstUser.content, firstUser.attachments || []) : "New chat"),
736
- updated_at: options?.updatedAt || (options?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
860
+ updated_at: options2?.updatedAt || (options2?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
737
861
  messages: nextMessages,
738
862
  isHydrated: true,
739
863
  hasOlderTurns: paging?.hasOlderTurns ?? hasOlderTurnsRef.current,
@@ -742,7 +866,7 @@ function useAgents24ChatController({
742
866
  },
743
867
  [storage, upsertStoredThread]
744
868
  );
745
- const markThreadRunStatus = useCallback2(
869
+ const markThreadRunStatus = useCallback3(
746
870
  (threadId, runId, status, lastEventSeq) => {
747
871
  const existing = storage.getThread(threadId);
748
872
  if (!existing || !runId) return;
@@ -772,7 +896,7 @@ function useAgents24ChatController({
772
896
  },
773
897
  [storage, upsertStoredThread]
774
898
  );
775
- const refresh = useCallback2(async () => {
899
+ const refresh = useCallback3(async () => {
776
900
  const seq = ++refreshSeqRef.current;
777
901
  setIsRefreshingThreads(true);
778
902
  try {
@@ -786,7 +910,7 @@ function useAgents24ChatController({
786
910
  if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
787
911
  }
788
912
  }, [lifecycleAbortController, storage, transport]);
789
- const applyThreadId = useCallback2(
913
+ const applyThreadId = useCallback3(
790
914
  (threadId, baseMessages) => {
791
915
  if (!threadId || activeThreadIdRef.current === threadId) return;
792
916
  activeThreadIdRef.current = threadId;
@@ -800,15 +924,15 @@ function useAgents24ChatController({
800
924
  streamingContentRef.current = value;
801
925
  setStreamingContent(value);
802
926
  };
803
- const setLoadingHistory = useCallback2((value) => {
927
+ const setLoadingHistory = useCallback3((value) => {
804
928
  isLoadingHistoryRef.current = value;
805
929
  setIsLoadingHistory(value);
806
930
  }, []);
807
- const setReasoningSteps = useCallback2((value) => {
931
+ const setReasoningSteps = useCallback3((value) => {
808
932
  reasoningRef.current = value || [];
809
933
  setCurrentReasoning(value || []);
810
934
  }, []);
811
- const detachActiveStream = useCallback2(() => {
935
+ const detachActiveStream = useCallback3(() => {
812
936
  const controller = abortControllerRef.current;
813
937
  abortControllerRef.current = null;
814
938
  abortDetachedStream(controller);
@@ -822,7 +946,7 @@ function useAgents24ChatController({
822
946
  setStreamingContent("");
823
947
  setCurrentReasoning([]);
824
948
  }, [setActiveRunIdValue]);
825
- const clearMissingThread = useCallback2(
949
+ const clearMissingThread = useCallback3(
826
950
  (threadId) => {
827
951
  storage.deleteThread?.(threadId);
828
952
  syncThreadsFromStorage();
@@ -844,7 +968,7 @@ function useAgents24ChatController({
844
968
  },
845
969
  [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
846
970
  );
847
- const setLiveAssistantMessage = useCallback2(
971
+ const setLiveAssistantMessage = useCallback3(
848
972
  (input) => {
849
973
  setMessages((prev) => {
850
974
  const next = upsertStableAssistantMessage(prev, {
@@ -876,7 +1000,7 @@ function useAgents24ChatController({
876
1000
  },
877
1001
  []
878
1002
  );
879
- const finalizeAssistantMessage = useCallback2(
1003
+ const finalizeAssistantMessage = useCallback3(
880
1004
  (input) => {
881
1005
  const content = input.error || input.assistantText.trim();
882
1006
  if (!content) return input.baseMessages;
@@ -930,7 +1054,7 @@ function useAgents24ChatController({
930
1054
  },
931
1055
  [createId, persistThread, storage, upsertStoredThread]
932
1056
  );
933
- const loadThread = useCallback2(
1057
+ const loadThread = useCallback3(
934
1058
  async (threadId) => {
935
1059
  const seq = ++requestSeqRef.current;
936
1060
  setLoadingHistory(true);
@@ -975,7 +1099,7 @@ function useAgents24ChatController({
975
1099
  },
976
1100
  [clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
977
1101
  );
978
- const loadOlderTurns = useCallback2(async () => {
1102
+ const loadOlderTurns = useCallback3(async () => {
979
1103
  const threadId = activeThreadIdRef.current;
980
1104
  const beforeTurnIndex = nextBeforeTurnIndexRef.current;
981
1105
  if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
@@ -1011,7 +1135,7 @@ function useAgents24ChatController({
1011
1135
  isLoadingOlderRef.current = false;
1012
1136
  }
1013
1137
  }, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
1014
- const handleStreamEvent = useCallback2(
1138
+ const handleStreamEvent = useCallback3(
1015
1139
  (input) => {
1016
1140
  const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
1017
1141
  const payload = event.payload || {};
@@ -1072,7 +1196,7 @@ function useAgents24ChatController({
1072
1196
  },
1073
1197
  [applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
1074
1198
  );
1075
- const runStream = useCallback2(
1199
+ const runStream = useCallback3(
1076
1200
  async (input) => {
1077
1201
  const startedAt = Date.now();
1078
1202
  const controller = new AbortController();
@@ -1195,18 +1319,26 @@ function useAgents24ChatController({
1195
1319
  },
1196
1320
  [createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
1197
1321
  );
1198
- const handleSubmit = useCallback2(
1322
+ const handleSubmit = useCallback3(
1199
1323
  async (message) => {
1200
1324
  if (!message.text.trim() && !(message.files || []).length) return;
1201
1325
  await runStream({ mode: "submit", message });
1202
1326
  },
1203
1327
  [runStream]
1204
1328
  );
1205
- const attachRun = useCallback2(async (runId, threadId) => {
1329
+ const attachRun = useCallback3(async (runId, threadId) => {
1206
1330
  const resolvedThreadId = threadId ?? activeThreadIdRef.current;
1207
1331
  if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
1208
1332
  }, [runStream]);
1209
- const handleStop = useCallback2(() => {
1333
+ const { pendingHitl, isResolvingHitl, resumeHitl } = useControllerHitl({
1334
+ messages,
1335
+ transport,
1336
+ activeRunId,
1337
+ activeRunIdRef,
1338
+ activeThreadIdRef,
1339
+ runStream
1340
+ });
1341
+ const handleStop = useCallback3(() => {
1210
1342
  const runId = activeRunIdRef.current;
1211
1343
  const partial = streamingContentRef.current;
1212
1344
  const liveMessageId = streamingMessageIdRef.current;
@@ -1230,56 +1362,25 @@ function useAgents24ChatController({
1230
1362
  });
1231
1363
  }
1232
1364
  }, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
1233
- useEffect(() => {
1365
+ useEffect2(() => {
1234
1366
  refresh().catch((error) => {
1235
1367
  if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
1236
1368
  setThreads(storage.listThreads());
1237
1369
  }
1238
1370
  });
1239
1371
  }, [lifecycleAbortController, refresh, storage]);
1240
- useEffect(() => {
1372
+ useEffect2(() => {
1241
1373
  return () => {
1242
1374
  lifecycleAbortController.abort();
1243
1375
  abortDetachedStream(abortControllerRef.current);
1244
1376
  abortControllerRef.current = null;
1245
1377
  };
1246
1378
  }, [lifecycleAbortController]);
1247
- useEffect(() => {
1248
- if (!transport.subscribeThreadEvents) return;
1249
- let cancelled = false;
1250
- let retryTimeout = null;
1251
- let controller = null;
1252
- const connect = () => {
1253
- if (cancelled) return;
1254
- controller = new AbortController();
1255
- transport.subscribeThreadEvents?.(
1256
- { cursor: threadEventsCursorRef.current, signal: controller.signal },
1257
- async (event) => {
1258
- if (typeof event.cursor === "number") threadEventsCursorRef.current = event.cursor;
1259
- if (event.event === "snapshot_required") {
1260
- await refresh().catch(() => void 0);
1261
- return;
1262
- }
1263
- const next = applyThreadSummaryEvent(storage.listThreads(), event);
1264
- storage.setThreads(next);
1265
- setThreads(storage.listThreads());
1266
- }
1267
- ).catch((error) => {
1268
- if (cancelled || isAbortError(error)) return;
1269
- retryTimeout = setTimeout(connect, 1500);
1270
- });
1271
- };
1272
- connect();
1273
- return () => {
1274
- cancelled = true;
1275
- if (retryTimeout) clearTimeout(retryTimeout);
1276
- controller?.abort();
1277
- };
1278
- }, [refresh, storage, transport]);
1279
- useEffect(() => {
1379
+ useControllerThreadEvents({ transport, storage, refresh, setThreads });
1380
+ useEffect2(() => {
1280
1381
  syncThreadsFromStorage();
1281
1382
  }, [storageKey, syncThreadsFromStorage]);
1282
- useEffect(() => {
1383
+ useEffect2(() => {
1283
1384
  const previous = activeThreadIdRef.current;
1284
1385
  activeThreadIdRef.current = activeThreadId;
1285
1386
  if (activeThreadId && previous === activeThreadId && (activeRunIdRef.current || streamingMessageIdRef.current)) {
@@ -1323,14 +1424,14 @@ function useAgents24ChatController({
1323
1424
  }
1324
1425
  void loadThread(activeThreadId).catch(() => setLoadingHistory(false));
1325
1426
  }, [activeThreadId, detachActiveStream, loadThread, setLoadingHistory, storage]);
1326
- useEffect(() => {
1427
+ useEffect2(() => {
1327
1428
  const threadId = activeThreadId;
1328
1429
  if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
1329
1430
  const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
1330
1431
  if (!runId || reattachedRunIdRef.current === runId) return;
1331
1432
  void runStream({ mode: "attach", threadId, runId });
1332
1433
  }, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
1333
- useEffect(() => {
1434
+ useEffect2(() => {
1334
1435
  const threadId = activeThreadId;
1335
1436
  if (!threadId || isLoadingHistoryRef.current || activeRunIdRef.current || streamingMessageIdRef.current) {
1336
1437
  return;
@@ -1371,7 +1472,7 @@ function useAgents24ChatController({
1371
1472
  setMessages,
1372
1473
  storage
1373
1474
  });
1374
- const loadThreadById = useCallback2(
1475
+ const loadThreadById = useCallback3(
1375
1476
  async (threadId) => {
1376
1477
  if (!threadId) return;
1377
1478
  setIsSelectingThread(true);
@@ -1390,7 +1491,7 @@ function useAgents24ChatController({
1390
1491
  [detachActiveStream, loadThread, onActiveThreadIdChange, storage]
1391
1492
  );
1392
1493
  const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
1393
- return useMemo(() => ({
1494
+ return useMemo2(() => ({
1394
1495
  threads,
1395
1496
  activeThreadId,
1396
1497
  activeThread,
@@ -1410,8 +1511,11 @@ function useAgents24ChatController({
1410
1511
  copiedMessageId,
1411
1512
  lastThinkingDurationMs,
1412
1513
  activeRunId,
1514
+ pendingHitl,
1515
+ isResolvingHitl,
1413
1516
  handleSubmit,
1414
1517
  attachRun,
1518
+ resumeHitl,
1415
1519
  handleStop,
1416
1520
  handleCopy,
1417
1521
  handleLike,
@@ -1450,8 +1554,11 @@ function useAgents24ChatController({
1450
1554
  loadThreadById,
1451
1555
  loadOlderTurns,
1452
1556
  messages,
1557
+ isResolvingHitl,
1453
1558
  onSourceClick,
1559
+ pendingHitl,
1454
1560
  refresh,
1561
+ resumeHitl,
1455
1562
  streamingContent,
1456
1563
  streamingMessageId,
1457
1564
  startNewThread,
@@ -1460,6 +1567,59 @@ function useAgents24ChatController({
1460
1567
  ]);
1461
1568
  }
1462
1569
 
1570
+ // src/mcp-oauth.ts
1571
+ 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;
1572
+ function normalizedOrigin(value) {
1573
+ try {
1574
+ return new URL(value).origin;
1575
+ } catch {
1576
+ throw new Error("MCP authorization returned an invalid callback origin.");
1577
+ }
1578
+ }
1579
+ function waitForMcpOauthComplete({
1580
+ popup,
1581
+ callbackOrigin,
1582
+ serverId,
1583
+ timeoutMs = 12e4
1584
+ }) {
1585
+ if (!popup) return Promise.reject(new Error("Could not open the MCP authorization popup."));
1586
+ const expectedOrigin = normalizedOrigin(callbackOrigin);
1587
+ const expectedServerId = String(serverId || "").trim();
1588
+ if (!expectedServerId) return Promise.reject(new Error("MCP authorization is missing its server identity."));
1589
+ return new Promise((resolve, reject) => {
1590
+ let settled = false;
1591
+ const finish = (result) => {
1592
+ if (settled) return;
1593
+ settled = true;
1594
+ window.clearTimeout(timeout);
1595
+ window.clearInterval(closeTimer);
1596
+ window.removeEventListener("message", onMessage);
1597
+ if (result instanceof Error) reject(result);
1598
+ else resolve(result);
1599
+ };
1600
+ const onMessage = (event) => {
1601
+ if (event.source !== popup || event.origin !== expectedOrigin) return;
1602
+ const data = event.data;
1603
+ if (data?.type !== "mcp-oauth-complete" || data.server_id !== expectedServerId) return;
1604
+ if (!data.success) {
1605
+ finish(new Error("MCP authorization failed."));
1606
+ return;
1607
+ }
1608
+ const connectionId = String(data.connection_id || "").trim();
1609
+ if (!CONNECTION_ID_PATTERN.test(connectionId)) return;
1610
+ finish({ connectionId });
1611
+ };
1612
+ const timeout = window.setTimeout(
1613
+ () => finish(new Error("MCP authorization timed out.")),
1614
+ Math.max(1, timeoutMs)
1615
+ );
1616
+ const closeTimer = window.setInterval(() => {
1617
+ if (popup.closed) finish(new Error("MCP authorization window was closed before connecting."));
1618
+ }, 500);
1619
+ window.addEventListener("message", onMessage);
1620
+ });
1621
+ }
1622
+
1463
1623
  // src/renderers.tsx
1464
1624
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
1465
1625
  var DefaultToolPart = ({ part }) => {
@@ -1680,9 +1840,10 @@ var createFetchChatTransport = ({
1680
1840
  method: "POST",
1681
1841
  headers: jsonHeaders(await loadHeaders()),
1682
1842
  body: JSON.stringify({
1683
- schema_version: "agents24.hitl.resume.v1",
1843
+ schema_version: "agents24.hitl.resume.v2",
1684
1844
  interrupt_id: input.interruptId,
1685
- decisions: input.decisions,
1845
+ action: input.action,
1846
+ comment: input.comment,
1686
1847
  client: input.client
1687
1848
  })
1688
1849
  });
@@ -1766,6 +1927,7 @@ export {
1766
1927
  useMessageScroller,
1767
1928
  useMessageScrollerScrollable,
1768
1929
  useMessageScrollerVisibility,
1769
- useStreamingText
1930
+ useStreamingText,
1931
+ waitForMcpOauthComplete
1770
1932
  };
1771
1933
  //# sourceMappingURL=index.js.map