@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.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,
@@ -373,13 +414,34 @@ var partsFromResponseBlocks = (blocks, fallbackText) => {
373
414
  }
374
415
  if (block.kind === "hitl_request") {
375
416
  const hitl = asRecord(block.hitl) || block;
417
+ if (hitl.schema_version !== "agents24.hitl.interrupt.v2") {
418
+ throw new Error("Invalid V2 HITL response block.");
419
+ }
420
+ const interruptId = optionalString(block.interruptId) || optionalString(hitl.interrupt_id);
421
+ const hitlKind = optionalString(block.hitlKind) || optionalString(hitl.kind);
422
+ const status = optionalString(block.status) || optionalString(hitl.status) || "pending";
423
+ const allowedActions = Array.isArray(hitl.allowed_actions) ? hitl.allowed_actions.filter((value) => ["approve", "reject", "connect", "skip"].includes(String(value))) : [];
424
+ if (!interruptId || allowedActions.length === 0 || !["tool_review", "mcp_auth", "user_approval", "app_data_permission"].includes(String(hitlKind))) {
425
+ throw new Error("Invalid V2 HITL response block.");
426
+ }
427
+ const resolution = asRecord(block.resolution);
376
428
  parts.push({
377
429
  id,
378
430
  type: "hitl",
379
431
  kind: "hitl",
380
- hitl,
381
- interruptId: optionalString(block.interruptId) || optionalString(hitl.interrupt_id) || null,
382
- hitlKind: optionalString(block.hitlKind) || optionalString(hitl.kind) || null,
432
+ interruptId,
433
+ hitlKind,
434
+ message: optionalString(hitl.message) || optionalString(block.text) || "Input is required to continue.",
435
+ allowedActions,
436
+ status,
437
+ presentation: asRecord(hitl.presentation) || {},
438
+ resolution: resolution ? {
439
+ action: ["approve", "reject", "connect", "skip"].includes(String(resolution.action)) ? String(resolution.action) : null,
440
+ outcome: optionalString(resolution.outcome),
441
+ reason: optionalString(resolution.reason),
442
+ resolvedAt: optionalString(resolution.resolved_at),
443
+ resolver: asRecord(resolution.resolver)
444
+ } : null,
383
445
  raw: block
384
446
  });
385
447
  return;
@@ -619,6 +681,47 @@ var applyThreadSummaryEvent = (currentThreads, event) => {
619
681
  return sortByActivity(next);
620
682
  };
621
683
 
684
+ // src/controller-thread-events.ts
685
+ function useControllerThreadEvents({
686
+ transport,
687
+ storage,
688
+ refresh,
689
+ setThreads
690
+ }) {
691
+ const cursorRef = useRef(null);
692
+ useEffect(() => {
693
+ if (!transport.subscribeThreadEvents) return;
694
+ let cancelled = false;
695
+ let retryTimeout = null;
696
+ let controller = null;
697
+ const connect = () => {
698
+ if (cancelled) return;
699
+ controller = new AbortController();
700
+ transport.subscribeThreadEvents?.(
701
+ { cursor: cursorRef.current, signal: controller.signal },
702
+ async (event) => {
703
+ if (typeof event.cursor === "number") cursorRef.current = event.cursor;
704
+ if (event.event === "snapshot_required") {
705
+ await refresh().catch(() => void 0);
706
+ return;
707
+ }
708
+ storage.setThreads(applyThreadSummaryEvent(storage.listThreads(), event));
709
+ setThreads(storage.listThreads());
710
+ }
711
+ ).catch((error) => {
712
+ if (cancelled || isAbortError(error)) return;
713
+ retryTimeout = setTimeout(connect, 1500);
714
+ });
715
+ };
716
+ connect();
717
+ return () => {
718
+ cancelled = true;
719
+ if (retryTimeout) clearTimeout(retryTimeout);
720
+ controller?.abort();
721
+ };
722
+ }, [refresh, setThreads, storage, transport]);
723
+ }
724
+
622
725
  // src/message-lifecycle.ts
623
726
  function findStableAssistantMessageIndex(messages, input) {
624
727
  return messages.findIndex(
@@ -657,83 +760,83 @@ function upsertStableAssistantMessage(messages, input) {
657
760
  }
658
761
 
659
762
  // 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
- }) {
763
+ function useAgents24ChatController(options) {
764
+ const {
765
+ transport,
766
+ storage,
767
+ activeThreadId: controlledActiveThreadId,
768
+ pageSize = DEFAULT_THREAD_PAGE_SIZE,
769
+ storageKey,
770
+ createId = createChatId,
771
+ onActiveThreadIdChange,
772
+ onSourceClick,
773
+ onStreamErrorMessage,
774
+ onRuntimeEvent,
775
+ onThreadDetailLoaded
776
+ } = options;
673
777
  const activeThreadId = controlledActiveThreadId ?? storage.getActiveThreadId?.() ?? null;
674
778
  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) => {
779
+ const [messages, setMessages] = useState2(() => initialCached?.messages || []);
780
+ const [isLoading, setIsLoading] = useState2(false);
781
+ const [isLoadingHistory, setIsLoadingHistory] = useState2(() => Boolean(activeThreadId) && !initialCached?.messages?.length);
782
+ const [isLoadingOlder, setIsLoadingOlder] = useState2(false);
783
+ const [hasOlderTurns, setHasOlderTurns] = useState2(Boolean(initialCached?.hasOlderTurns));
784
+ const [streamingContent, setStreamingContent] = useState2("");
785
+ const [streamingMessageId, setStreamingMessageId] = useState2(null);
786
+ const [contextStatus, setContextStatus] = useState2(null);
787
+ const [currentReasoning, setCurrentReasoning] = useState2([]);
788
+ const [liked, setLiked] = useState2({});
789
+ const [disliked, setDisliked] = useState2({});
790
+ const [copiedMessageId, setCopiedMessageId] = useState2(null);
791
+ const [lastThinkingDurationMs, setLastThinkingDurationMs] = useState2(null);
792
+ const [threads, setThreads] = useState2(() => storage.listThreads());
793
+ const [isRefreshingThreads, setIsRefreshingThreads] = useState2(false);
794
+ const [isSelectingThread, setIsSelectingThread] = useState2(false);
795
+ const [activeRunId, setActiveRunId] = useState2(null);
796
+ const textareaRef = useRef2(null);
797
+ const activeThreadIdRef = useRef2(activeThreadId);
798
+ const messagesRef = useRef2(messages);
799
+ const nextBeforeTurnIndexRef = useRef2(initialCached?.nextBeforeTurnIndex ?? null);
800
+ const hasOlderTurnsRef = useRef2(Boolean(initialCached?.hasOlderTurns));
801
+ const isLoadingOlderRef = useRef2(false);
802
+ const loadedThreadIdRef = useRef2(initialCached?.messages?.length ? activeThreadId : null);
803
+ const requestSeqRef = useRef2(0);
804
+ const isLoadingHistoryRef = useRef2(isLoadingHistory);
805
+ const activeRunIdRef = useRef2(null);
806
+ const reattachedRunIdRef = useRef2(null);
807
+ const abortControllerRef = useRef2(null);
808
+ const [lifecycleAbortController] = useState2(() => new AbortController());
809
+ const streamingContentRef = useRef2("");
810
+ const streamingMessageIdRef = useRef2(null);
811
+ const reasoningRef = useRef2([]);
812
+ const liveVoiceIdsRef = useRef2({});
813
+ const refreshSeqRef = useRef2(0);
814
+ const setActiveRunIdValue = useCallback3((runId) => {
712
815
  activeRunIdRef.current = runId;
713
816
  setActiveRunId(runId);
714
817
  }, []);
715
- const syncThreadsFromStorage = useCallback2(() => {
818
+ const syncThreadsFromStorage = useCallback3(() => {
716
819
  setThreads(storage.listThreads());
717
820
  }, [storage]);
718
- const upsertStoredThread = useCallback2(
821
+ const upsertStoredThread = useCallback3(
719
822
  (thread) => {
720
823
  storage.upsertThread(thread);
721
824
  syncThreadsFromStorage();
722
825
  },
723
826
  [storage, syncThreadsFromStorage]
724
827
  );
725
- useEffect(() => {
828
+ useEffect2(() => {
726
829
  messagesRef.current = messages;
727
830
  }, [messages]);
728
- const persistThread = useCallback2(
729
- (threadId, nextMessages, paging, options) => {
831
+ const persistThread = useCallback3(
832
+ (threadId, nextMessages, paging, options2) => {
730
833
  const existing = storage.getThread(threadId);
731
834
  const firstUser = nextMessages.find((message) => message.role === "user");
732
835
  upsertStoredThread({
733
836
  ...existing || {},
734
837
  id: threadId,
735
838
  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(),
839
+ updated_at: options2?.updatedAt || (options2?.touch ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updated_at) || (/* @__PURE__ */ new Date()).toISOString(),
737
840
  messages: nextMessages,
738
841
  isHydrated: true,
739
842
  hasOlderTurns: paging?.hasOlderTurns ?? hasOlderTurnsRef.current,
@@ -742,7 +845,7 @@ function useAgents24ChatController({
742
845
  },
743
846
  [storage, upsertStoredThread]
744
847
  );
745
- const markThreadRunStatus = useCallback2(
848
+ const markThreadRunStatus = useCallback3(
746
849
  (threadId, runId, status, lastEventSeq) => {
747
850
  const existing = storage.getThread(threadId);
748
851
  if (!existing || !runId) return;
@@ -772,7 +875,7 @@ function useAgents24ChatController({
772
875
  },
773
876
  [storage, upsertStoredThread]
774
877
  );
775
- const refresh = useCallback2(async () => {
878
+ const refresh = useCallback3(async () => {
776
879
  const seq = ++refreshSeqRef.current;
777
880
  setIsRefreshingThreads(true);
778
881
  try {
@@ -786,7 +889,7 @@ function useAgents24ChatController({
786
889
  if (!lifecycleAbortController.signal.aborted && seq === refreshSeqRef.current) setIsRefreshingThreads(false);
787
890
  }
788
891
  }, [lifecycleAbortController, storage, transport]);
789
- const applyThreadId = useCallback2(
892
+ const applyThreadId = useCallback3(
790
893
  (threadId, baseMessages) => {
791
894
  if (!threadId || activeThreadIdRef.current === threadId) return;
792
895
  activeThreadIdRef.current = threadId;
@@ -800,15 +903,15 @@ function useAgents24ChatController({
800
903
  streamingContentRef.current = value;
801
904
  setStreamingContent(value);
802
905
  };
803
- const setLoadingHistory = useCallback2((value) => {
906
+ const setLoadingHistory = useCallback3((value) => {
804
907
  isLoadingHistoryRef.current = value;
805
908
  setIsLoadingHistory(value);
806
909
  }, []);
807
- const setReasoningSteps = useCallback2((value) => {
910
+ const setReasoningSteps = useCallback3((value) => {
808
911
  reasoningRef.current = value || [];
809
912
  setCurrentReasoning(value || []);
810
913
  }, []);
811
- const detachActiveStream = useCallback2(() => {
914
+ const detachActiveStream = useCallback3(() => {
812
915
  const controller = abortControllerRef.current;
813
916
  abortControllerRef.current = null;
814
917
  abortDetachedStream(controller);
@@ -822,7 +925,7 @@ function useAgents24ChatController({
822
925
  setStreamingContent("");
823
926
  setCurrentReasoning([]);
824
927
  }, [setActiveRunIdValue]);
825
- const clearMissingThread = useCallback2(
928
+ const clearMissingThread = useCallback3(
826
929
  (threadId) => {
827
930
  storage.deleteThread?.(threadId);
828
931
  syncThreadsFromStorage();
@@ -844,7 +947,7 @@ function useAgents24ChatController({
844
947
  },
845
948
  [detachActiveStream, onActiveThreadIdChange, setLoadingHistory, storage, syncThreadsFromStorage]
846
949
  );
847
- const setLiveAssistantMessage = useCallback2(
950
+ const setLiveAssistantMessage = useCallback3(
848
951
  (input) => {
849
952
  setMessages((prev) => {
850
953
  const next = upsertStableAssistantMessage(prev, {
@@ -876,7 +979,7 @@ function useAgents24ChatController({
876
979
  },
877
980
  []
878
981
  );
879
- const finalizeAssistantMessage = useCallback2(
982
+ const finalizeAssistantMessage = useCallback3(
880
983
  (input) => {
881
984
  const content = input.error || input.assistantText.trim();
882
985
  if (!content) return input.baseMessages;
@@ -930,7 +1033,7 @@ function useAgents24ChatController({
930
1033
  },
931
1034
  [createId, persistThread, storage, upsertStoredThread]
932
1035
  );
933
- const loadThread = useCallback2(
1036
+ const loadThread = useCallback3(
934
1037
  async (threadId) => {
935
1038
  const seq = ++requestSeqRef.current;
936
1039
  setLoadingHistory(true);
@@ -975,7 +1078,7 @@ function useAgents24ChatController({
975
1078
  },
976
1079
  [clearMissingThread, lifecycleAbortController, onThreadDetailLoaded, pageSize, setLoadingHistory, transport, upsertStoredThread]
977
1080
  );
978
- const loadOlderTurns = useCallback2(async () => {
1081
+ const loadOlderTurns = useCallback3(async () => {
979
1082
  const threadId = activeThreadIdRef.current;
980
1083
  const beforeTurnIndex = nextBeforeTurnIndexRef.current;
981
1084
  if (!threadId || beforeTurnIndex === null || isLoadingOlderRef.current) return;
@@ -1011,7 +1114,7 @@ function useAgents24ChatController({
1011
1114
  isLoadingOlderRef.current = false;
1012
1115
  }
1013
1116
  }, [clearMissingThread, lifecycleAbortController, pageSize, persistThread, transport]);
1014
- const handleStreamEvent = useCallback2(
1117
+ const handleStreamEvent = useCallback3(
1015
1118
  (input) => {
1016
1119
  const { mode, event, assistantMessageId, startedAt, streamThreadIdRef, baseMessages } = input;
1017
1120
  const payload = event.payload || {};
@@ -1072,7 +1175,7 @@ function useAgents24ChatController({
1072
1175
  },
1073
1176
  [applyThreadId, finalizeAssistantMessage, markThreadRunStatus, onRuntimeEvent, onStreamErrorMessage, setActiveRunIdValue, setLiveAssistantMessage, setReasoningSteps]
1074
1177
  );
1075
- const runStream = useCallback2(
1178
+ const runStream = useCallback3(
1076
1179
  async (input) => {
1077
1180
  const startedAt = Date.now();
1078
1181
  const controller = new AbortController();
@@ -1195,18 +1298,26 @@ function useAgents24ChatController({
1195
1298
  },
1196
1299
  [createId, finalizeAssistantMessage, handleStreamEvent, onStreamErrorMessage, persistThread, refresh, setActiveRunIdValue, setReasoningSteps, transport]
1197
1300
  );
1198
- const handleSubmit = useCallback2(
1301
+ const handleSubmit = useCallback3(
1199
1302
  async (message) => {
1200
1303
  if (!message.text.trim() && !(message.files || []).length) return;
1201
1304
  await runStream({ mode: "submit", message });
1202
1305
  },
1203
1306
  [runStream]
1204
1307
  );
1205
- const attachRun = useCallback2(async (runId, threadId) => {
1308
+ const attachRun = useCallback3(async (runId, threadId) => {
1206
1309
  const resolvedThreadId = threadId ?? activeThreadIdRef.current;
1207
1310
  if (runId && resolvedThreadId) await runStream({ mode: "attach", runId, threadId: resolvedThreadId });
1208
1311
  }, [runStream]);
1209
- const handleStop = useCallback2(() => {
1312
+ const { pendingHitl, isResolvingHitl, resumeHitl } = useControllerHitl({
1313
+ messages,
1314
+ transport,
1315
+ activeRunId,
1316
+ activeRunIdRef,
1317
+ activeThreadIdRef,
1318
+ runStream
1319
+ });
1320
+ const handleStop = useCallback3(() => {
1210
1321
  const runId = activeRunIdRef.current;
1211
1322
  const partial = streamingContentRef.current;
1212
1323
  const liveMessageId = streamingMessageIdRef.current;
@@ -1230,56 +1341,25 @@ function useAgents24ChatController({
1230
1341
  });
1231
1342
  }
1232
1343
  }, [finalizeAssistantMessage, lastThinkingDurationMs, setActiveRunIdValue, setReasoningSteps, transport]);
1233
- useEffect(() => {
1344
+ useEffect2(() => {
1234
1345
  refresh().catch((error) => {
1235
1346
  if (!isAbortError(error) && !lifecycleAbortController.signal.aborted) {
1236
1347
  setThreads(storage.listThreads());
1237
1348
  }
1238
1349
  });
1239
1350
  }, [lifecycleAbortController, refresh, storage]);
1240
- useEffect(() => {
1351
+ useEffect2(() => {
1241
1352
  return () => {
1242
1353
  lifecycleAbortController.abort();
1243
1354
  abortDetachedStream(abortControllerRef.current);
1244
1355
  abortControllerRef.current = null;
1245
1356
  };
1246
1357
  }, [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(() => {
1358
+ useControllerThreadEvents({ transport, storage, refresh, setThreads });
1359
+ useEffect2(() => {
1280
1360
  syncThreadsFromStorage();
1281
1361
  }, [storageKey, syncThreadsFromStorage]);
1282
- useEffect(() => {
1362
+ useEffect2(() => {
1283
1363
  const previous = activeThreadIdRef.current;
1284
1364
  activeThreadIdRef.current = activeThreadId;
1285
1365
  if (activeThreadId && previous === activeThreadId && (activeRunIdRef.current || streamingMessageIdRef.current)) {
@@ -1323,14 +1403,14 @@ function useAgents24ChatController({
1323
1403
  }
1324
1404
  void loadThread(activeThreadId).catch(() => setLoadingHistory(false));
1325
1405
  }, [activeThreadId, detachActiveStream, loadThread, setLoadingHistory, storage]);
1326
- useEffect(() => {
1406
+ useEffect2(() => {
1327
1407
  const threadId = activeThreadId;
1328
1408
  if (!threadId || isLoadingHistory || loadedThreadIdRef.current !== threadId || activeRunIdRef.current) return;
1329
1409
  const runId = autoAttachRunIdFromThread(storage.getThread(threadId));
1330
1410
  if (!runId || reattachedRunIdRef.current === runId) return;
1331
1411
  void runStream({ mode: "attach", threadId, runId });
1332
1412
  }, [activeThreadId, isLoadingHistory, runStream, storage, storageKey]);
1333
- useEffect(() => {
1413
+ useEffect2(() => {
1334
1414
  const threadId = activeThreadId;
1335
1415
  if (!threadId || isLoadingHistoryRef.current || activeRunIdRef.current || streamingMessageIdRef.current) {
1336
1416
  return;
@@ -1371,7 +1451,7 @@ function useAgents24ChatController({
1371
1451
  setMessages,
1372
1452
  storage
1373
1453
  });
1374
- const loadThreadById = useCallback2(
1454
+ const loadThreadById = useCallback3(
1375
1455
  async (threadId) => {
1376
1456
  if (!threadId) return;
1377
1457
  setIsSelectingThread(true);
@@ -1390,7 +1470,7 @@ function useAgents24ChatController({
1390
1470
  [detachActiveStream, loadThread, onActiveThreadIdChange, storage]
1391
1471
  );
1392
1472
  const activeThread = activeThreadId ? storage.getThread(activeThreadId) || threads.find((thread) => thread.id === activeThreadId) || null : null;
1393
- return useMemo(() => ({
1473
+ return useMemo2(() => ({
1394
1474
  threads,
1395
1475
  activeThreadId,
1396
1476
  activeThread,
@@ -1410,8 +1490,11 @@ function useAgents24ChatController({
1410
1490
  copiedMessageId,
1411
1491
  lastThinkingDurationMs,
1412
1492
  activeRunId,
1493
+ pendingHitl,
1494
+ isResolvingHitl,
1413
1495
  handleSubmit,
1414
1496
  attachRun,
1497
+ resumeHitl,
1415
1498
  handleStop,
1416
1499
  handleCopy,
1417
1500
  handleLike,
@@ -1450,8 +1533,11 @@ function useAgents24ChatController({
1450
1533
  loadThreadById,
1451
1534
  loadOlderTurns,
1452
1535
  messages,
1536
+ isResolvingHitl,
1453
1537
  onSourceClick,
1538
+ pendingHitl,
1454
1539
  refresh,
1540
+ resumeHitl,
1455
1541
  streamingContent,
1456
1542
  streamingMessageId,
1457
1543
  startNewThread,
@@ -1680,9 +1766,10 @@ var createFetchChatTransport = ({
1680
1766
  method: "POST",
1681
1767
  headers: jsonHeaders(await loadHeaders()),
1682
1768
  body: JSON.stringify({
1683
- schema_version: "agents24.hitl.resume.v1",
1769
+ schema_version: "agents24.hitl.resume.v2",
1684
1770
  interrupt_id: input.interruptId,
1685
- decisions: input.decisions,
1771
+ action: input.action,
1772
+ comment: input.comment,
1686
1773
  client: input.client
1687
1774
  })
1688
1775
  });