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