@agents24/chat-react 0.2.0 → 0.3.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.cjs CHANGED
@@ -86,7 +86,7 @@ __export(index_exports, {
86
86
  module.exports = __toCommonJS(index_exports);
87
87
 
88
88
  // src/controller.ts
89
- var import_react2 = require("react");
89
+ var import_react4 = require("react");
90
90
 
91
91
  // src/controller-actions.ts
92
92
  var import_react = require("react");
@@ -160,6 +160,47 @@ function useControllerMessageActions(input) {
160
160
  return { handleCopy, handleDislike, handleLike, handleRetry, startNewThread, upsertLiveVoiceMessage };
161
161
  }
162
162
 
163
+ // src/controller-hitl.ts
164
+ var import_react2 = require("react");
165
+ function useControllerHitl({
166
+ messages,
167
+ transport,
168
+ activeRunId,
169
+ activeRunIdRef,
170
+ activeThreadIdRef,
171
+ runStream
172
+ }) {
173
+ const [isResolvingHitl, setIsResolvingHitl] = (0, import_react2.useState)(false);
174
+ const pendingHitl = (0, import_react2.useMemo)(() => {
175
+ for (let messageIndex = messages.length - 1; messageIndex >= 0; messageIndex -= 1) {
176
+ const parts = messages[messageIndex]?.parts || [];
177
+ for (let partIndex = parts.length - 1; partIndex >= 0; partIndex -= 1) {
178
+ const part = parts[partIndex];
179
+ if (part?.kind === "hitl" && part.status === "pending") return part;
180
+ }
181
+ }
182
+ return null;
183
+ }, [messages]);
184
+ const resumeHitl = (0, import_react2.useCallback)(async (input) => {
185
+ if (!transport.resumeHitl) throw new Error("HITL resume is not configured.");
186
+ const runId = input.runId || activeRunIdRef.current || activeRunId;
187
+ if (!runId) throw new Error("No paused run is available to resume.");
188
+ setIsResolvingHitl(true);
189
+ try {
190
+ const result = await transport.resumeHitl({ ...input, runId });
191
+ const threadId = result.thread_id || activeThreadIdRef.current;
192
+ if (threadId) await runStream({ mode: "attach", runId: result.run_id || runId, threadId });
193
+ return result;
194
+ } finally {
195
+ setIsResolvingHitl(false);
196
+ }
197
+ }, [activeRunId, activeRunIdRef, activeThreadIdRef, runStream, transport]);
198
+ return { pendingHitl, isResolvingHitl, resumeHitl };
199
+ }
200
+
201
+ // src/controller-thread-events.ts
202
+ var import_react3 = require("react");
203
+
163
204
  // src/context-window.ts
164
205
  var SOURCE_PRIORITY = {
165
206
  unknown: 0,
@@ -444,13 +485,34 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
444
485
  }
445
486
  if (block.kind === "hitl_request") {
446
487
  const hitl = asRecord(block.hitl) || block;
488
+ if (hitl.schema_version !== "agents24.hitl.interrupt.v2") {
489
+ throw new Error("Invalid V2 HITL response block.");
490
+ }
491
+ const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
492
+ const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
493
+ const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
494
+ const allowedActions = Array.isArray(hitl.allowed_actions) ? hitl.allowed_actions.filter((value) => ["approve", "reject", "connect", "skip"].includes(String(value))) : [];
495
+ if (!interruptId || allowedActions.length === 0 || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind))) {
496
+ throw new Error("Invalid V2 HITL response block.");
497
+ }
498
+ const resolution = asRecord(block.resolution);
447
499
  parts.push({
448
500
  id,
449
501
  type: "hitl",
450
502
  kind: "hitl",
451
- hitl,
452
- interruptId: optionalString(block.interruptId) || optionalString(hitl.interrupt_id) || null,
453
- hitlKind: optionalString(block.hitlKind) || optionalString(hitl.kind) || null,
503
+ interruptId,
504
+ hitlKind,
505
+ message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
506
+ allowedActions,
507
+ status,
508
+ presentation: asRecord(hitl.presentation) || {},
509
+ resolution: resolution ? {
510
+ action: ["approve", "reject", "connect", "skip"].includes(String(resolution.action)) ? String(resolution.action) : null,
511
+ outcome: optionalString(resolution.outcome),
512
+ reason: optionalString(resolution.reason),
513
+ resolvedAt: optionalString(resolution.resolved_at),
514
+ resolver: asRecord(resolution.resolver)
515
+ } : null,
454
516
  raw: block
455
517
  });
456
518
  return;
@@ -690,6 +752,47 @@ var applyThreadSummaryEvent = (currentThreads, event) => {
690
752
  return sortByActivity(next);
691
753
  };
692
754
 
755
+ // src/controller-thread-events.ts
756
+ function useControllerThreadEvents({
757
+ transport,
758
+ storage,
759
+ refresh,
760
+ setThreads
761
+ }) {
762
+ const cursorRef = (0, import_react3.useRef)(null);
763
+ (0, import_react3.useEffect)(() => {
764
+ if (!transport.subscribeThreadEvents) return;
765
+ let cancelled = false;
766
+ let retryTimeout = null;
767
+ let controller = null;
768
+ const connect = () => {
769
+ if (cancelled) return;
770
+ controller = new AbortController();
771
+ transport.subscribeThreadEvents?.(
772
+ { cursor: cursorRef.current, signal: controller.signal },
773
+ async (event) => {
774
+ if (typeof event.cursor === "number") cursorRef.current = event.cursor;
775
+ if (event.event === "snapshot_required") {
776
+ await refresh().catch(() => void 0);
777
+ return;
778
+ }
779
+ storage.setThreads(applyThreadSummaryEvent(storage.listThreads(), event));
780
+ setThreads(storage.listThreads());
781
+ }
782
+ ).catch((error) => {
783
+ if (cancelled || isAbortError(error)) return;
784
+ retryTimeout = setTimeout(connect, 1500);
785
+ });
786
+ };
787
+ connect();
788
+ return () => {
789
+ cancelled = true;
790
+ if (retryTimeout) clearTimeout(retryTimeout);
791
+ controller?.abort();
792
+ };
793
+ }, [refresh, setThreads, storage, transport]);
794
+ }
795
+
693
796
  // src/message-lifecycle.ts
694
797
  function findStableAssistantMessageIndex(messages, input) {
695
798
  return messages.findIndex(
@@ -728,83 +831,83 @@ function upsertStableAssistantMessage(messages, input) {
728
831
  }
729
832
 
730
833
  // 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
- }) {
834
+ function useAgents24ChatController(options) {
835
+ const {
836
+ transport,
837
+ storage,
838
+ activeThreadId: controlledActiveThreadId,
839
+ pageSize = DEFAULT_THREAD_PAGE_SIZE,
840
+ storageKey,
841
+ createId = createChatId,
842
+ onActiveThreadIdChange,
843
+ onSourceClick,
844
+ onStreamErrorMessage,
845
+ onRuntimeEvent,
846
+ onThreadDetailLoaded
847
+ } = options;
744
848
  const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
745
849
  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) => {
850
+ const [messages, setMessages] = (0, import_react4.useState)(() => initialCached?.messages || []);
851
+ const [isLoading, setIsLoading] = (0, import_react4.useState)(false);
852
+ const [isLoadingHistory, setIsLoadingHistory] = (0, import_react4.useState)(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
853
+ const [isLoadingOlder, setIsLoadingOlder] = (0, import_react4.useState)(false);
854
+ const [hasOlderTurns, setHasOlderTurns] = (0, import_react4.useState)(Boolean(initialCached?.hasOlderTurns));
855
+ const [streamingContent, setStreamingContent] = (0, import_react4.useState)("");
856
+ const [streamingMessageId, setStreamingMessageId] = (0, import_react4.useState)(null);
857
+ const [contextStatus, setContextStatus] = (0, import_react4.useState)(null);
858
+ const [currentReasoning, setCurrentReasoning] = (0, import_react4.useState)([]);
859
+ const [liked, setLiked] = (0, import_react4.useState)({});
860
+ const [disliked, setDisliked] = (0, import_react4.useState)({});
861
+ const [copiedMessageId, setCopiedMessageId] = (0, import_react4.useState)(null);
862
+ const [lastThinkingDurationMs, setLastThinkingDurationMs] = (0, import_react4.useState)(null);
863
+ const [threads, setThreads] = (0, import_react4.useState)(() => storage.listThreads());
864
+ const [isRefreshingThreads, setIsRefreshingThreads] = (0, import_react4.useState)(false);
865
+ const [isSelectingThread, setIsSelectingThread] = (0, import_react4.useState)(false);
866
+ const [activeRunId, setActiveRunId] = (0, import_react4.useState)(null);
867
+ const textareaRef = (0, import_react4.useRef)(null);
868
+ const activeThreadIdRef = (0, import_react4.useRef)(activeThreadId);
869
+ const messagesRef = (0, import_react4.useRef)(messages);
870
+ const nextBeforeTurnIndexRef = (0, import_react4.useRef)(initialCached?.nextBeforeTurnIndex ?? null);
871
+ const hasOlderTurnsRef = (0, import_react4.useRef)(Boolean(initialCached?.hasOlderTurns));
872
+ const isLoadingOlderRef = (0, import_react4.useRef)(false);
873
+ const loadedThreadIdRef = (0, import_react4.useRef)(initialCached?.messages?.length ? activeThreadId : null);
874
+ const requestSeqRef = (0, import_react4.useRef)(0);
875
+ const isLoadingHistoryRef = (0, import_react4.useRef)(isLoadingHistory);
876
+ const activeRunIdRef = (0, import_react4.useRef)(null);
877
+ const reattachedRunIdRef = (0, import_react4.useRef)(null);
878
+ const abortControllerRef = (0, import_react4.useRef)(null);
879
+ const [lifecycleAbortController] = (0, import_react4.useState)(() => new AbortController());
880
+ const streamingContentRef = (0, import_react4.useRef)("");
881
+ const streamingMessageIdRef = (0, import_react4.useRef)(null);
882
+ const reasoningRef = (0, import_react4.useRef)([]);
883
+ const liveVoiceIdsRef = (0, import_react4.useRef)({});
884
+ const refreshSeqRef = (0, import_react4.useRef)(0);
885
+ const setActiveRunIdValue = (0, import_react4.useCallback)((runId) => {
783
886
  activeRunIdRef.current = runId;
784
887
  setActiveRunId(runId);
785
888
  }, []);
786
- const syncThreadsFromStorage = (0, import_react2.useCallback)(() => {
889
+ const syncThreadsFromStorage = (0, import_react4.useCallback)(() => {
787
890
  setThreads(storage.listThreads());
788
891
  }, [storage]);
789
- const upsertStoredThread = (0, import_react2.useCallback)(
892
+ const upsertStoredThread = (0, import_react4.useCallback)(
790
893
  (thread) => {
791
894
  storage.upsertThread(thread);
792
895
  syncThreadsFromStorage();
793
896
  },
794
897
  [storage, syncThreadsFromStorage]
795
898
  );
796
- (0, import_react2.useEffect)(() => {
899
+ (0, import_react4.useEffect)(() => {
797
900
  messagesRef.current = messages;
798
901
  }, [messages]);
799
- const persistThread = (0, import_react2.useCallback)(
800
- (threadId, nextMessages, paging, options) => {
902
+ const persistThread = (0, import_react4.useCallback)(
903
+ (threadId, nextMessages, paging, options2) => {
801
904
  const existing = storage.getThread(threadId);
802
905
  const firstUser = nextMessages.find((message) => message.role === "user");
803
906
  upsertStoredThread({
804
907
  ...existing || {},
805
908
  id: threadId,
806
909
  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(),
910
+ updated_at: options2?.updatedAt || (options2?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
808
911
  messages: nextMessages,
809
912
  isHydrated: true,
810
913
  hasOlderTurns: paging?.hasOlderTurns ?? hasOlderTurnsRef.current,
@@ -813,7 +916,7 @@ function useAgents24ChatController({
813
916
  },
814
917
  [storage, upsertStoredThread]
815
918
  );
816
- const markThreadRunStatus = (0, import_react2.useCallback)(
919
+ const markThreadRunStatus = (0, import_react4.useCallback)(
817
920
  (threadId, runId, status, lastEventSeq) => {
818
921
  const existing = storage.getThread(threadId);
819
922
  if (!existing || !runId) return;
@@ -843,7 +946,7 @@ function useAgents24ChatController({
843
946
  },
844
947
  [storage, upsertStoredThread]
845
948
  );
846
- const refresh = (0, import_react2.useCallback)(async () => {
949
+ const refresh = (0, import_react4.useCallback)(async () => {
847
950
  const seq = ++refreshSeqRef.current;
848
951
  setIsRefreshingThreads(true);
849
952
  try {
@@ -857,7 +960,7 @@ function useAgents24ChatController({
857
960
  if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
858
961
  }
859
962
  }, [lifecycleAbortController, storage, transport]);
860
- const applyThreadId = (0, import_react2.useCallback)(
963
+ const applyThreadId = (0, import_react4.useCallback)(
861
964
  (threadId, baseMessages) => {
862
965
  if (!threadId || activeThreadIdRef.current === threadId) return;
863
966
  activeThreadIdRef.current = threadId;
@@ -871,15 +974,15 @@ function useAgents24ChatController({
871
974
  streamingContentRef.current = value;
872
975
  setStreamingContent(value);
873
976
  };
874
- const setLoadingHistory = (0, import_react2.useCallback)((value) => {
977
+ const setLoadingHistory = (0, import_react4.useCallback)((value) => {
875
978
  isLoadingHistoryRef.current = value;
876
979
  setIsLoadingHistory(value);
877
980
  }, []);
878
- const setReasoningSteps = (0, import_react2.useCallback)((value) => {
981
+ const setReasoningSteps = (0, import_react4.useCallback)((value) => {
879
982
  reasoningRef.current = value || [];
880
983
  setCurrentReasoning(value || []);
881
984
  }, []);
882
- const detachActiveStream = (0, import_react2.useCallback)(() => {
985
+ const detachActiveStream = (0, import_react4.useCallback)(() => {
883
986
  const controller = abortControllerRef.current;
884
987
  abortControllerRef.current = null;
885
988
  abortDetachedStream(controller);
@@ -893,7 +996,7 @@ function useAgents24ChatController({
893
996
  setStreamingContent("");
894
997
  setCurrentReasoning([]);
895
998
  }, [setActiveRunIdValue]);
896
- const clearMissingThread = (0, import_react2.useCallback)(
999
+ const clearMissingThread = (0, import_react4.useCallback)(
897
1000
  (threadId) => {
898
1001
  storage.deleteThread?.(threadId);
899
1002
  syncThreadsFromStorage();
@@ -915,7 +1018,7 @@ function useAgents24ChatController({
915
1018
  },
916
1019
  [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
917
1020
  );
918
- const setLiveAssistantMessage = (0, import_react2.useCallback)(
1021
+ const setLiveAssistantMessage = (0, import_react4.useCallback)(
919
1022
  (input) => {
920
1023
  setMessages((prev) => {
921
1024
  const next = upsertStableAssistantMessage(prev, {
@@ -947,7 +1050,7 @@ function useAgents24ChatController({
947
1050
  },
948
1051
  []
949
1052
  );
950
- const finalizeAssistantMessage = (0, import_react2.useCallback)(
1053
+ const finalizeAssistantMessage = (0, import_react4.useCallback)(
951
1054
  (input) => {
952
1055
  const content = input.error || input.assistantText.trim();
953
1056
  if (!content) return input.baseMessages;
@@ -1001,7 +1104,7 @@ function useAgents24ChatController({
1001
1104
  },
1002
1105
  [createId, persistThread, storage, upsertStoredThread]
1003
1106
  );
1004
- const loadThread = (0, import_react2.useCallback)(
1107
+ const loadThread = (0, import_react4.useCallback)(
1005
1108
  async (threadId) => {
1006
1109
  const seq = ++requestSeqRef.current;
1007
1110
  setLoadingHistory(true);
@@ -1046,7 +1149,7 @@ function useAgents24ChatController({
1046
1149
  },
1047
1150
  [clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
1048
1151
  );
1049
- const loadOlderTurns = (0, import_react2.useCallback)(async () => {
1152
+ const loadOlderTurns = (0, import_react4.useCallback)(async () => {
1050
1153
  const threadId = activeThreadIdRef.current;
1051
1154
  const beforeTurnIndex = nextBeforeTurnIndexRef.current;
1052
1155
  if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
@@ -1082,7 +1185,7 @@ function useAgents24ChatController({
1082
1185
  isLoadingOlderRef.current = false;
1083
1186
  }
1084
1187
  }, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
1085
- const handleStreamEvent = (0, import_react2.useCallback)(
1188
+ const handleStreamEvent = (0, import_react4.useCallback)(
1086
1189
  (input) => {
1087
1190
  const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
1088
1191
  const payload = event.payload || {};
@@ -1143,7 +1246,7 @@ function useAgents24ChatController({
1143
1246
  },
1144
1247
  [applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
1145
1248
  );
1146
- const runStream = (0, import_react2.useCallback)(
1249
+ const runStream = (0, import_react4.useCallback)(
1147
1250
  async (input) => {
1148
1251
  const startedAt = Date.now();
1149
1252
  const controller = new AbortController();
@@ -1266,18 +1369,26 @@ function useAgents24ChatController({
1266
1369
  },
1267
1370
  [createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
1268
1371
  );
1269
- const handleSubmit = (0, import_react2.useCallback)(
1372
+ const handleSubmit = (0, import_react4.useCallback)(
1270
1373
  async (message) => {
1271
1374
  if (!message.text.trim() && !(message.files || []).length) return;
1272
1375
  await runStream({ mode: "submit", message });
1273
1376
  },
1274
1377
  [runStream]
1275
1378
  );
1276
- const attachRun = (0, import_react2.useCallback)(async (runId, threadId) => {
1379
+ const attachRun = (0, import_react4.useCallback)(async (runId, threadId) => {
1277
1380
  const resolvedThreadId = threadId ?? activeThreadIdRef.current;
1278
1381
  if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
1279
1382
  }, [runStream]);
1280
- const handleStop = (0, import_react2.useCallback)(() => {
1383
+ const { pendingHitl, isResolvingHitl, resumeHitl } = useControllerHitl({
1384
+ messages,
1385
+ transport,
1386
+ activeRunId,
1387
+ activeRunIdRef,
1388
+ activeThreadIdRef,
1389
+ runStream
1390
+ });
1391
+ const handleStop = (0, import_react4.useCallback)(() => {
1281
1392
  const runId = activeRunIdRef.current;
1282
1393
  const partial = streamingContentRef.current;
1283
1394
  const liveMessageId = streamingMessageIdRef.current;
@@ -1301,56 +1412,25 @@ function useAgents24ChatController({
1301
1412
  });
1302
1413
  }
1303
1414
  }, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
1304
- (0, import_react2.useEffect)(() => {
1415
+ (0, import_react4.useEffect)(() => {
1305
1416
  refresh().catch((error) => {
1306
1417
  if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
1307
1418
  setThreads(storage.listThreads());
1308
1419
  }
1309
1420
  });
1310
1421
  }, [lifecycleAbortController, refresh, storage]);
1311
- (0, import_react2.useEffect)(() => {
1422
+ (0, import_react4.useEffect)(() => {
1312
1423
  return () => {
1313
1424
  lifecycleAbortController.abort();
1314
1425
  abortDetachedStream(abortControllerRef.current);
1315
1426
  abortControllerRef.current = null;
1316
1427
  };
1317
1428
  }, [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)(() => {
1429
+ useControllerThreadEvents({ transport, storage, refresh, setThreads });
1430
+ (0, import_react4.useEffect)(() => {
1351
1431
  syncThreadsFromStorage();
1352
1432
  }, [storageKey, syncThreadsFromStorage]);
1353
- (0, import_react2.useEffect)(() => {
1433
+ (0, import_react4.useEffect)(() => {
1354
1434
  const previous = activeThreadIdRef.current;
1355
1435
  activeThreadIdRef.current = activeThreadId;
1356
1436
  if (activeThreadId && previous === activeThreadId && (activeRunIdRef.current || streamingMessageIdRef.current)) {
@@ -1394,14 +1474,14 @@ function useAgents24ChatController({
1394
1474
  }
1395
1475
  void loadThread(activeThreadId).catch(() => setLoadingHistory(false));
1396
1476
  }, [activeThreadId, detachActiveStream, loadThread, setLoadingHistory, storage]);
1397
- (0, import_react2.useEffect)(() => {
1477
+ (0, import_react4.useEffect)(() => {
1398
1478
  const threadId = activeThreadId;
1399
1479
  if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
1400
1480
  const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
1401
1481
  if (!runId || reattachedRunIdRef.current === runId) return;
1402
1482
  void runStream({ mode: "attach", threadId, runId });
1403
1483
  }, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
1404
- (0, import_react2.useEffect)(() => {
1484
+ (0, import_react4.useEffect)(() => {
1405
1485
  const threadId = activeThreadId;
1406
1486
  if (!threadId || isLoadingHistoryRef.current || activeRunIdRef.current || streamingMessageIdRef.current) {
1407
1487
  return;
@@ -1442,7 +1522,7 @@ function useAgents24ChatController({
1442
1522
  setMessages,
1443
1523
  storage
1444
1524
  });
1445
- const loadThreadById = (0, import_react2.useCallback)(
1525
+ const loadThreadById = (0, import_react4.useCallback)(
1446
1526
  async (threadId) => {
1447
1527
  if (!threadId) return;
1448
1528
  setIsSelectingThread(true);
@@ -1461,7 +1541,7 @@ function useAgents24ChatController({
1461
1541
  [detachActiveStream, loadThread, onActiveThreadIdChange, storage]
1462
1542
  );
1463
1543
  const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
1464
- return (0, import_react2.useMemo)(() => ({
1544
+ return (0, import_react4.useMemo)(() => ({
1465
1545
  threads,
1466
1546
  activeThreadId,
1467
1547
  activeThread,
@@ -1481,8 +1561,11 @@ function useAgents24ChatController({
1481
1561
  copiedMessageId,
1482
1562
  lastThinkingDurationMs,
1483
1563
  activeRunId,
1564
+ pendingHitl,
1565
+ isResolvingHitl,
1484
1566
  handleSubmit,
1485
1567
  attachRun,
1568
+ resumeHitl,
1486
1569
  handleStop,
1487
1570
  handleCopy,
1488
1571
  handleLike,
@@ -1521,8 +1604,11 @@ function useAgents24ChatController({
1521
1604
  loadThreadById,
1522
1605
  loadOlderTurns,
1523
1606
  messages,
1607
+ isResolvingHitl,
1524
1608
  onSourceClick,
1609
+ pendingHitl,
1525
1610
  refresh,
1611
+ resumeHitl,
1526
1612
  streamingContent,
1527
1613
  streamingMessageId,
1528
1614
  startNewThread,
@@ -1992,7 +2078,7 @@ var consumeSseResponse = async (response, onEvent, options = {}) => {
1992
2078
  };
1993
2079
 
1994
2080
  // src/streaming-text.ts
1995
- var import_react3 = require("react");
2081
+ var import_react5 = require("react");
1996
2082
  var DEFAULT_COMPLETED_TEXT_CACHE_SIZE = 200;
1997
2083
  var defaultCompletedTextCache = /* @__PURE__ */ new Map();
1998
2084
  var defaultStreamingTextCache = {
@@ -2020,24 +2106,24 @@ function useStreamingText({
2020
2106
  maxCatchupChars = 20
2021
2107
  }) {
2022
2108
  const cacheAdapter = cache === false ? null : cache;
2023
- const [displayedText, setDisplayedText] = (0, import_react3.useState)(() => {
2109
+ const [displayedText, setDisplayedText] = (0, import_react5.useState)(() => {
2024
2110
  const cachedText = cacheAdapter?.get(id);
2025
2111
  return isStreaming && cachedText !== text ? "" : text;
2026
2112
  });
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)(() => {
2113
+ const targetRef = (0, import_react5.useRef)(text);
2114
+ const displayedRef = (0, import_react5.useRef)(displayedText);
2115
+ const rafRef = (0, import_react5.useRef)(null);
2116
+ const lastFrameAtRef = (0, import_react5.useRef)(null);
2117
+ const idRef = (0, import_react5.useRef)(id);
2118
+ const shouldAnimateRef = (0, import_react5.useRef)(isStreaming);
2119
+ (0, import_react5.useEffect)(() => {
2034
2120
  if (isStreaming || !text) return;
2035
2121
  cacheAdapter?.set(id, text);
2036
2122
  }, [cacheAdapter, id, isStreaming, text]);
2037
- (0, import_react3.useEffect)(() => {
2123
+ (0, import_react5.useEffect)(() => {
2038
2124
  targetRef.current = text;
2039
2125
  }, [text]);
2040
- (0, import_react3.useEffect)(() => {
2126
+ (0, import_react5.useEffect)(() => {
2041
2127
  if (idRef.current === id) return;
2042
2128
  idRef.current = id;
2043
2129
  const cachedText = cacheAdapter?.get(id);
@@ -2051,7 +2137,7 @@ function useStreamingText({
2051
2137
  }
2052
2138
  lastFrameAtRef.current = null;
2053
2139
  }, [cacheAdapter, id, isStreaming, text]);
2054
- (0, import_react3.useEffect)(() => {
2140
+ (0, import_react5.useEffect)(() => {
2055
2141
  if (typeof window === "undefined") {
2056
2142
  displayedRef.current = text;
2057
2143
  setDisplayedText(text);
@@ -2229,9 +2315,10 @@ var createFetchChatTransport = ({
2229
2315
  method: "POST",
2230
2316
  headers: jsonHeaders(await loadHeaders()),
2231
2317
  body: JSON.stringify({
2232
- schema_version: "agents24.hitl.resume.v1",
2318
+ schema_version: "agents24.hitl.resume.v2",
2233
2319
  interrupt_id: input.interruptId,
2234
- decisions: input.decisions,
2320
+ action: input.action,
2321
+ comment: input.comment,
2235
2322
  client: input.client
2236
2323
  })
2237
2324
  });