@personaai/react 0.7.9 → 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
@@ -374,6 +374,37 @@ function parsePresentedFile(content) {
374
374
  return null;
375
375
  }
376
376
  }
377
+ function parseSandboxCommand(tc) {
378
+ let command = tc.args ?? "";
379
+ try {
380
+ const parsedArgs = JSON.parse(tc.args || "{}");
381
+ if (typeof parsedArgs.command === "string") command = parsedArgs.command;
382
+ } catch {
383
+ }
384
+ let output;
385
+ let exitCode;
386
+ if (tc.result) {
387
+ try {
388
+ const parsedResult = JSON.parse(tc.result);
389
+ if (typeof parsedResult.output === "string") {
390
+ output = parsedResult.output;
391
+ exitCode = typeof parsedResult.exitCode === "number" ? parsedResult.exitCode : null;
392
+ } else {
393
+ output = tc.result;
394
+ }
395
+ } catch {
396
+ output = tc.result;
397
+ }
398
+ }
399
+ return {
400
+ toolCallId: tc.toolCallId,
401
+ command,
402
+ output,
403
+ exitCode,
404
+ status: tc.isError ? "error" : tc.result ? "done" : "running",
405
+ seq: tc.seq
406
+ };
407
+ }
377
408
  function normalizeWorkspaceFiles(raw) {
378
409
  const normalized = {};
379
410
  for (const [path, file] of Object.entries(raw || {})) {
@@ -409,6 +440,11 @@ function useChat(options = {}) {
409
440
  const chatLogger = (0, import_react2.useMemo)(() => logger.child("chat"), [logger]);
410
441
  const agentId = options.agentId || defaultAgentId;
411
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]);
412
448
  const didLogInitRef = (0, import_react2.useRef)(false);
413
449
  if (!didLogInitRef.current) {
414
450
  didLogInitRef.current = true;
@@ -434,11 +470,21 @@ function useChat(options = {}) {
434
470
  const [files, setFiles] = (0, import_react2.useState)({});
435
471
  const [todos, setTodos] = (0, import_react2.useState)([]);
436
472
  const [presentedFile, setPresentedFile] = (0, import_react2.useState)(null);
473
+ const sandboxCommands = (0, import_react2.useMemo)(() => {
474
+ const commands = [];
475
+ for (const message of messages) {
476
+ for (const tc of message.toolCalls ?? []) {
477
+ if (tc.toolName === "execute") commands.push(parseSandboxCommand(tc));
478
+ }
479
+ }
480
+ return commands;
481
+ }, [messages]);
437
482
  const abortControllerRef = (0, import_react2.useRef)(null);
438
483
  const loadedThreadIdRef = (0, import_react2.useRef)(void 0);
439
- const voiceThreadRef = (0, import_react2.useRef)(threadId);
484
+ const voiceThreadRef = (0, import_react2.useRef)(effectiveThreadId);
440
485
  const voicePrevLenRef = (0, import_react2.useRef)(0);
441
486
  const voiceStreamingIdRef = (0, import_react2.useRef)(null);
487
+ const voiceUserPartialIdRef = (0, import_react2.useRef)(null);
442
488
  const stop = (0, import_react2.useCallback)(() => {
443
489
  if (abortControllerRef.current) {
444
490
  chatLogger.info("stop streaming", {});
@@ -463,7 +509,25 @@ function useChat(options = {}) {
463
509
  setFiles({});
464
510
  setTodos([]);
465
511
  setPresentedFile(null);
512
+ loadedThreadIdRef.current = void 0;
466
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]);
467
531
  const loadThreadMessages = (0, import_react2.useCallback)(
468
532
  async (id) => {
469
533
  chatLogger.debug("loadThreadMessages start", { threadId: id });
@@ -495,6 +559,7 @@ function useChat(options = {}) {
495
559
  setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
496
560
  setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
497
561
  setTodos(data?.state?.todos ?? []);
562
+ loadedThreadIdRef.current = id;
498
563
  chatLogger.info("loadThreadMessages succeeded", {
499
564
  threadId: id,
500
565
  messageCount: loaded.length
@@ -515,6 +580,7 @@ function useChat(options = {}) {
515
580
  error: errorObj.message
516
581
  });
517
582
  setError(errorObj);
583
+ if (loadedThreadIdRef.current === id) loadedThreadIdRef.current = void 0;
518
584
  return [];
519
585
  } finally {
520
586
  setIsLoadingHistory(false);
@@ -524,33 +590,33 @@ function useChat(options = {}) {
524
590
  [fetchWithAuth, chatLogger]
525
591
  );
526
592
  (0, import_react2.useEffect)(() => {
527
- if (!threadId || isStreaming) {
528
- chatLogger.trace("auto-load skipped", { threadId, isStreaming });
593
+ if (!effectiveThreadId || isStreaming) {
594
+ chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
529
595
  return;
530
596
  }
531
- if (loadedThreadIdRef.current === threadId) {
532
- chatLogger.trace("auto-load already loaded", { threadId });
597
+ if (loadedThreadIdRef.current === effectiveThreadId) {
598
+ chatLogger.trace("auto-load already loaded", { threadId: effectiveThreadId });
533
599
  return;
534
600
  }
535
601
  if (messages.length > 0) {
536
602
  chatLogger.trace("auto-load has messages", {
537
- threadId,
603
+ threadId: effectiveThreadId,
538
604
  count: messages.length
539
605
  });
540
606
  return;
541
607
  }
542
- loadedThreadIdRef.current = threadId;
543
- chatLogger.info("auto-load thread history", { threadId });
544
- chatLogger.debug("loadThreadMessages trigger", { threadId });
545
- void loadThreadMessages(threadId);
546
- }, [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]);
547
612
  const voice = options.voice;
548
613
  (0, import_react2.useEffect)(() => {
549
614
  if (!voice) return;
550
- if (voiceThreadRef.current !== threadId) {
551
- voiceThreadRef.current = threadId;
615
+ if (voiceThreadRef.current !== effectiveThreadId) {
616
+ voiceThreadRef.current = effectiveThreadId;
552
617
  voicePrevLenRef.current = voice.transcript.length;
553
618
  voiceStreamingIdRef.current = null;
619
+ voiceUserPartialIdRef.current = null;
554
620
  return;
555
621
  }
556
622
  const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
@@ -558,15 +624,40 @@ function useChat(options = {}) {
558
624
  if (curLen < voicePrevLenRef.current) {
559
625
  voicePrevLenRef.current = 0;
560
626
  voiceStreamingIdRef.current = null;
627
+ voiceUserPartialIdRef.current = null;
561
628
  }
562
629
  if (!isVoiceActive) {
563
- 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
+ }
564
652
  if (voiceStreamingIdRef.current) {
565
653
  const doneId = voiceStreamingIdRef.current;
566
654
  voiceStreamingIdRef.current = null;
567
- setMessages(
568
- (prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
569
- );
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));
570
661
  }
571
662
  return;
572
663
  }
@@ -593,54 +684,71 @@ function useChat(options = {}) {
593
684
  });
594
685
  }
595
686
  }
596
- const partialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
597
- 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
+ }
598
695
  setMessages((prev) => {
599
696
  const last = prev[prev.length - 1];
600
697
  if (last?.id.startsWith("voice-") && last.role === "assistant") {
601
698
  voiceStreamingIdRef.current = last.id;
602
- return [...prev.slice(0, -1), { ...last, content: partialText, isStreaming: true }];
699
+ return [...prev.slice(0, -1), { ...last, content: agentPartialText, isStreaming: true }];
603
700
  }
604
701
  const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
605
702
  voiceStreamingIdRef.current = id;
606
- 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() }];
607
704
  });
608
- } else if (voice.state !== "speaking" && voiceStreamingIdRef.current) {
609
- const doneId = voiceStreamingIdRef.current;
610
- voiceStreamingIdRef.current = null;
611
- setMessages(
612
- (prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m)
613
- );
614
- }
615
- }, [voice?.transcript, voice?.partial, voice?.state, threadId]);
616
- const sendMessage = (0, import_react2.useCallback)(
617
- async (contentToSend, overrideOptions) => {
618
- const prompt = (contentToSend ?? input).trim();
619
- if (!prompt || isStreaming) {
620
- chatLogger.trace("sendMessage skipped", {
621
- hasPrompt: !!prompt,
622
- isStreaming
623
- });
624
- 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));
625
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) => {
626
736
  const targetAgentId = overrideOptions?.agentId || agentId;
627
737
  if (!targetAgentId) {
628
738
  const err = new Error("No Agent ID specified for useChat.");
629
739
  chatLogger.warn("sendMessage no agentId", {});
630
- chatLogger.error("sendMessage failed \u2014 no agent", {
631
- error: err.message
632
- });
740
+ chatLogger.error("sendMessage failed \u2014 no agent", { error: err.message });
633
741
  setError(err);
634
742
  options.onError?.(err);
635
- return;
743
+ return false;
636
744
  }
637
745
  chatLogger.debug("sendMessage start", {
638
746
  promptPreview: prompt.slice(0, 100),
639
747
  agentId: targetAgentId,
640
- threadId: threadId ?? overrideOptions?.threadId,
748
+ threadId: effectiveThreadId ?? overrideOptions?.threadId,
641
749
  hasResume: !!overrideOptions?.resume,
642
750
  hasContextOverride: !!overrideOptions?.contextOverride,
643
- messageCount: messages.length
751
+ messageCount: baseMessages.length
644
752
  });
645
753
  chatLogger.trace("sendMessage details", {
646
754
  agentId: targetAgentId,
@@ -663,7 +771,7 @@ function useChat(options = {}) {
663
771
  isStreaming: true,
664
772
  toolCalls: []
665
773
  };
666
- const nextMessages = [...messages, userMessage];
774
+ const nextMessages = [...baseMessages, userMessage];
667
775
  setMessages([...nextMessages, placeholderAssistant]);
668
776
  setInput("");
669
777
  setIsStreaming(true);
@@ -673,9 +781,7 @@ function useChat(options = {}) {
673
781
  abortControllerRef.current = controller;
674
782
  const finalizeReasoning = () => {
675
783
  setMessages(
676
- (prev) => prev.map(
677
- (m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m
678
- )
784
+ (prev) => prev.map((m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m)
679
785
  );
680
786
  };
681
787
  try {
@@ -683,7 +789,24 @@ function useChat(options = {}) {
683
789
  role: m.role === "assistant" ? "assistant" : "user",
684
790
  content: m.content
685
791
  }));
686
- 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
+ }
687
810
  chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
688
811
  const token = getAuthToken ? await getAuthToken() : null;
689
812
  const resolvedHookContext = typeof options.context === "function" ? options.context() : options.context;
@@ -712,27 +835,12 @@ function useChat(options = {}) {
712
835
  signal: controller.signal
713
836
  });
714
837
  if (!stream.ok) {
715
- chatLogger.warn("SSE stream not ok", {
716
- status: stream.status,
717
- errorText: stream.errorText
718
- });
719
- chatLogger.error("chat stream failed", {
720
- agentId: targetAgentId,
721
- status: stream.status,
722
- error: stream.errorText
723
- });
724
- throw new Error(
725
- `Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`
726
- );
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"}`);
727
841
  }
728
- chatLogger.debug("SSE stream opened", {
729
- agentId: targetAgentId,
730
- status: stream.status
731
- });
732
- chatLogger.info("chat stream started", {
733
- agentId: targetAgentId,
734
- threadId: resolvedThreadId
735
- });
842
+ chatLogger.debug("SSE stream opened", { agentId: targetAgentId, status: stream.status });
843
+ chatLogger.info("chat stream started", { agentId: targetAgentId, threadId: resolvedThreadId });
736
844
  const reader = stream.reader;
737
845
  let buffer = "";
738
846
  let accumulatedText = "";
@@ -743,23 +851,12 @@ function useChat(options = {}) {
743
851
  const patchAssistant = (patch) => {
744
852
  setMessages(
745
853
  (prev) => prev.map(
746
- (msg) => msg.id === assistantMessageId ? {
747
- ...msg,
748
- toolCalls: Array.from(toolCallsMap.values()),
749
- ...patch
750
- } : msg
854
+ (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
751
855
  )
752
856
  );
753
857
  };
754
858
  const insertReasoningMessage = (id, seq) => {
755
- const msg = {
756
- id,
757
- role: "reasoning",
758
- content: "",
759
- createdAt: /* @__PURE__ */ new Date(),
760
- isStreaming: true,
761
- seq
762
- };
859
+ const msg = { id, role: "reasoning", content: "", createdAt: /* @__PURE__ */ new Date(), isStreaming: true, seq };
763
860
  setMessages((prev) => {
764
861
  const idx = prev.findIndex((m) => m.id === assistantMessageId);
765
862
  if (idx === -1) return [...prev, msg];
@@ -784,16 +881,11 @@ function useChat(options = {}) {
784
881
  chatLogger.trace("stream event", { type: event.type, event });
785
882
  options.onEvent?.(event);
786
883
  if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
787
- chatLogger.debug("text chunk", {
788
- deltaLength: event.delta.length
789
- });
884
+ chatLogger.debug("text chunk", { deltaLength: event.delta.length });
790
885
  accumulatedText += event.delta;
791
886
  patchAssistant({ content: accumulatedText, isStreaming: true });
792
887
  } else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
793
- chatLogger.debug("tool call chunk", {
794
- toolCallId: event.toolCallId,
795
- toolCallName: event.toolCallName
796
- });
888
+ chatLogger.debug("tool call chunk", { toolCallId: event.toolCallId, toolCallName: event.toolCallName });
797
889
  const existing = toolCallsMap.get(event.toolCallId);
798
890
  if (existing) {
799
891
  existing.args = (existing.args || "") + (event.delta || "");
@@ -807,17 +899,12 @@ function useChat(options = {}) {
807
899
  }
808
900
  patchAssistant({});
809
901
  } else if (event.type === "TOOL_CALL_RESULT") {
810
- chatLogger.debug("tool call result", {
811
- toolCallId: event.toolCallId
812
- });
902
+ chatLogger.debug("tool call result", { toolCallId: event.toolCallId });
813
903
  const existing = toolCallsMap.get(event.toolCallId);
814
904
  if (existing) {
815
905
  existing.result = event.content;
816
906
  existing.isError = isErrorToolContent(event.content);
817
- if (existing.isError)
818
- chatLogger.warn("tool call error", {
819
- toolCallId: event.toolCallId
820
- });
907
+ if (existing.isError) chatLogger.warn("tool call error", { toolCallId: event.toolCallId });
821
908
  if (existing.toolName === "present_file" && !existing.isError) {
822
909
  const presented = parsePresentedFile(event.content);
823
910
  if (presented) {
@@ -832,12 +919,10 @@ function useChat(options = {}) {
832
919
  fileCount: Object.keys(event.snapshot.files ?? {}).length,
833
920
  todoCount: event.snapshot.todos?.length ?? 0
834
921
  });
835
- setFiles(normalizeWorkspaceFiles(event.snapshot.files));
836
- setTodos(event.snapshot.todos);
922
+ setFiles(normalizeWorkspaceFiles(event.snapshot.files ?? {}));
923
+ setTodos(event.snapshot.todos ?? []);
837
924
  } else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
838
- chatLogger.debug("reasoning start", {
839
- messageId: event.messageId
840
- });
925
+ chatLogger.debug("reasoning start", { messageId: event.messageId });
841
926
  activeReasoningId = event.messageId;
842
927
  reasoningById.set(event.messageId, { content: "" });
843
928
  insertReasoningMessage(event.messageId, streamSeq++);
@@ -852,61 +937,31 @@ function useChat(options = {}) {
852
937
  }
853
938
  const entry = reasoningById.get(rid);
854
939
  entry.content += event.delta;
855
- chatLogger.trace("reasoning content", {
856
- messageId: rid,
857
- deltaLength: event.delta?.length ?? 0
858
- });
859
- setMessages(
860
- (prev) => prev.map(
861
- (m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
862
- )
863
- );
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));
864
942
  } else if (event.type === "REASONING_END") {
865
- chatLogger.debug("reasoning end", {
866
- messageId: activeReasoningId
867
- });
943
+ chatLogger.debug("reasoning end", { messageId: activeReasoningId });
868
944
  const rid = activeReasoningId;
869
945
  activeReasoningId = null;
870
946
  if (rid) {
871
- setMessages(
872
- (prev) => prev.map(
873
- (m) => m.id === rid ? { ...m, isStreaming: false } : m
874
- )
875
- );
947
+ setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m));
876
948
  }
877
949
  } else if (event.type === "CUSTOM") {
878
950
  chatLogger.debug("custom event", { name: event.name });
879
951
  if (event.name === "hitl_request") {
880
952
  const value2 = event.value;
881
- chatLogger.info("hitl interrupt", {
882
- actionCount: value2.actionRequests?.length ?? 0
883
- });
884
- setInterrupt({
885
- kind: "hitl",
886
- actionRequests: value2.actionRequests,
887
- reviewConfigs: value2.reviewConfigs
888
- });
953
+ chatLogger.info("hitl interrupt", { actionCount: value2.actionRequests?.length ?? 0 });
954
+ setInterrupt({ kind: "hitl", actionRequests: value2.actionRequests, reviewConfigs: value2.reviewConfigs });
889
955
  } else if (event.name === "clarification_request") {
890
956
  const value2 = event.value;
891
- chatLogger.info("clarification interrupt", {
892
- questionCount: value2.questions?.length ?? 0
893
- });
894
- setInterrupt({
895
- kind: "clarification",
896
- questions: value2.questions
897
- });
957
+ chatLogger.info("clarification interrupt", { questionCount: value2.questions?.length ?? 0 });
958
+ setInterrupt({ kind: "clarification", questions: value2.questions });
898
959
  } else if (event.name === "subagent_activity") {
899
960
  const { toolCallId, ...entry } = event.value;
900
- chatLogger.trace("subagent activity", {
901
- toolCallId,
902
- kind: entry.kind
903
- });
961
+ chatLogger.trace("subagent activity", { toolCallId, kind: entry.kind });
904
962
  const existing = toolCallsMap.get(toolCallId);
905
963
  if (existing) {
906
- existing.subagentActivity = [
907
- ...existing.subagentActivity || [],
908
- entry
909
- ];
964
+ existing.subagentActivity = [...existing.subagentActivity || [], entry];
910
965
  patchAssistant({});
911
966
  }
912
967
  } else if (event.name === "mcp_app") {
@@ -914,35 +969,21 @@ function useChat(options = {}) {
914
969
  if (val?.toolCallId && val?.resourceUri && val?.mcpId) {
915
970
  const existing = toolCallsMap.get(val.toolCallId);
916
971
  if (existing) {
917
- existing.mcpApp = {
918
- resourceUri: val.resourceUri,
919
- mcpId: val.mcpId
920
- };
972
+ existing.mcpApp = { resourceUri: val.resourceUri, mcpId: val.mcpId };
921
973
  patchAssistant({});
922
974
  }
923
975
  }
924
976
  }
925
977
  } else if (event.type === "RUN_ERROR") {
926
- chatLogger.warn("run error", {
927
- message: event.message,
928
- code: event.code
929
- });
930
- chatLogger.error("stream run error", {
931
- code: event.code,
932
- message: event.message
933
- });
978
+ chatLogger.warn("run error", { message: event.message, code: event.code });
979
+ chatLogger.error("stream run error", { code: event.code, message: event.message });
934
980
  throw new Error(event.message || "Stream error from agent");
935
981
  } else {
936
982
  chatLogger.trace("unhandled event", { type: event.type });
937
983
  }
938
984
  } catch (e) {
939
- if (e instanceof Error && e.message.startsWith("Stream error")) {
940
- throw e;
941
- }
942
- chatLogger.warn("event parse error", {
943
- raw: raw.slice(0, 200),
944
- error: e instanceof Error ? e.message : String(e)
945
- });
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) });
946
987
  }
947
988
  }
948
989
  }
@@ -954,75 +995,55 @@ function useChat(options = {}) {
954
995
  isStreaming: false,
955
996
  toolCalls: Array.from(toolCallsMap.values())
956
997
  };
957
- setMessages(
958
- (prev) => prev.map(
959
- (msg) => msg.id === assistantMessageId ? finalMessage : msg
960
- )
961
- );
962
- chatLogger.info("sendMessage succeeded", {
963
- agentId: targetAgentId,
964
- textLength: accumulatedText.length,
965
- toolCallCount: toolCallsMap.size
966
- });
967
- chatLogger.debug("sendMessage completed", {
968
- agentId: targetAgentId,
969
- textLength: accumulatedText.length,
970
- eventCount: toolCallsMap.size + 1
971
- });
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 });
972
1001
  options.onFinish?.(finalMessage);
1002
+ return true;
973
1003
  } catch (err) {
974
1004
  if (controller.signal.aborted) {
975
1005
  chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
976
1006
  chatLogger.debug("stream aborted", { agentId: targetAgentId });
977
- setMessages(
978
- (prev) => prev.map(
979
- (msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg
980
- )
981
- );
982
- return;
1007
+ setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
1008
+ return false;
983
1009
  }
984
1010
  const errorObj = err instanceof Error ? err : new Error(String(err));
985
- chatLogger.warn("sendMessage failed", {
986
- agentId: targetAgentId,
987
- error: errorObj.message
988
- });
989
- chatLogger.error("sendMessage error", {
990
- agentId: targetAgentId,
991
- error: errorObj.message
992
- });
1011
+ chatLogger.warn("sendMessage failed", { agentId: targetAgentId, error: errorObj.message });
1012
+ chatLogger.error("sendMessage error", { agentId: targetAgentId, error: errorObj.message });
993
1013
  setError(errorObj);
994
1014
  options.onError?.(errorObj);
995
1015
  setMessages(
996
1016
  (prev) => prev.map(
997
- (msg) => msg.id === assistantMessageId ? {
998
- ...msg,
999
- content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`,
1000
- isStreaming: false
1001
- } : msg
1017
+ (msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
1002
1018
  )
1003
1019
  );
1020
+ return false;
1004
1021
  } finally {
1005
1022
  setIsStreaming(false);
1006
1023
  abortControllerRef.current = null;
1007
1024
  finalizeReasoning();
1008
- chatLogger.debug("sendMessage finally", {
1009
- agentId: targetAgentId,
1010
- isStreaming: false
1011
- });
1025
+ chatLogger.debug("sendMessage finally", { agentId: targetAgentId, isStreaming: false });
1012
1026
  chatLogger.trace("sendMessage end", { agentId: targetAgentId });
1013
1027
  }
1014
1028
  },
1015
- [
1016
- agentId,
1017
- baseUrl,
1018
- getAuthToken,
1019
- input,
1020
- isStreaming,
1021
- messages,
1022
- options,
1023
- chatLogger,
1024
- threadId
1025
- ]
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]
1026
1047
  );
1027
1048
  const handleInputChange = (0, import_react2.useCallback)(
1028
1049
  (e) => {
@@ -1039,13 +1060,13 @@ function useChat(options = {}) {
1039
1060
  },
1040
1061
  [sendMessage, chatLogger]
1041
1062
  );
1042
- const reload = (0, import_react2.useCallback)(() => {
1063
+ const reload = (0, import_react2.useCallback)(async () => {
1043
1064
  if (messages.length === 0 || isStreaming) {
1044
1065
  chatLogger.trace("reload skipped", {
1045
1066
  messageCount: messages.length,
1046
1067
  isStreaming
1047
1068
  });
1048
- return;
1069
+ return false;
1049
1070
  }
1050
1071
  let lastUserIndex = -1;
1051
1072
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -1056,16 +1077,16 @@ function useChat(options = {}) {
1056
1077
  }
1057
1078
  if (lastUserIndex === -1) {
1058
1079
  chatLogger.warn("reload no user message", {});
1059
- return;
1080
+ return false;
1060
1081
  }
1061
1082
  const lastUserMessage = messages[lastUserIndex];
1083
+ const truncated = messages.slice(0, lastUserIndex);
1062
1084
  chatLogger.info("reload", { messageId: lastUserMessage.id });
1063
1085
  chatLogger.debug("reload last user", {
1064
1086
  preview: lastUserMessage.content.slice(0, 100)
1065
1087
  });
1066
- setMessages(messages.slice(0, lastUserIndex));
1067
- void sendMessage(lastUserMessage.content);
1068
- }, [isStreaming, messages, sendMessage, chatLogger]);
1088
+ return doSend(lastUserMessage.content, truncated);
1089
+ }, [isStreaming, messages, doSend, chatLogger]);
1069
1090
  const resumeInterrupt = (0, import_react2.useCallback)(
1070
1091
  (resume, displayContent) => {
1071
1092
  chatLogger.info("resumeInterrupt", {
@@ -1109,9 +1130,13 @@ function useChat(options = {}) {
1109
1130
  presentedFile,
1110
1131
  dismissPresentedFile,
1111
1132
  openWorkspaceFile,
1133
+ sandboxCommands,
1112
1134
  stop,
1113
1135
  reload,
1114
1136
  clear,
1137
+ startNewChat,
1138
+ currentThreadId: effectiveThreadId,
1139
+ isEphemeral: !effectiveThreadId,
1115
1140
  setMessages,
1116
1141
  loadThreadMessages
1117
1142
  };
@@ -2074,7 +2099,7 @@ function useMcp(options = {}) {
2074
2099
 
2075
2100
  // src/index.ts
2076
2101
  var import_logger2 = require("@personaai/logger");
2077
- var VERSION = "0.7.9";
2102
+ var VERSION = "0.8.0";
2078
2103
  // Annotate the CommonJS export names for ESM import in node:
2079
2104
  0 && (module.exports = {
2080
2105
  PersonaProvider,