@personaai/react 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,47 +1,111 @@
1
1
  // src/context/PersonaContext.tsx
2
2
  import { createContext, useContext, useMemo } from "react";
3
+ import { createLogger } from "@personaai/logger";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  var PersonaContext = createContext(null);
5
6
  function PersonaProvider({
6
7
  baseUrl,
7
8
  getAuthToken,
8
9
  defaultAgentId,
10
+ logLevel,
11
+ logger: loggerProp,
9
12
  children
10
13
  }) {
11
- const normalizedBaseUrl = useMemo(() => baseUrl.replace(/\/+$/, ""), [baseUrl]);
14
+ const normalizedBaseUrl = useMemo(
15
+ () => baseUrl.replace(/\/+$/, ""),
16
+ [baseUrl]
17
+ );
18
+ const logger = useMemo(() => {
19
+ const l = loggerProp ?? createLogger(
20
+ "react",
21
+ logLevel !== void 0 ? { level: logLevel } : void 0
22
+ );
23
+ l.debug("PersonaProvider created", {
24
+ baseUrl: normalizedBaseUrl,
25
+ hasDefaultAgentId: !!defaultAgentId
26
+ });
27
+ l.info("PersonaProvider mounted", { baseUrl: normalizedBaseUrl });
28
+ l.trace("PersonaProvider config", {
29
+ baseUrl: normalizedBaseUrl,
30
+ hasGetAuthToken: !!getAuthToken
31
+ });
32
+ return l;
33
+ }, [loggerProp, logLevel, normalizedBaseUrl, defaultAgentId, getAuthToken]);
12
34
  const value = useMemo(() => {
13
35
  async function fetchWithAuth(path, init = {}) {
14
36
  const token = getAuthToken ? await getAuthToken() : null;
15
37
  const headers = new Headers(init.headers || {});
16
38
  if (token) {
17
39
  headers.set("Authorization", `Bearer ${token}`);
40
+ logger.trace("fetchWithAuth with token", { path });
41
+ } else {
42
+ logger.trace("fetchWithAuth without token", { path });
43
+ logger.debug("fetchWithAuth no token", { path });
18
44
  }
19
45
  const cleanPath = path.startsWith("/") ? path : `/${path}`;
20
46
  const url = `${normalizedBaseUrl}${cleanPath}`;
21
- return fetch(url, {
22
- ...init,
23
- headers
47
+ logger.debug("fetchWithAuth request", {
48
+ path: cleanPath,
49
+ url,
50
+ hasToken: !!token
51
+ });
52
+ logger.trace("fetchWithAuth details", {
53
+ url,
54
+ path: cleanPath,
55
+ hasToken: !!token
24
56
  });
57
+ try {
58
+ const res = await fetch(url, {
59
+ ...init,
60
+ headers
61
+ });
62
+ logger.debug("fetchWithAuth response", {
63
+ path: cleanPath,
64
+ status: res.status,
65
+ ok: res.ok
66
+ });
67
+ if (!res.ok) {
68
+ logger.warn("fetchWithAuth non-ok", {
69
+ path: cleanPath,
70
+ status: res.status
71
+ });
72
+ } else {
73
+ logger.info("fetchWithAuth succeeded", {
74
+ path: cleanPath,
75
+ status: res.status
76
+ });
77
+ }
78
+ return res;
79
+ } catch (err) {
80
+ logger.error("fetchWithAuth failed", {
81
+ path: cleanPath,
82
+ error: err instanceof Error ? err.message : String(err)
83
+ });
84
+ throw err;
85
+ }
25
86
  }
26
87
  return {
27
88
  baseUrl: normalizedBaseUrl,
28
89
  getAuthToken,
29
90
  defaultAgentId,
30
- fetchWithAuth
91
+ fetchWithAuth,
92
+ logger
31
93
  };
32
- }, [normalizedBaseUrl, getAuthToken, defaultAgentId]);
94
+ }, [normalizedBaseUrl, getAuthToken, defaultAgentId, logger]);
33
95
  return /* @__PURE__ */ jsx(PersonaContext.Provider, { value, children });
34
96
  }
35
97
  function usePersonaContext() {
36
98
  const context = useContext(PersonaContext);
37
99
  if (!context) {
38
- throw new Error("usePersonaContext must be used within a <PersonaProvider>");
100
+ throw new Error(
101
+ "usePersonaContext must be used within a <PersonaProvider>"
102
+ );
39
103
  }
40
104
  return context;
41
105
  }
42
106
 
43
107
  // src/hooks/useChat.ts
44
- import { useCallback, useEffect, useRef, useState } from "react";
108
+ import { useCallback, useEffect, useMemo as useMemo2, useRef, useState } from "react";
45
109
 
46
110
  // src/streaming.ts
47
111
  function supportsStreamingFetch() {
@@ -70,7 +134,8 @@ function xhrStream(opts) {
70
134
  return new Promise((resolveStream, rejectStream) => {
71
135
  const xhr = new XMLHttpRequest();
72
136
  xhr.open("POST", opts.url);
73
- for (const [key, val] of Object.entries(opts.headers)) xhr.setRequestHeader(key, val);
137
+ for (const [key, val] of Object.entries(opts.headers))
138
+ xhr.setRequestHeader(key, val);
74
139
  let consumed = 0;
75
140
  let finished = false;
76
141
  let failure = null;
@@ -192,7 +257,10 @@ async function openSSEStream(opts) {
192
257
  ok: false,
193
258
  errorText,
194
259
  getHeader: (name) => response.headers.get(name),
195
- reader: { read: async () => ({ done: true }), cancel: () => controller.abort() }
260
+ reader: {
261
+ read: async () => ({ done: true }),
262
+ cancel: () => controller.abort()
263
+ }
196
264
  };
197
265
  }
198
266
  if (!response.body) {
@@ -218,7 +286,8 @@ async function openSSEStream(opts) {
218
286
 
219
287
  // src/hooks/useChat.ts
220
288
  function isErrorToolContent(content) {
221
- if (typeof content !== "string" || !content.trim().startsWith("{")) return false;
289
+ if (typeof content !== "string" || !content.trim().startsWith("{"))
290
+ return false;
222
291
  try {
223
292
  return JSON.parse(content)?.status === "error";
224
293
  } catch {
@@ -231,9 +300,17 @@ function persistedTraceToActivityEntries(items) {
231
300
  if (item.type === "text") {
232
301
  if (item.text) entries.push({ kind: "text", delta: item.text });
233
302
  } else {
234
- entries.push({ kind: "tool_start", toolName: item.name, args: item.argsText });
303
+ entries.push({
304
+ kind: "tool_start",
305
+ toolName: item.name,
306
+ args: item.argsText
307
+ });
235
308
  if (item.status === "completed") {
236
- entries.push({ kind: "tool_result", toolName: item.name, result: item.resultText });
309
+ entries.push({
310
+ kind: "tool_result",
311
+ toolName: item.name,
312
+ result: item.resultText
313
+ });
237
314
  }
238
315
  }
239
316
  }
@@ -242,7 +319,8 @@ function persistedTraceToActivityEntries(items) {
242
319
  function parsePresentedFile(content) {
243
320
  try {
244
321
  const parsed = JSON.parse(content);
245
- if (parsed?.status !== "success" || typeof parsed.filePath !== "string") return null;
322
+ if (parsed?.status !== "success" || typeof parsed.filePath !== "string")
323
+ return null;
246
324
  return {
247
325
  path: parsed.filePath,
248
326
  title: typeof parsed.title === "string" ? parsed.title : parsed.filePath,
@@ -283,10 +361,27 @@ function normalizePendingInterrupt(pending) {
283
361
  return null;
284
362
  }
285
363
  function useChat(options = {}) {
286
- const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken } = usePersonaContext();
364
+ const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
365
+ const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
287
366
  const agentId = options.agentId || defaultAgentId;
288
367
  const threadId = options.threadId;
289
- const [messages, setMessages] = useState(options.initialMessages || []);
368
+ const didLogInitRef = useRef(false);
369
+ if (!didLogInitRef.current) {
370
+ didLogInitRef.current = true;
371
+ chatLogger.debug("useChat init", {
372
+ agentId,
373
+ threadId,
374
+ hasInitialMessages: !!options.initialMessages?.length
375
+ });
376
+ chatLogger.trace("useChat options", {
377
+ agentId,
378
+ threadId,
379
+ initialMessageCount: options.initialMessages?.length ?? 0
380
+ });
381
+ }
382
+ const [messages, setMessages] = useState(
383
+ options.initialMessages || []
384
+ );
290
385
  const [input, setInput] = useState("");
291
386
  const [isStreaming, setIsStreaming] = useState(false);
292
387
  const [isLoadingHistory, setIsLoadingHistory] = useState(false);
@@ -299,12 +394,21 @@ function useChat(options = {}) {
299
394
  const loadedThreadIdRef = useRef(void 0);
300
395
  const stop = useCallback(() => {
301
396
  if (abortControllerRef.current) {
397
+ chatLogger.info("stop streaming", {});
398
+ chatLogger.debug("abort controller", {
399
+ hasController: !!abortControllerRef.current
400
+ });
302
401
  abortControllerRef.current.abort();
303
402
  abortControllerRef.current = null;
304
403
  setIsStreaming(false);
404
+ chatLogger.debug("streaming stopped");
405
+ } else {
406
+ chatLogger.trace("stop called \u2014 no active stream");
305
407
  }
306
- }, []);
408
+ }, [chatLogger]);
307
409
  const clear = useCallback(() => {
410
+ chatLogger.info("clear chat", { messageCount: messages.length });
411
+ chatLogger.debug("clear", { messageCount: messages.length });
308
412
  stop();
309
413
  setMessages([]);
310
414
  setError(null);
@@ -312,14 +416,17 @@ function useChat(options = {}) {
312
416
  setFiles({});
313
417
  setTodos([]);
314
418
  setPresentedFile(null);
315
- }, [stop]);
419
+ }, [stop, chatLogger, messages.length]);
316
420
  const loadThreadMessages = useCallback(
317
421
  async (id) => {
422
+ chatLogger.debug("loadThreadMessages start", { threadId: id });
423
+ chatLogger.trace("loadThreadMessages", { threadId: id });
318
424
  setIsLoadingHistory(true);
319
425
  setError(null);
320
426
  try {
321
427
  const res = await fetchWithAuth(`/threads/${id}/messages`);
322
- if (!res.ok) throw new Error(`Failed to load thread history: ${res.statusText}`);
428
+ if (!res.ok)
429
+ throw new Error(`Failed to load thread history: ${res.statusText}`);
323
430
  const body = await res.json();
324
431
  const data = body?.data ?? body;
325
432
  const raw = data?.messages ?? [];
@@ -331,42 +438,98 @@ function useChat(options = {}) {
331
438
  createdAt: /* @__PURE__ */ new Date(),
332
439
  toolCalls: m.toolCalls?.map((tc) => {
333
440
  const trace = subagentTraces[tc.toolCallId];
334
- return Array.isArray(trace) && trace.length > 0 ? { ...tc, subagentActivity: persistedTraceToActivityEntries(trace) } : tc;
441
+ return Array.isArray(trace) && trace.length > 0 ? {
442
+ ...tc,
443
+ subagentActivity: persistedTraceToActivityEntries(trace)
444
+ } : tc;
335
445
  })
336
446
  }));
337
447
  setMessages(loaded);
338
448
  setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
339
449
  setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
340
450
  setTodos(data?.state?.todos ?? []);
451
+ chatLogger.info("loadThreadMessages succeeded", {
452
+ threadId: id,
453
+ messageCount: loaded.length
454
+ });
455
+ chatLogger.debug("loadThreadMessages completed", {
456
+ threadId: id,
457
+ messageCount: loaded.length
458
+ });
341
459
  return loaded;
342
460
  } catch (err) {
343
461
  const errorObj = err instanceof Error ? err : new Error(String(err));
462
+ chatLogger.warn("loadThreadMessages failed", {
463
+ threadId: id,
464
+ error: errorObj.message
465
+ });
466
+ chatLogger.error("loadThreadMessages error", {
467
+ threadId: id,
468
+ error: errorObj.message
469
+ });
344
470
  setError(errorObj);
345
471
  return [];
346
472
  } finally {
347
473
  setIsLoadingHistory(false);
474
+ chatLogger.trace("loadThreadMessages end", { threadId: id });
348
475
  }
349
476
  },
350
- [fetchWithAuth]
477
+ [fetchWithAuth, chatLogger]
351
478
  );
352
479
  useEffect(() => {
353
- if (!threadId || isStreaming) return;
354
- if (loadedThreadIdRef.current === threadId) return;
355
- if (messages.length > 0) return;
480
+ if (!threadId || isStreaming) {
481
+ chatLogger.trace("auto-load skipped", { threadId, isStreaming });
482
+ return;
483
+ }
484
+ if (loadedThreadIdRef.current === threadId) {
485
+ chatLogger.trace("auto-load already loaded", { threadId });
486
+ return;
487
+ }
488
+ if (messages.length > 0) {
489
+ chatLogger.trace("auto-load has messages", {
490
+ threadId,
491
+ count: messages.length
492
+ });
493
+ return;
494
+ }
356
495
  loadedThreadIdRef.current = threadId;
496
+ chatLogger.info("auto-load thread history", { threadId });
497
+ chatLogger.debug("loadThreadMessages trigger", { threadId });
357
498
  void loadThreadMessages(threadId);
358
- }, [threadId, isStreaming, messages.length, loadThreadMessages]);
499
+ }, [threadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
359
500
  const sendMessage = useCallback(
360
501
  async (contentToSend, overrideOptions) => {
361
502
  const prompt = (contentToSend ?? input).trim();
362
- if (!prompt || isStreaming) return;
503
+ if (!prompt || isStreaming) {
504
+ chatLogger.trace("sendMessage skipped", {
505
+ hasPrompt: !!prompt,
506
+ isStreaming
507
+ });
508
+ return;
509
+ }
363
510
  const targetAgentId = overrideOptions?.agentId || agentId;
364
511
  if (!targetAgentId) {
365
512
  const err = new Error("No Agent ID specified for useChat.");
513
+ chatLogger.warn("sendMessage no agentId", {});
514
+ chatLogger.error("sendMessage failed \u2014 no agent", {
515
+ error: err.message
516
+ });
366
517
  setError(err);
367
518
  options.onError?.(err);
368
519
  return;
369
520
  }
521
+ chatLogger.debug("sendMessage start", {
522
+ promptPreview: prompt.slice(0, 100),
523
+ agentId: targetAgentId,
524
+ threadId: threadId ?? overrideOptions?.threadId,
525
+ hasResume: !!overrideOptions?.resume,
526
+ messageCount: messages.length
527
+ });
528
+ chatLogger.trace("sendMessage details", {
529
+ agentId: targetAgentId,
530
+ promptPreview: prompt.slice(0, 200),
531
+ hasResume: !!overrideOptions?.resume
532
+ });
370
533
  const userMessage = {
371
534
  id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
372
535
  role: "user",
@@ -403,7 +566,13 @@ function useChat(options = {}) {
403
566
  content: m.content
404
567
  }));
405
568
  const resolvedThreadId = await (overrideOptions?.threadId ?? options.threadId);
569
+ chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
406
570
  const token = getAuthToken ? await getAuthToken() : null;
571
+ chatLogger.debug("opening SSE stream", {
572
+ agentId: targetAgentId,
573
+ hasToken: !!token,
574
+ hasThreadId: !!resolvedThreadId
575
+ });
407
576
  const stream = await openSSEStream({
408
577
  url: `${baseUrl}/chat`,
409
578
  headers: {
@@ -419,8 +588,27 @@ function useChat(options = {}) {
419
588
  signal: controller.signal
420
589
  });
421
590
  if (!stream.ok) {
422
- throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
591
+ chatLogger.warn("SSE stream not ok", {
592
+ status: stream.status,
593
+ errorText: stream.errorText
594
+ });
595
+ chatLogger.error("chat stream failed", {
596
+ agentId: targetAgentId,
597
+ status: stream.status,
598
+ error: stream.errorText
599
+ });
600
+ throw new Error(
601
+ `Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`
602
+ );
423
603
  }
604
+ chatLogger.debug("SSE stream opened", {
605
+ agentId: targetAgentId,
606
+ status: stream.status
607
+ });
608
+ chatLogger.info("chat stream started", {
609
+ agentId: targetAgentId,
610
+ threadId: resolvedThreadId
611
+ });
424
612
  const reader = stream.reader;
425
613
  let buffer = "";
426
614
  let accumulatedText = "";
@@ -431,7 +619,11 @@ function useChat(options = {}) {
431
619
  const patchAssistant = (patch) => {
432
620
  setMessages(
433
621
  (prev) => prev.map(
434
- (msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
622
+ (msg) => msg.id === assistantMessageId ? {
623
+ ...msg,
624
+ toolCalls: Array.from(toolCallsMap.values()),
625
+ ...patch
626
+ } : msg
435
627
  )
436
628
  );
437
629
  };
@@ -465,11 +657,19 @@ function useChat(options = {}) {
465
657
  if (raw === "[DONE]") break;
466
658
  try {
467
659
  const event = JSON.parse(raw);
660
+ chatLogger.trace("stream event", { type: event.type, event });
468
661
  options.onEvent?.(event);
469
662
  if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
663
+ chatLogger.debug("text chunk", {
664
+ deltaLength: event.delta.length
665
+ });
470
666
  accumulatedText += event.delta;
471
667
  patchAssistant({ content: accumulatedText, isStreaming: true });
472
668
  } else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
669
+ chatLogger.debug("tool call chunk", {
670
+ toolCallId: event.toolCallId,
671
+ toolCallName: event.toolCallName
672
+ });
473
673
  const existing = toolCallsMap.get(event.toolCallId);
474
674
  if (existing) {
475
675
  existing.args = (existing.args || "") + (event.delta || "");
@@ -483,20 +683,37 @@ function useChat(options = {}) {
483
683
  }
484
684
  patchAssistant({});
485
685
  } else if (event.type === "TOOL_CALL_RESULT") {
686
+ chatLogger.debug("tool call result", {
687
+ toolCallId: event.toolCallId
688
+ });
486
689
  const existing = toolCallsMap.get(event.toolCallId);
487
690
  if (existing) {
488
691
  existing.result = event.content;
489
692
  existing.isError = isErrorToolContent(event.content);
693
+ if (existing.isError)
694
+ chatLogger.warn("tool call error", {
695
+ toolCallId: event.toolCallId
696
+ });
490
697
  if (existing.toolName === "present_file" && !existing.isError) {
491
698
  const presented = parsePresentedFile(event.content);
492
- if (presented) setPresentedFile(presented);
699
+ if (presented) {
700
+ chatLogger.info("present_file", { path: presented.path });
701
+ setPresentedFile(presented);
702
+ }
493
703
  }
494
704
  patchAssistant({});
495
705
  }
496
706
  } else if (event.type === "STATE_SNAPSHOT") {
707
+ chatLogger.debug("state snapshot", {
708
+ fileCount: Object.keys(event.snapshot.files ?? {}).length,
709
+ todoCount: event.snapshot.todos?.length ?? 0
710
+ });
497
711
  setFiles(normalizeWorkspaceFiles(event.snapshot.files));
498
712
  setTodos(event.snapshot.todos);
499
713
  } else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
714
+ chatLogger.debug("reasoning start", {
715
+ messageId: event.messageId
716
+ });
500
717
  activeReasoningId = event.messageId;
501
718
  reasoningById.set(event.messageId, { content: "" });
502
719
  insertReasoningMessage(event.messageId, streamSeq++);
@@ -507,25 +724,39 @@ function useChat(options = {}) {
507
724
  activeReasoningId = rid;
508
725
  reasoningById.set(rid, { content: "" });
509
726
  insertReasoningMessage(rid, streamSeq++);
727
+ chatLogger.debug("reasoning lazy start", { messageId: rid });
510
728
  }
511
729
  const entry = reasoningById.get(rid);
512
730
  entry.content += event.delta;
731
+ chatLogger.trace("reasoning content", {
732
+ messageId: rid,
733
+ deltaLength: event.delta?.length ?? 0
734
+ });
513
735
  setMessages(
514
736
  (prev) => prev.map(
515
737
  (m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m
516
738
  )
517
739
  );
518
740
  } else if (event.type === "REASONING_END") {
741
+ chatLogger.debug("reasoning end", {
742
+ messageId: activeReasoningId
743
+ });
519
744
  const rid = activeReasoningId;
520
745
  activeReasoningId = null;
521
746
  if (rid) {
522
747
  setMessages(
523
- (prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m)
748
+ (prev) => prev.map(
749
+ (m) => m.id === rid ? { ...m, isStreaming: false } : m
750
+ )
524
751
  );
525
752
  }
526
753
  } else if (event.type === "CUSTOM") {
754
+ chatLogger.debug("custom event", { name: event.name });
527
755
  if (event.name === "hitl_request") {
528
756
  const value2 = event.value;
757
+ chatLogger.info("hitl interrupt", {
758
+ actionCount: value2.actionRequests?.length ?? 0
759
+ });
529
760
  setInterrupt({
530
761
  kind: "hitl",
531
762
  actionRequests: value2.actionRequests,
@@ -533,22 +764,49 @@ function useChat(options = {}) {
533
764
  });
534
765
  } else if (event.name === "clarification_request") {
535
766
  const value2 = event.value;
536
- setInterrupt({ kind: "clarification", questions: value2.questions });
767
+ chatLogger.info("clarification interrupt", {
768
+ questionCount: value2.questions?.length ?? 0
769
+ });
770
+ setInterrupt({
771
+ kind: "clarification",
772
+ questions: value2.questions
773
+ });
537
774
  } else if (event.name === "subagent_activity") {
538
775
  const { toolCallId, ...entry } = event.value;
776
+ chatLogger.trace("subagent activity", {
777
+ toolCallId,
778
+ kind: entry.kind
779
+ });
539
780
  const existing = toolCallsMap.get(toolCallId);
540
781
  if (existing) {
541
- existing.subagentActivity = [...existing.subagentActivity || [], entry];
782
+ existing.subagentActivity = [
783
+ ...existing.subagentActivity || [],
784
+ entry
785
+ ];
542
786
  patchAssistant({});
543
787
  }
544
788
  }
545
789
  } else if (event.type === "RUN_ERROR") {
790
+ chatLogger.warn("run error", {
791
+ message: event.message,
792
+ code: event.code
793
+ });
794
+ chatLogger.error("stream run error", {
795
+ code: event.code,
796
+ message: event.message
797
+ });
546
798
  throw new Error(event.message || "Stream error from agent");
799
+ } else {
800
+ chatLogger.trace("unhandled event", { type: event.type });
547
801
  }
548
802
  } catch (e) {
549
803
  if (e instanceof Error && e.message.startsWith("Stream error")) {
550
804
  throw e;
551
805
  }
806
+ chatLogger.warn("event parse error", {
807
+ raw: raw.slice(0, 200),
808
+ error: e instanceof Error ? e.message : String(e)
809
+ });
552
810
  }
553
811
  }
554
812
  }
@@ -561,11 +819,25 @@ function useChat(options = {}) {
561
819
  toolCalls: Array.from(toolCallsMap.values())
562
820
  };
563
821
  setMessages(
564
- (prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg)
822
+ (prev) => prev.map(
823
+ (msg) => msg.id === assistantMessageId ? finalMessage : msg
824
+ )
565
825
  );
826
+ chatLogger.info("sendMessage succeeded", {
827
+ agentId: targetAgentId,
828
+ textLength: accumulatedText.length,
829
+ toolCallCount: toolCallsMap.size
830
+ });
831
+ chatLogger.debug("sendMessage completed", {
832
+ agentId: targetAgentId,
833
+ textLength: accumulatedText.length,
834
+ eventCount: toolCallsMap.size + 1
835
+ });
566
836
  options.onFinish?.(finalMessage);
567
837
  } catch (err) {
568
838
  if (controller.signal.aborted) {
839
+ chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
840
+ chatLogger.debug("stream aborted", { agentId: targetAgentId });
569
841
  setMessages(
570
842
  (prev) => prev.map(
571
843
  (msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg
@@ -574,6 +846,14 @@ function useChat(options = {}) {
574
846
  return;
575
847
  }
576
848
  const errorObj = err instanceof Error ? err : new Error(String(err));
849
+ chatLogger.warn("sendMessage failed", {
850
+ agentId: targetAgentId,
851
+ error: errorObj.message
852
+ });
853
+ chatLogger.error("sendMessage error", {
854
+ agentId: targetAgentId,
855
+ error: errorObj.message
856
+ });
577
857
  setError(errorObj);
578
858
  options.onError?.(errorObj);
579
859
  setMessages(
@@ -589,22 +869,48 @@ function useChat(options = {}) {
589
869
  setIsStreaming(false);
590
870
  abortControllerRef.current = null;
591
871
  finalizeReasoning();
872
+ chatLogger.debug("sendMessage finally", {
873
+ agentId: targetAgentId,
874
+ isStreaming: false
875
+ });
876
+ chatLogger.trace("sendMessage end", { agentId: targetAgentId });
592
877
  }
593
878
  },
594
- [agentId, baseUrl, getAuthToken, input, isStreaming, messages, options]
879
+ [
880
+ agentId,
881
+ baseUrl,
882
+ getAuthToken,
883
+ input,
884
+ isStreaming,
885
+ messages,
886
+ options,
887
+ chatLogger,
888
+ threadId
889
+ ]
890
+ );
891
+ const handleInputChange = useCallback(
892
+ (e) => {
893
+ chatLogger.trace("handleInputChange", { length: e.target.value.length });
894
+ setInput(e.target.value);
895
+ },
896
+ [chatLogger]
595
897
  );
596
- const handleInputChange = useCallback((e) => {
597
- setInput(e.target.value);
598
- }, []);
599
898
  const handleSubmit = useCallback(
600
899
  (e) => {
601
900
  if (e) e.preventDefault();
901
+ chatLogger.debug("handleSubmit", {});
602
902
  void sendMessage();
603
903
  },
604
- [sendMessage]
904
+ [sendMessage, chatLogger]
605
905
  );
606
906
  const reload = useCallback(() => {
607
- if (messages.length === 0 || isStreaming) return;
907
+ if (messages.length === 0 || isStreaming) {
908
+ chatLogger.trace("reload skipped", {
909
+ messageCount: messages.length,
910
+ isStreaming
911
+ });
912
+ return;
913
+ }
608
914
  let lastUserIndex = -1;
609
915
  for (let i = messages.length - 1; i >= 0; i--) {
610
916
  if (messages[i].role === "user") {
@@ -612,19 +918,42 @@ function useChat(options = {}) {
612
918
  break;
613
919
  }
614
920
  }
615
- if (lastUserIndex === -1) return;
921
+ if (lastUserIndex === -1) {
922
+ chatLogger.warn("reload no user message", {});
923
+ return;
924
+ }
616
925
  const lastUserMessage = messages[lastUserIndex];
926
+ chatLogger.info("reload", { messageId: lastUserMessage.id });
927
+ chatLogger.debug("reload last user", {
928
+ preview: lastUserMessage.content.slice(0, 100)
929
+ });
617
930
  setMessages(messages.slice(0, lastUserIndex));
618
931
  void sendMessage(lastUserMessage.content);
619
- }, [isStreaming, messages, sendMessage]);
932
+ }, [isStreaming, messages, sendMessage, chatLogger]);
620
933
  const resumeInterrupt = useCallback(
621
- (resume, displayContent) => sendMessage(displayContent, { resume }),
622
- [sendMessage]
934
+ (resume, displayContent) => {
935
+ chatLogger.info("resumeInterrupt", {
936
+ kind: resume.decisions ? "hitl" : "clarification",
937
+ preview: displayContent.slice(0, 50)
938
+ });
939
+ return sendMessage(displayContent, { resume });
940
+ },
941
+ [sendMessage, chatLogger]
623
942
  );
624
- const dismissPresentedFile = useCallback(() => setPresentedFile(null), []);
943
+ const dismissPresentedFile = useCallback(() => {
944
+ chatLogger.debug("dismissPresentedFile", {});
945
+ setPresentedFile(null);
946
+ }, [chatLogger]);
625
947
  const openWorkspaceFile = useCallback(
626
- (path) => setPresentedFile({ path, title: path.split("/").pop() || path, description: "" }),
627
- []
948
+ (path) => {
949
+ chatLogger.debug("openWorkspaceFile", { path });
950
+ setPresentedFile({
951
+ path,
952
+ title: path.split("/").pop() || path,
953
+ description: ""
954
+ });
955
+ },
956
+ [chatLogger]
628
957
  );
629
958
  return {
630
959
  messages,
@@ -656,7 +985,10 @@ function useChat(options = {}) {
656
985
  import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
657
986
  function useMemory(autoFetch = true) {
658
987
  const { fetchWithAuth } = usePersonaContext();
659
- const [memory, setMemory] = useState2({ userFiles: [], agentMemories: [] });
988
+ const [memory, setMemory] = useState2({
989
+ userFiles: [],
990
+ agentMemories: []
991
+ });
660
992
  const [isLoading, setIsLoading] = useState2(false);
661
993
  const [error, setError] = useState2(null);
662
994
  const fetchMemory = useCallback2(async () => {
@@ -684,7 +1016,8 @@ function useMemory(autoFetch = true) {
684
1016
  if (params.scope) query.set("scope", params.scope);
685
1017
  if (params.agentId) query.set("agentId", params.agentId);
686
1018
  const res = await fetchWithAuth(`/memory/file?${query.toString()}`);
687
- if (!res.ok) throw new Error(`Failed to get memory file: ${res.statusText}`);
1019
+ if (!res.ok)
1020
+ throw new Error(`Failed to get memory file: ${res.statusText}`);
688
1021
  return await res.json();
689
1022
  },
690
1023
  [fetchWithAuth]
@@ -696,7 +1029,8 @@ function useMemory(autoFetch = true) {
696
1029
  headers: { "Content-Type": "application/json" },
697
1030
  body: JSON.stringify(params)
698
1031
  });
699
- if (!res.ok) throw new Error(`Failed to write memory file: ${res.statusText}`);
1032
+ if (!res.ok)
1033
+ throw new Error(`Failed to write memory file: ${res.statusText}`);
700
1034
  const updated = await res.json();
701
1035
  void fetchMemory();
702
1036
  return updated;
@@ -711,7 +1045,8 @@ function useMemory(autoFetch = true) {
711
1045
  const res = await fetchWithAuth(`/memory/file?${query.toString()}`, {
712
1046
  method: "DELETE"
713
1047
  });
714
- if (!res.ok) throw new Error(`Failed to delete memory file: ${res.statusText}`);
1048
+ if (!res.ok)
1049
+ throw new Error(`Failed to delete memory file: ${res.statusText}`);
715
1050
  void fetchMemory();
716
1051
  },
717
1052
  [fetchWithAuth, fetchMemory]
@@ -765,7 +1100,8 @@ function useThreads(autoFetch = true) {
765
1100
  headers: { "Content-Type": "application/json" },
766
1101
  body: JSON.stringify({ agentId: targetAgentId })
767
1102
  });
768
- if (!res.ok) throw new Error(`Failed to create thread: ${res.statusText}`);
1103
+ if (!res.ok)
1104
+ throw new Error(`Failed to create thread: ${res.statusText}`);
769
1105
  const data = await res.json();
770
1106
  const created = data?.data ?? data;
771
1107
  void fetchThreads();
@@ -775,8 +1111,11 @@ function useThreads(autoFetch = true) {
775
1111
  );
776
1112
  const deleteThread = useCallback3(
777
1113
  async (threadId) => {
778
- const res = await fetchWithAuth(`/threads/${threadId}`, { method: "DELETE" });
779
- if (!res.ok) throw new Error(`Failed to delete thread: ${res.statusText}`);
1114
+ const res = await fetchWithAuth(`/threads/${threadId}`, {
1115
+ method: "DELETE"
1116
+ });
1117
+ if (!res.ok)
1118
+ throw new Error(`Failed to delete thread: ${res.statusText}`);
780
1119
  setThreads((prev) => prev.filter((t) => t._id !== threadId));
781
1120
  },
782
1121
  [fetchWithAuth]
@@ -788,7 +1127,8 @@ function useThreads(autoFetch = true) {
788
1127
  headers: { "Content-Type": "application/json" },
789
1128
  body: JSON.stringify({ ids: threadIds })
790
1129
  });
791
- if (!res.ok) throw new Error(`Failed to bulk-delete threads: ${res.statusText}`);
1130
+ if (!res.ok)
1131
+ throw new Error(`Failed to bulk-delete threads: ${res.statusText}`);
792
1132
  const data = await res.json();
793
1133
  const result = data?.data ?? data;
794
1134
  const deletedSet = new Set(result.deleted);
@@ -810,10 +1150,13 @@ function useThreads(autoFetch = true) {
810
1150
  headers: { "Content-Type": "application/json" },
811
1151
  body: JSON.stringify(input)
812
1152
  });
813
- if (!res.ok) throw new Error(`Failed to update thread: ${res.statusText}`);
1153
+ if (!res.ok)
1154
+ throw new Error(`Failed to update thread: ${res.statusText}`);
814
1155
  const data = await res.json();
815
1156
  const updated = data?.data ?? data;
816
- setThreads((prev) => prev.map((t) => t._id === threadId ? { ...t, ...updated } : t));
1157
+ setThreads(
1158
+ (prev) => prev.map((t) => t._id === threadId ? { ...t, ...updated } : t)
1159
+ );
817
1160
  return updated;
818
1161
  },
819
1162
  [fetchWithAuth]
@@ -822,6 +1165,21 @@ function useThreads(autoFetch = true) {
822
1165
  (threadId, title) => updateThread(threadId, { title }),
823
1166
  [updateThread]
824
1167
  );
1168
+ const resetThread = useCallback3(
1169
+ async (threadId) => {
1170
+ const res = await fetchWithAuth(`/threads/${threadId}/reset`, {
1171
+ method: "POST"
1172
+ });
1173
+ if (!res.ok) throw new Error(`Failed to reset thread: ${res.statusText}`);
1174
+ const data = await res.json();
1175
+ const reset = data?.data ?? data;
1176
+ setThreads(
1177
+ (prev) => prev.map((t) => t._id === threadId ? { ...t, ...reset } : t)
1178
+ );
1179
+ return reset;
1180
+ },
1181
+ [fetchWithAuth]
1182
+ );
825
1183
  const getThread = useCallback3(
826
1184
  async (threadId) => {
827
1185
  const res = await fetchWithAuth(`/threads/${threadId}`);
@@ -847,6 +1205,7 @@ function useThreads(autoFetch = true) {
847
1205
  deleteAllThreads,
848
1206
  updateThread,
849
1207
  renameThread,
1208
+ resetThread,
850
1209
  getThread
851
1210
  };
852
1211
  }
@@ -925,19 +1284,17 @@ function useFiles(autoFetch = true) {
925
1284
  headers: { "Content-Type": "application/json" },
926
1285
  body: JSON.stringify({ ids: fileIds })
927
1286
  });
928
- if (!res.ok) throw new Error(`Failed to bulk-delete files: ${res.statusText}`);
1287
+ if (!res.ok)
1288
+ throw new Error(`Failed to bulk-delete files: ${res.statusText}`);
929
1289
  const idSet = new Set(fileIds);
930
1290
  setFiles((prev) => prev.filter((f) => !idSet.has(f.id)));
931
1291
  return await res.json();
932
1292
  },
933
1293
  [fetchWithAuth]
934
1294
  );
935
- const getDownloadUrl = useCallback4(
936
- (fileId) => {
937
- return `/files/${fileId}`;
938
- },
939
- []
940
- );
1295
+ const getDownloadUrl = useCallback4((fileId) => {
1296
+ return `/files/${fileId}`;
1297
+ }, []);
941
1298
  useEffect4(() => {
942
1299
  if (autoFetch) {
943
1300
  void fetchFiles();
@@ -1049,8 +1406,11 @@ function useMcpConnections(options = {}) {
1049
1406
  try {
1050
1407
  const returnTo = options.returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
1051
1408
  const query = returnTo ? `?returnTo=${encodeURIComponent(returnTo)}` : "";
1052
- const res = await fetchWithAuth(`/agents/${agentId}/mcp-connections${query}`);
1053
- if (!res.ok) throw new Error(`Failed to load MCP connections: ${res.statusText}`);
1409
+ const res = await fetchWithAuth(
1410
+ `/agents/${agentId}/mcp-connections${query}`
1411
+ );
1412
+ if (!res.ok)
1413
+ throw new Error(`Failed to load MCP connections: ${res.statusText}`);
1054
1414
  const data = await res.json();
1055
1415
  const items = Array.isArray(data) ? data : data?.items || [];
1056
1416
  setConnections(items);
@@ -1077,11 +1437,23 @@ function useMcpConnections(options = {}) {
1077
1437
  }
1078
1438
 
1079
1439
  // src/index.ts
1080
- var VERSION = "0.4.0";
1440
+ import {
1441
+ createLogger as createLogger2,
1442
+ createNoopLogger,
1443
+ setLogLevel,
1444
+ getLogLevel,
1445
+ isLevelEnabled
1446
+ } from "@personaai/logger";
1447
+ var VERSION = "0.6.0";
1081
1448
  export {
1082
1449
  PersonaProvider,
1083
1450
  VERSION,
1451
+ createLogger2 as createLogger,
1452
+ createNoopLogger,
1453
+ getLogLevel,
1454
+ isLevelEnabled,
1084
1455
  openSSEStream,
1456
+ setLogLevel,
1085
1457
  supportsStreamingFetch,
1086
1458
  useAgents,
1087
1459
  useChat,