@personaai/react 0.7.10 → 0.8.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
@@ -440,6 +440,11 @@ function useChat(options = {}) {
440
440
  const chatLogger = (0, import_react2.useMemo)(() => logger.child("chat"), [logger]);
441
441
  const agentId = options.agentId || defaultAgentId;
442
442
  const threadId = options.threadId;
443
+ const [internalThreadId, setInternalThreadId] = (0, import_react2.useState)(void 0);
444
+ const effectiveThreadId = threadId ?? internalThreadId;
445
+ (0, import_react2.useEffect)(() => {
446
+ if (threadId !== void 0) setInternalThreadId(threadId);
447
+ }, [threadId]);
443
448
  const didLogInitRef = (0, import_react2.useRef)(false);
444
449
  if (!didLogInitRef.current) {
445
450
  didLogInitRef.current = true;
@@ -476,9 +481,10 @@ function useChat(options = {}) {
476
481
  }, [messages]);
477
482
  const abortControllerRef = (0, import_react2.useRef)(null);
478
483
  const loadedThreadIdRef = (0, import_react2.useRef)(void 0);
479
- const voiceThreadRef = (0, import_react2.useRef)(threadId);
484
+ const voiceThreadRef = (0, import_react2.useRef)(effectiveThreadId);
480
485
  const voicePrevLenRef = (0, import_react2.useRef)(0);
481
486
  const voiceStreamingIdRef = (0, import_react2.useRef)(null);
487
+ const voiceUserPartialIdRef = (0, import_react2.useRef)(null);
482
488
  const stop = (0, import_react2.useCallback)(() => {
483
489
  if (abortControllerRef.current) {
484
490
  chatLogger.info("stop streaming", {});
@@ -503,7 +509,25 @@ function useChat(options = {}) {
503
509
  setFiles({});
504
510
  setTodos([]);
505
511
  setPresentedFile(null);
512
+ loadedThreadIdRef.current = void 0;
506
513
  }, [stop, chatLogger, messages.length]);
514
+ const startNewChat = (0, import_react2.useCallback)(() => {
515
+ chatLogger.info("startNewChat \u2014 entering ephemeral mode");
516
+ stop();
517
+ setMessages([]);
518
+ setError(null);
519
+ setInterrupt(null);
520
+ setFiles({});
521
+ setTodos([]);
522
+ setPresentedFile(null);
523
+ setInput("");
524
+ loadedThreadIdRef.current = void 0;
525
+ voiceThreadRef.current = void 0;
526
+ voicePrevLenRef.current = 0;
527
+ voiceStreamingIdRef.current = null;
528
+ voiceUserPartialIdRef.current = null;
529
+ setInternalThreadId(void 0);
530
+ }, [stop, chatLogger]);
507
531
  const loadThreadMessages = (0, import_react2.useCallback)(
508
532
  async (id) => {
509
533
  chatLogger.debug("loadThreadMessages start", { threadId: id });
@@ -535,6 +559,7 @@ function useChat(options = {}) {
535
559
  setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
536
560
  setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
537
561
  setTodos(data?.state?.todos ?? []);
562
+ loadedThreadIdRef.current = id;
538
563
  chatLogger.info("loadThreadMessages succeeded", {
539
564
  threadId: id,
540
565
  messageCount: loaded.length
@@ -555,6 +580,7 @@ function useChat(options = {}) {
555
580
  error: errorObj.message
556
581
  });
557
582
  setError(errorObj);
583
+ if (loadedThreadIdRef.current === id) loadedThreadIdRef.current = void 0;
558
584
  return [];
559
585
  } finally {
560
586
  setIsLoadingHistory(false);
@@ -564,33 +590,33 @@ function useChat(options = {}) {
564
590
  [fetchWithAuth, chatLogger]
565
591
  );
566
592
  (0, import_react2.useEffect)(() => {
567
- if (!threadId || isStreaming) {
568
- chatLogger.trace("auto-load skipped", { threadId, isStreaming });
593
+ if (!effectiveThreadId || isStreaming) {
594
+ chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
569
595
  return;
570
596
  }
571
- if (loadedThreadIdRef.current === threadId) {
572
- chatLogger.trace("auto-load already loaded", { threadId });
597
+ if (loadedThreadIdRef.current === effectiveThreadId) {
598
+ chatLogger.trace("auto-load already loaded", { threadId: effectiveThreadId });
573
599
  return;
574
600
  }
575
601
  if (messages.length > 0) {
576
602
  chatLogger.trace("auto-load has messages", {
577
- threadId,
603
+ threadId: effectiveThreadId,
578
604
  count: messages.length
579
605
  });
580
606
  return;
581
607
  }
582
- loadedThreadIdRef.current = threadId;
583
- chatLogger.info("auto-load thread history", { threadId });
584
- chatLogger.debug("loadThreadMessages trigger", { threadId });
585
- void loadThreadMessages(threadId);
586
- }, [threadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
608
+ chatLogger.info("auto-load thread history", { threadId: effectiveThreadId });
609
+ chatLogger.debug("loadThreadMessages trigger", { threadId: effectiveThreadId });
610
+ void loadThreadMessages(effectiveThreadId);
611
+ }, [effectiveThreadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
587
612
  const voice = options.voice;
588
613
  (0, import_react2.useEffect)(() => {
589
614
  if (!voice) return;
590
- if (voiceThreadRef.current !== threadId) {
591
- voiceThreadRef.current = threadId;
615
+ if (voiceThreadRef.current !== effectiveThreadId) {
616
+ voiceThreadRef.current = effectiveThreadId;
592
617
  voicePrevLenRef.current = voice.transcript.length;
593
618
  voiceStreamingIdRef.current = null;
619
+ voiceUserPartialIdRef.current = null;
594
620
  return;
595
621
  }
596
622
  const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
@@ -598,15 +624,40 @@ function useChat(options = {}) {
598
624
  if (curLen < voicePrevLenRef.current) {
599
625
  voicePrevLenRef.current = 0;
600
626
  voiceStreamingIdRef.current = null;
627
+ voiceUserPartialIdRef.current = null;
601
628
  }
602
629
  if (!isVoiceActive) {
603
- voicePrevLenRef.current = voice.transcript.length;
630
+ if (curLen > voicePrevLenRef.current) {
631
+ const pendingLines = voice.transcript.slice(voicePrevLenRef.current);
632
+ voicePrevLenRef.current = curLen;
633
+ for (const line of pendingLines) {
634
+ const text = (line.text || "").trim();
635
+ if (!text) continue;
636
+ const role = line.speaker === "user" ? "user" : "assistant";
637
+ const voiceId = `voice-${line.id}`;
638
+ setMessages((prev) => {
639
+ if (prev.some((m) => m.id === voiceId)) return prev;
640
+ if (prev.some((m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text)) return prev;
641
+ const last = prev[prev.length - 1];
642
+ if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
643
+ const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
644
+ return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
645
+ }
646
+ return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
647
+ });
648
+ }
649
+ } else {
650
+ voicePrevLenRef.current = voice.transcript.length;
651
+ }
604
652
  if (voiceStreamingIdRef.current) {
605
653
  const doneId = voiceStreamingIdRef.current;
606
654
  voiceStreamingIdRef.current = null;
607
- setMessages(
608
- (prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
609
- );
655
+ setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
656
+ }
657
+ if (voiceUserPartialIdRef.current) {
658
+ const doneId = voiceUserPartialIdRef.current;
659
+ voiceUserPartialIdRef.current = null;
660
+ setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
610
661
  }
611
662
  return;
612
663
  }
@@ -633,54 +684,71 @@ function useChat(options = {}) {
633
684
  });
634
685
  }
635
686
  }
636
- const partialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
637
- if (partialText) {
687
+ const agentPartialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
688
+ const userPartialText = voice.partial?.speaker === "user" ? voice.partial.text.trim() : "";
689
+ if (agentPartialText) {
690
+ if (voiceUserPartialIdRef.current) {
691
+ const doneId = voiceUserPartialIdRef.current;
692
+ voiceUserPartialIdRef.current = null;
693
+ setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
694
+ }
638
695
  setMessages((prev) => {
639
696
  const last = prev[prev.length - 1];
640
697
  if (last?.id.startsWith("voice-") && last.role === "assistant") {
641
698
  voiceStreamingIdRef.current = last.id;
642
- return [...prev.slice(0, -1), { ...last, content: partialText, isStreaming: true }];
699
+ return [...prev.slice(0, -1), { ...last, content: agentPartialText, isStreaming: true }];
643
700
  }
644
701
  const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
645
702
  voiceStreamingIdRef.current = id;
646
- return [...prev, { id, role: "assistant", content: partialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
703
+ return [...prev, { id, role: "assistant", content: agentPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
647
704
  });
648
- } else if (voice.state !== "speaking" && voiceStreamingIdRef.current) {
649
- const doneId = voiceStreamingIdRef.current;
650
- voiceStreamingIdRef.current = null;
651
- setMessages(
652
- (prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
653
- );
654
- }
655
- }, [voice?.transcript, voice?.partial, voice?.state, threadId]);
656
- const sendMessage = (0, import_react2.useCallback)(
657
- async (contentToSend, overrideOptions) => {
658
- const prompt = (contentToSend ?? input).trim();
659
- if (!prompt || isStreaming) {
660
- chatLogger.trace("sendMessage skipped", {
661
- hasPrompt: !!prompt,
662
- isStreaming
663
- });
664
- return;
705
+ } else if (userPartialText) {
706
+ if (voiceStreamingIdRef.current) {
707
+ const doneId = voiceStreamingIdRef.current;
708
+ voiceStreamingIdRef.current = null;
709
+ setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
665
710
  }
711
+ setMessages((prev) => {
712
+ const last = prev[prev.length - 1];
713
+ if (last?.id.startsWith("voice-") && last.role === "user" && last.isStreaming) {
714
+ voiceUserPartialIdRef.current = last.id;
715
+ return [...prev.slice(0, -1), { ...last, content: userPartialText, isStreaming: true }];
716
+ }
717
+ const id = voiceUserPartialIdRef.current || `voice-partial-user-${Date.now()}`;
718
+ voiceUserPartialIdRef.current = id;
719
+ return [...prev, { id, role: "user", content: userPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
720
+ });
721
+ } else {
722
+ if (voiceStreamingIdRef.current && voice.state !== "speaking") {
723
+ const doneId = voiceStreamingIdRef.current;
724
+ voiceStreamingIdRef.current = null;
725
+ setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
726
+ }
727
+ if (voiceUserPartialIdRef.current) {
728
+ const doneId = voiceUserPartialIdRef.current;
729
+ voiceUserPartialIdRef.current = null;
730
+ setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
731
+ }
732
+ }
733
+ }, [voice?.transcript, voice?.partial, voice?.state, effectiveThreadId]);
734
+ const doSend = (0, import_react2.useCallback)(
735
+ async (prompt, baseMessages, overrideOptions) => {
666
736
  const targetAgentId = overrideOptions?.agentId || agentId;
667
737
  if (!targetAgentId) {
668
738
  const err = new Error("No Agent ID specified for useChat.");
669
739
  chatLogger.warn("sendMessage no agentId", {});
670
- chatLogger.error("sendMessage failed \u2014 no agent", {
671
- error: err.message
672
- });
740
+ chatLogger.error("sendMessage failed \u2014 no agent", { error: err.message });
673
741
  setError(err);
674
742
  options.onError?.(err);
675
- return;
743
+ return false;
676
744
  }
677
745
  chatLogger.debug("sendMessage start", {
678
746
  promptPreview: prompt.slice(0, 100),
679
747
  agentId: targetAgentId,
680
- threadId: threadId ?? overrideOptions?.threadId,
748
+ threadId: effectiveThreadId ?? overrideOptions?.threadId,
681
749
  hasResume: !!overrideOptions?.resume,
682
750
  hasContextOverride: !!overrideOptions?.contextOverride,
683
- messageCount: messages.length
751
+ messageCount: baseMessages.length
684
752
  });
685
753
  chatLogger.trace("sendMessage details", {
686
754
  agentId: targetAgentId,
@@ -703,7 +771,7 @@ function useChat(options = {}) {
703
771
  isStreaming: true,
704
772
  toolCalls: []
705
773
  };
706
- const nextMessages = [...messages, userMessage];
774
+ const nextMessages = [...baseMessages, userMessage];
707
775
  setMessages([...nextMessages, placeholderAssistant]);
708
776
  setInput("");
709
777
  setIsStreaming(true);
@@ -713,9 +781,7 @@ function useChat(options = {}) {
713
781
  abortControllerRef.current = controller;
714
782
  const finalizeReasoning = () => {
715
783
  setMessages(
716
- (prev) => prev.map(
717
- (m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m
718
- )
784
+ (prev) => prev.map((m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m)
719
785
  );
720
786
  };
721
787
  try {
@@ -723,7 +789,24 @@ function useChat(options = {}) {
723
789
  role: m.role === "assistant" ? "assistant" : "user",
724
790
  content: m.content
725
791
  }));
726
- const resolvedThreadId = await (overrideOptions?.threadId ?? options.threadId);
792
+ let resolvedThreadId = await overrideOptions?.threadId ?? effectiveThreadId;
793
+ if (!resolvedThreadId) {
794
+ chatLogger.info("minting real thread for ephemeral chat", { agentId: targetAgentId });
795
+ const createRes = await fetchWithAuth("/threads", {
796
+ method: "POST",
797
+ headers: { "Content-Type": "application/json" },
798
+ body: JSON.stringify({ agentId: targetAgentId })
799
+ });
800
+ if (!createRes.ok) throw new Error(`Failed to create thread: ${createRes.statusText}`);
801
+ const createBody = await createRes.json();
802
+ const created = createBody?.data ?? createBody;
803
+ resolvedThreadId = created._id ?? created.id;
804
+ if (!resolvedThreadId) throw new Error("Thread creation returned no id");
805
+ setInternalThreadId(resolvedThreadId);
806
+ loadedThreadIdRef.current = resolvedThreadId;
807
+ options.onThreadCreated?.(resolvedThreadId);
808
+ chatLogger.info("ephemeral chat minted", { threadId: resolvedThreadId });
809
+ }
727
810
  chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
728
811
  const token = getAuthToken ? await getAuthToken() : null;
729
812
  const resolvedHookContext = typeof options.context === "function" ? options.context() : options.context;
@@ -752,27 +835,12 @@ function useChat(options = {}) {
752
835
  signal: controller.signal
753
836
  });
754
837
  if (!stream.ok) {
755
- chatLogger.warn("SSE stream not ok", {
756
- status: stream.status,
757
- errorText: stream.errorText
758
- });
759
- chatLogger.error("chat stream failed", {
760
- agentId: targetAgentId,
761
- status: stream.status,
762
- error: stream.errorText
763
- });
764
- throw new Error(
765
- `Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`
766
- );
838
+ chatLogger.warn("SSE stream not ok", { status: stream.status, errorText: stream.errorText });
839
+ chatLogger.error("chat stream failed", { agentId: targetAgentId, status: stream.status, error: stream.errorText });
840
+ throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
767
841
  }
768
- chatLogger.debug("SSE stream opened", {
769
- agentId: targetAgentId,
770
- status: stream.status
771
- });
772
- chatLogger.info("chat stream started", {
773
- agentId: targetAgentId,
774
- threadId: resolvedThreadId
775
- });
842
+ chatLogger.debug("SSE stream opened", { agentId: targetAgentId, status: stream.status });
843
+ chatLogger.info("chat stream started", { agentId: targetAgentId, threadId: resolvedThreadId });
776
844
  const reader = stream.reader;
777
845
  let buffer = "";
778
846
  let accumulatedText = "";
@@ -783,23 +851,12 @@ function useChat(options = {}) {
783
851
  const patchAssistant = (patch) => {
784
852
  setMessages(
785
853
  (prev) => prev.map(
786
- (msg) => msg.id === assistantMessageId ? {
787
- ...msg,
788
- toolCalls: Array.from(toolCallsMap.values()),
789
- ...patch
790
- } : msg
854
+ (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
791
855
  )
792
856
  );
793
857
  };
794
858
  const insertReasoningMessage = (id, seq) => {
795
- const msg = {
796
- id,
797
- role: "reasoning",
798
- content: "",
799
- createdAt: /* @__PURE__ */ new Date(),
800
- isStreaming: true,
801
- seq
802
- };
859
+ const msg = { id, role: "reasoning", content: "", createdAt: /* @__PURE__ */ new Date(), isStreaming: true, seq };
803
860
  setMessages((prev) => {
804
861
  const idx = prev.findIndex((m) => m.id === assistantMessageId);
805
862
  if (idx === -1) return [...prev, msg];
@@ -824,16 +881,11 @@ function useChat(options = {}) {
824
881
  chatLogger.trace("stream event", { type: event.type, event });
825
882
  options.onEvent?.(event);
826
883
  if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
827
- chatLogger.debug("text chunk", {
828
- deltaLength: event.delta.length
829
- });
884
+ chatLogger.debug("text chunk", { deltaLength: event.delta.length });
830
885
  accumulatedText += event.delta;
831
886
  patchAssistant({ content: accumulatedText, isStreaming: true });
832
887
  } else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
833
- chatLogger.debug("tool call chunk", {
834
- toolCallId: event.toolCallId,
835
- toolCallName: event.toolCallName
836
- });
888
+ chatLogger.debug("tool call chunk", { toolCallId: event.toolCallId, toolCallName: event.toolCallName });
837
889
  const existing = toolCallsMap.get(event.toolCallId);
838
890
  if (existing) {
839
891
  existing.args = (existing.args || "") + (event.delta || "");
@@ -847,17 +899,12 @@ function useChat(options = {}) {
847
899
  }
848
900
  patchAssistant({});
849
901
  } else if (event.type === "TOOL_CALL_RESULT") {
850
- chatLogger.debug("tool call result", {
851
- toolCallId: event.toolCallId
852
- });
902
+ chatLogger.debug("tool call result", { toolCallId: event.toolCallId });
853
903
  const existing = toolCallsMap.get(event.toolCallId);
854
904
  if (existing) {
855
905
  existing.result = event.content;
856
906
  existing.isError = isErrorToolContent(event.content);
857
- if (existing.isError)
858
- chatLogger.warn("tool call error", {
859
- toolCallId: event.toolCallId
860
- });
907
+ if (existing.isError) chatLogger.warn("tool call error", { toolCallId: event.toolCallId });
861
908
  if (existing.toolName === "present_file" && !existing.isError) {
862
909
  const presented = parsePresentedFile(event.content);
863
910
  if (presented) {
@@ -872,12 +919,10 @@ function useChat(options = {}) {
872
919
  fileCount: Object.keys(event.snapshot.files ?? {}).length,
873
920
  todoCount: event.snapshot.todos?.length ?? 0
874
921
  });
875
- setFiles(normalizeWorkspaceFiles(event.snapshot.files));
876
- setTodos(event.snapshot.todos);
922
+ setFiles(normalizeWorkspaceFiles(event.snapshot.files ?? {}));
923
+ setTodos(event.snapshot.todos ?? []);
877
924
  } else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
878
- chatLogger.debug("reasoning start", {
879
- messageId: event.messageId
880
- });
925
+ chatLogger.debug("reasoning start", { messageId: event.messageId });
881
926
  activeReasoningId = event.messageId;
882
927
  reasoningById.set(event.messageId, { content: "" });
883
928
  insertReasoningMessage(event.messageId, streamSeq++);
@@ -892,61 +937,31 @@ function useChat(options = {}) {
892
937
  }
893
938
  const entry = reasoningById.get(rid);
894
939
  entry.content += event.delta;
895
- chatLogger.trace("reasoning content", {
896
- messageId: rid,
897
- deltaLength: event.delta?.length ?? 0
898
- });
899
- setMessages(
900
- (prev) => prev.map(
901
- (m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
902
- )
903
- );
940
+ chatLogger.trace("reasoning content", { messageId: rid, deltaLength: event.delta?.length ?? 0 });
941
+ setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m));
904
942
  } else if (event.type === "REASONING_END") {
905
- chatLogger.debug("reasoning end", {
906
- messageId: activeReasoningId
907
- });
943
+ chatLogger.debug("reasoning end", { messageId: activeReasoningId });
908
944
  const rid = activeReasoningId;
909
945
  activeReasoningId = null;
910
946
  if (rid) {
911
- setMessages(
912
- (prev) => prev.map(
913
- (m) => m.id === rid ? { ...m, isStreaming: false } : m
914
- )
915
- );
947
+ setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m));
916
948
  }
917
949
  } else if (event.type === "CUSTOM") {
918
950
  chatLogger.debug("custom event", { name: event.name });
919
951
  if (event.name === "hitl_request") {
920
952
  const value2 = event.value;
921
- chatLogger.info("hitl interrupt", {
922
- actionCount: value2.actionRequests?.length ?? 0
923
- });
924
- setInterrupt({
925
- kind: "hitl",
926
- actionRequests: value2.actionRequests,
927
- reviewConfigs: value2.reviewConfigs
928
- });
953
+ chatLogger.info("hitl interrupt", { actionCount: value2.actionRequests?.length ?? 0 });
954
+ setInterrupt({ kind: "hitl", actionRequests: value2.actionRequests, reviewConfigs: value2.reviewConfigs });
929
955
  } else if (event.name === "clarification_request") {
930
956
  const value2 = event.value;
931
- chatLogger.info("clarification interrupt", {
932
- questionCount: value2.questions?.length ?? 0
933
- });
934
- setInterrupt({
935
- kind: "clarification",
936
- questions: value2.questions
937
- });
957
+ chatLogger.info("clarification interrupt", { questionCount: value2.questions?.length ?? 0 });
958
+ setInterrupt({ kind: "clarification", questions: value2.questions });
938
959
  } else if (event.name === "subagent_activity") {
939
960
  const { toolCallId, ...entry } = event.value;
940
- chatLogger.trace("subagent activity", {
941
- toolCallId,
942
- kind: entry.kind
943
- });
961
+ chatLogger.trace("subagent activity", { toolCallId, kind: entry.kind });
944
962
  const existing = toolCallsMap.get(toolCallId);
945
963
  if (existing) {
946
- existing.subagentActivity = [
947
- ...existing.subagentActivity || [],
948
- entry
949
- ];
964
+ existing.subagentActivity = [...existing.subagentActivity || [], entry];
950
965
  patchAssistant({});
951
966
  }
952
967
  } else if (event.name === "mcp_app") {
@@ -954,35 +969,21 @@ function useChat(options = {}) {
954
969
  if (val?.toolCallId && val?.resourceUri && val?.mcpId) {
955
970
  const existing = toolCallsMap.get(val.toolCallId);
956
971
  if (existing) {
957
- existing.mcpApp = {
958
- resourceUri: val.resourceUri,
959
- mcpId: val.mcpId
960
- };
972
+ existing.mcpApp = { resourceUri: val.resourceUri, mcpId: val.mcpId };
961
973
  patchAssistant({});
962
974
  }
963
975
  }
964
976
  }
965
977
  } else if (event.type === "RUN_ERROR") {
966
- chatLogger.warn("run error", {
967
- message: event.message,
968
- code: event.code
969
- });
970
- chatLogger.error("stream run error", {
971
- code: event.code,
972
- message: event.message
973
- });
978
+ chatLogger.warn("run error", { message: event.message, code: event.code });
979
+ chatLogger.error("stream run error", { code: event.code, message: event.message });
974
980
  throw new Error(event.message || "Stream error from agent");
975
981
  } else {
976
982
  chatLogger.trace("unhandled event", { type: event.type });
977
983
  }
978
984
  } catch (e) {
979
- if (e instanceof Error && e.message.startsWith("Stream error")) {
980
- throw e;
981
- }
982
- chatLogger.warn("event parse error", {
983
- raw: raw.slice(0, 200),
984
- error: e instanceof Error ? e.message : String(e)
985
- });
985
+ if (e instanceof Error && e.message.startsWith("Stream error")) throw e;
986
+ chatLogger.warn("event parse error", { raw: raw.slice(0, 200), error: e instanceof Error ? e.message : String(e) });
986
987
  }
987
988
  }
988
989
  }
@@ -994,75 +995,55 @@ function useChat(options = {}) {
994
995
  isStreaming: false,
995
996
  toolCalls: Array.from(toolCallsMap.values())
996
997
  };
997
- setMessages(
998
- (prev) => prev.map(
999
- (msg) => msg.id === assistantMessageId ? finalMessage : msg
1000
- )
1001
- );
1002
- chatLogger.info("sendMessage succeeded", {
1003
- agentId: targetAgentId,
1004
- textLength: accumulatedText.length,
1005
- toolCallCount: toolCallsMap.size
1006
- });
1007
- chatLogger.debug("sendMessage completed", {
1008
- agentId: targetAgentId,
1009
- textLength: accumulatedText.length,
1010
- eventCount: toolCallsMap.size + 1
1011
- });
998
+ setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg));
999
+ chatLogger.info("sendMessage succeeded", { agentId: targetAgentId, textLength: accumulatedText.length, toolCallCount: toolCallsMap.size });
1000
+ chatLogger.debug("sendMessage completed", { agentId: targetAgentId, textLength: accumulatedText.length, eventCount: toolCallsMap.size + 1 });
1012
1001
  options.onFinish?.(finalMessage);
1002
+ return true;
1013
1003
  } catch (err) {
1014
1004
  if (controller.signal.aborted) {
1015
1005
  chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
1016
1006
  chatLogger.debug("stream aborted", { agentId: targetAgentId });
1017
- setMessages(
1018
- (prev) => prev.map(
1019
- (msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg
1020
- )
1021
- );
1022
- return;
1007
+ setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
1008
+ return false;
1023
1009
  }
1024
1010
  const errorObj = err instanceof Error ? err : new Error(String(err));
1025
- chatLogger.warn("sendMessage failed", {
1026
- agentId: targetAgentId,
1027
- error: errorObj.message
1028
- });
1029
- chatLogger.error("sendMessage error", {
1030
- agentId: targetAgentId,
1031
- error: errorObj.message
1032
- });
1011
+ chatLogger.warn("sendMessage failed", { agentId: targetAgentId, error: errorObj.message });
1012
+ chatLogger.error("sendMessage error", { agentId: targetAgentId, error: errorObj.message });
1033
1013
  setError(errorObj);
1034
1014
  options.onError?.(errorObj);
1035
1015
  setMessages(
1036
1016
  (prev) => prev.map(
1037
- (msg) => msg.id === assistantMessageId ? {
1038
- ...msg,
1039
- content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`,
1040
- isStreaming: false
1041
- } : msg
1017
+ (msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
1042
1018
  )
1043
1019
  );
1020
+ return false;
1044
1021
  } finally {
1045
1022
  setIsStreaming(false);
1046
1023
  abortControllerRef.current = null;
1047
1024
  finalizeReasoning();
1048
- chatLogger.debug("sendMessage finally", {
1049
- agentId: targetAgentId,
1050
- isStreaming: false
1051
- });
1025
+ chatLogger.debug("sendMessage finally", { agentId: targetAgentId, isStreaming: false });
1052
1026
  chatLogger.trace("sendMessage end", { agentId: targetAgentId });
1053
1027
  }
1054
1028
  },
1055
- [
1056
- agentId,
1057
- baseUrl,
1058
- getAuthToken,
1059
- input,
1060
- isStreaming,
1061
- messages,
1062
- options,
1063
- chatLogger,
1064
- threadId
1065
- ]
1029
+ [agentId, baseUrl, getAuthToken, fetchWithAuth, options, chatLogger, effectiveThreadId]
1030
+ );
1031
+ const sendMessage = (0, import_react2.useCallback)(
1032
+ async (contentToSend, overrideOptions) => {
1033
+ const prompt = (contentToSend ?? input).trim();
1034
+ if (!prompt || isStreaming) {
1035
+ chatLogger.trace("sendMessage skipped", {
1036
+ hasPrompt: !!prompt,
1037
+ isStreaming
1038
+ });
1039
+ if (isStreaming) {
1040
+ chatLogger.warn("sendMessage dropped while streaming \u2014 caller should queue or disable send", {});
1041
+ }
1042
+ return false;
1043
+ }
1044
+ return doSend(prompt, messages, overrideOptions);
1045
+ },
1046
+ [input, isStreaming, messages, doSend, chatLogger]
1066
1047
  );
1067
1048
  const handleInputChange = (0, import_react2.useCallback)(
1068
1049
  (e) => {
@@ -1079,13 +1060,13 @@ function useChat(options = {}) {
1079
1060
  },
1080
1061
  [sendMessage, chatLogger]
1081
1062
  );
1082
- const reload = (0, import_react2.useCallback)(() => {
1063
+ const reload = (0, import_react2.useCallback)(async () => {
1083
1064
  if (messages.length === 0 || isStreaming) {
1084
1065
  chatLogger.trace("reload skipped", {
1085
1066
  messageCount: messages.length,
1086
1067
  isStreaming
1087
1068
  });
1088
- return;
1069
+ return false;
1089
1070
  }
1090
1071
  let lastUserIndex = -1;
1091
1072
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -1096,16 +1077,16 @@ function useChat(options = {}) {
1096
1077
  }
1097
1078
  if (lastUserIndex === -1) {
1098
1079
  chatLogger.warn("reload no user message", {});
1099
- return;
1080
+ return false;
1100
1081
  }
1101
1082
  const lastUserMessage = messages[lastUserIndex];
1083
+ const truncated = messages.slice(0, lastUserIndex);
1102
1084
  chatLogger.info("reload", { messageId: lastUserMessage.id });
1103
1085
  chatLogger.debug("reload last user", {
1104
1086
  preview: lastUserMessage.content.slice(0, 100)
1105
1087
  });
1106
- setMessages(messages.slice(0, lastUserIndex));
1107
- void sendMessage(lastUserMessage.content);
1108
- }, [isStreaming, messages, sendMessage, chatLogger]);
1088
+ return doSend(lastUserMessage.content, truncated);
1089
+ }, [isStreaming, messages, doSend, chatLogger]);
1109
1090
  const resumeInterrupt = (0, import_react2.useCallback)(
1110
1091
  (resume, displayContent) => {
1111
1092
  chatLogger.info("resumeInterrupt", {
@@ -1153,6 +1134,9 @@ function useChat(options = {}) {
1153
1134
  stop,
1154
1135
  reload,
1155
1136
  clear,
1137
+ startNewChat,
1138
+ currentThreadId: effectiveThreadId,
1139
+ isEphemeral: !effectiveThreadId,
1156
1140
  setMessages,
1157
1141
  loadThreadMessages
1158
1142
  };
@@ -2115,7 +2099,7 @@ function useMcp(options = {}) {
2115
2099
 
2116
2100
  // src/index.ts
2117
2101
  var import_logger2 = require("@personaai/logger");
2118
- var VERSION = "0.7.10";
2102
+ var VERSION = "0.8.0";
2119
2103
  // Annotate the CommonJS export names for ESM import in node:
2120
2104
  0 && (module.exports = {
2121
2105
  PersonaProvider,