@personaai/react 0.9.0 → 0.10.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/README.md +23 -0
- package/dist/index.cjs +905 -347
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +79 -2
- package/dist/index.d.ts +79 -2
- package/dist/index.js +903 -347
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -286,7 +286,7 @@ async function openSSEStream(opts) {
|
|
|
286
286
|
};
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
// src/hooks/
|
|
289
|
+
// src/hooks/streamEventHelpers.ts
|
|
290
290
|
function isErrorToolContent(content) {
|
|
291
291
|
if (typeof content !== "string" || !content.trim().startsWith("{"))
|
|
292
292
|
return false;
|
|
@@ -332,6 +332,38 @@ function parsePresentedFile(content) {
|
|
|
332
332
|
return null;
|
|
333
333
|
}
|
|
334
334
|
}
|
|
335
|
+
function normalizeWorkspaceFiles(raw) {
|
|
336
|
+
const normalized = {};
|
|
337
|
+
for (const [path, file] of Object.entries(raw || {})) {
|
|
338
|
+
normalized[path] = {
|
|
339
|
+
content: file.content,
|
|
340
|
+
size: file.size,
|
|
341
|
+
createdAt: file.created_at,
|
|
342
|
+
modifiedAt: file.modified_at
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
return normalized;
|
|
346
|
+
}
|
|
347
|
+
function normalizePendingInterrupt(pending) {
|
|
348
|
+
if (!pending || typeof pending !== "object") return null;
|
|
349
|
+
const p = pending;
|
|
350
|
+
if (p.kind === "hitl") {
|
|
351
|
+
return {
|
|
352
|
+
kind: "hitl",
|
|
353
|
+
actionRequests: p.value?.actionRequests ?? [],
|
|
354
|
+
reviewConfigs: p.value?.reviewConfigs ?? []
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
if (p.kind === "clarification") {
|
|
358
|
+
return {
|
|
359
|
+
kind: "clarification",
|
|
360
|
+
questions: p.value?.questions ?? []
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/hooks/useChat.ts
|
|
335
367
|
function parseSandboxCommand(tc) {
|
|
336
368
|
let command = tc.args ?? "";
|
|
337
369
|
try {
|
|
@@ -363,36 +395,6 @@ function parseSandboxCommand(tc) {
|
|
|
363
395
|
seq: tc.seq
|
|
364
396
|
};
|
|
365
397
|
}
|
|
366
|
-
function normalizeWorkspaceFiles(raw) {
|
|
367
|
-
const normalized = {};
|
|
368
|
-
for (const [path, file] of Object.entries(raw || {})) {
|
|
369
|
-
normalized[path] = {
|
|
370
|
-
content: file.content,
|
|
371
|
-
size: file.size,
|
|
372
|
-
createdAt: file.created_at,
|
|
373
|
-
modifiedAt: file.modified_at
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
return normalized;
|
|
377
|
-
}
|
|
378
|
-
function normalizePendingInterrupt(pending) {
|
|
379
|
-
if (!pending || typeof pending !== "object") return null;
|
|
380
|
-
const p = pending;
|
|
381
|
-
if (p.kind === "hitl") {
|
|
382
|
-
return {
|
|
383
|
-
kind: "hitl",
|
|
384
|
-
actionRequests: p.value?.actionRequests ?? [],
|
|
385
|
-
reviewConfigs: p.value?.reviewConfigs ?? []
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
if (p.kind === "clarification") {
|
|
389
|
-
return {
|
|
390
|
-
kind: "clarification",
|
|
391
|
-
questions: p.value?.questions ?? []
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
return null;
|
|
395
|
-
}
|
|
396
398
|
function useChat(options = {}) {
|
|
397
399
|
const { defaultAgentId, fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
|
|
398
400
|
const chatLogger = useMemo2(() => logger.child("chat"), [logger]);
|
|
@@ -457,7 +459,707 @@ function useChat(options = {}) {
|
|
|
457
459
|
chatLogger.trace("stop called \u2014 no active stream");
|
|
458
460
|
}
|
|
459
461
|
}, [chatLogger]);
|
|
460
|
-
const clear = useCallback(() => {
|
|
462
|
+
const clear = useCallback(() => {
|
|
463
|
+
chatLogger.info("clear chat", { messageCount: messages.length });
|
|
464
|
+
chatLogger.debug("clear", { messageCount: messages.length });
|
|
465
|
+
stop();
|
|
466
|
+
setMessages([]);
|
|
467
|
+
setError(null);
|
|
468
|
+
setInterrupt(null);
|
|
469
|
+
setFiles({});
|
|
470
|
+
setTodos([]);
|
|
471
|
+
setPresentedFile(null);
|
|
472
|
+
loadedThreadIdRef.current = void 0;
|
|
473
|
+
}, [stop, chatLogger, messages.length]);
|
|
474
|
+
const startNewChat = useCallback(() => {
|
|
475
|
+
chatLogger.info("startNewChat \u2014 entering ephemeral mode");
|
|
476
|
+
stop();
|
|
477
|
+
setMessages([]);
|
|
478
|
+
setError(null);
|
|
479
|
+
setInterrupt(null);
|
|
480
|
+
setFiles({});
|
|
481
|
+
setTodos([]);
|
|
482
|
+
setPresentedFile(null);
|
|
483
|
+
setInput("");
|
|
484
|
+
loadedThreadIdRef.current = void 0;
|
|
485
|
+
voiceThreadRef.current = void 0;
|
|
486
|
+
voicePrevLenRef.current = 0;
|
|
487
|
+
voiceStreamingIdRef.current = null;
|
|
488
|
+
voiceUserPartialIdRef.current = null;
|
|
489
|
+
setInternalThreadId(void 0);
|
|
490
|
+
}, [stop, chatLogger]);
|
|
491
|
+
const loadThreadMessages = useCallback(
|
|
492
|
+
async (id) => {
|
|
493
|
+
chatLogger.debug("loadThreadMessages start", { threadId: id });
|
|
494
|
+
chatLogger.trace("loadThreadMessages", { threadId: id });
|
|
495
|
+
setIsLoadingHistory(true);
|
|
496
|
+
setError(null);
|
|
497
|
+
try {
|
|
498
|
+
const res = await fetchWithAuth(`/threads/${id}/messages`);
|
|
499
|
+
if (!res.ok)
|
|
500
|
+
throw new Error(`Failed to load thread history: ${res.statusText}`);
|
|
501
|
+
const body = await res.json();
|
|
502
|
+
const data = body?.data ?? body;
|
|
503
|
+
const raw = data?.messages ?? [];
|
|
504
|
+
const subagentTraces = data?.subagentTraces ?? {};
|
|
505
|
+
const loaded = raw.map((m, i) => ({
|
|
506
|
+
id: m.id || `history-${id}-${i}`,
|
|
507
|
+
role: m.role,
|
|
508
|
+
content: m.content,
|
|
509
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
510
|
+
toolCalls: m.toolCalls?.map((tc) => {
|
|
511
|
+
const trace = subagentTraces[tc.toolCallId];
|
|
512
|
+
return Array.isArray(trace) && trace.length > 0 ? {
|
|
513
|
+
...tc,
|
|
514
|
+
subagentActivity: persistedTraceToActivityEntries(trace)
|
|
515
|
+
} : tc;
|
|
516
|
+
})
|
|
517
|
+
}));
|
|
518
|
+
setMessages(loaded);
|
|
519
|
+
setInterrupt(normalizePendingInterrupt(data?.pendingInterrupt));
|
|
520
|
+
setFiles(normalizeWorkspaceFiles(data?.state?.files ?? {}));
|
|
521
|
+
setTodos(data?.state?.todos ?? []);
|
|
522
|
+
loadedThreadIdRef.current = id;
|
|
523
|
+
chatLogger.info("loadThreadMessages succeeded", {
|
|
524
|
+
threadId: id,
|
|
525
|
+
messageCount: loaded.length
|
|
526
|
+
});
|
|
527
|
+
chatLogger.debug("loadThreadMessages completed", {
|
|
528
|
+
threadId: id,
|
|
529
|
+
messageCount: loaded.length
|
|
530
|
+
});
|
|
531
|
+
return loaded;
|
|
532
|
+
} catch (err) {
|
|
533
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
534
|
+
chatLogger.warn("loadThreadMessages failed", {
|
|
535
|
+
threadId: id,
|
|
536
|
+
error: errorObj.message
|
|
537
|
+
});
|
|
538
|
+
chatLogger.error("loadThreadMessages error", {
|
|
539
|
+
threadId: id,
|
|
540
|
+
error: errorObj.message
|
|
541
|
+
});
|
|
542
|
+
setError(errorObj);
|
|
543
|
+
if (loadedThreadIdRef.current === id) loadedThreadIdRef.current = void 0;
|
|
544
|
+
return [];
|
|
545
|
+
} finally {
|
|
546
|
+
setIsLoadingHistory(false);
|
|
547
|
+
chatLogger.trace("loadThreadMessages end", { threadId: id });
|
|
548
|
+
}
|
|
549
|
+
},
|
|
550
|
+
[fetchWithAuth, chatLogger]
|
|
551
|
+
);
|
|
552
|
+
useEffect(() => {
|
|
553
|
+
if (!effectiveThreadId || isStreaming) {
|
|
554
|
+
chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
if (loadedThreadIdRef.current === effectiveThreadId) {
|
|
558
|
+
chatLogger.trace("auto-load already loaded", { threadId: effectiveThreadId });
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (messages.length > 0) {
|
|
562
|
+
chatLogger.trace("auto-load has messages", {
|
|
563
|
+
threadId: effectiveThreadId,
|
|
564
|
+
count: messages.length
|
|
565
|
+
});
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
chatLogger.info("auto-load thread history", { threadId: effectiveThreadId });
|
|
569
|
+
chatLogger.debug("loadThreadMessages trigger", { threadId: effectiveThreadId });
|
|
570
|
+
void loadThreadMessages(effectiveThreadId);
|
|
571
|
+
}, [effectiveThreadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
|
|
572
|
+
const voice = options.voice;
|
|
573
|
+
useEffect(() => {
|
|
574
|
+
if (!voice) return;
|
|
575
|
+
if (voiceThreadRef.current !== effectiveThreadId) {
|
|
576
|
+
voiceThreadRef.current = effectiveThreadId;
|
|
577
|
+
voicePrevLenRef.current = voice.transcript.length;
|
|
578
|
+
voiceStreamingIdRef.current = null;
|
|
579
|
+
voiceUserPartialIdRef.current = null;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
|
|
583
|
+
const curLen = voice.transcript.length;
|
|
584
|
+
if (curLen < voicePrevLenRef.current) {
|
|
585
|
+
voicePrevLenRef.current = 0;
|
|
586
|
+
voiceStreamingIdRef.current = null;
|
|
587
|
+
voiceUserPartialIdRef.current = null;
|
|
588
|
+
}
|
|
589
|
+
if (!isVoiceActive) {
|
|
590
|
+
if (curLen > voicePrevLenRef.current) {
|
|
591
|
+
const pendingLines = voice.transcript.slice(voicePrevLenRef.current);
|
|
592
|
+
voicePrevLenRef.current = curLen;
|
|
593
|
+
for (const line of pendingLines) {
|
|
594
|
+
const text = (line.text || "").trim();
|
|
595
|
+
if (!text) continue;
|
|
596
|
+
const role = line.speaker === "user" ? "user" : "assistant";
|
|
597
|
+
const voiceId = `voice-${line.id}`;
|
|
598
|
+
setMessages((prev) => {
|
|
599
|
+
if (prev.some((m) => m.id === voiceId)) return prev;
|
|
600
|
+
if (prev.some((m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text)) return prev;
|
|
601
|
+
const last = prev[prev.length - 1];
|
|
602
|
+
if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
|
|
603
|
+
const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
|
|
604
|
+
return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
|
|
605
|
+
}
|
|
606
|
+
return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
voicePrevLenRef.current = voice.transcript.length;
|
|
611
|
+
}
|
|
612
|
+
if (voiceStreamingIdRef.current) {
|
|
613
|
+
const doneId = voiceStreamingIdRef.current;
|
|
614
|
+
voiceStreamingIdRef.current = null;
|
|
615
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
616
|
+
}
|
|
617
|
+
if (voiceUserPartialIdRef.current) {
|
|
618
|
+
const doneId = voiceUserPartialIdRef.current;
|
|
619
|
+
voiceUserPartialIdRef.current = null;
|
|
620
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
621
|
+
}
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (curLen > voicePrevLenRef.current) {
|
|
625
|
+
const newLines = voice.transcript.slice(voicePrevLenRef.current);
|
|
626
|
+
voicePrevLenRef.current = curLen;
|
|
627
|
+
for (const line of newLines) {
|
|
628
|
+
const text = (line.text || "").trim();
|
|
629
|
+
if (!text) continue;
|
|
630
|
+
const role = line.speaker === "user" ? "user" : "assistant";
|
|
631
|
+
const voiceId = `voice-${line.id}`;
|
|
632
|
+
setMessages((prev) => {
|
|
633
|
+
if (prev.some((m) => m.id === voiceId)) return prev;
|
|
634
|
+
if (prev.some(
|
|
635
|
+
(m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text
|
|
636
|
+
))
|
|
637
|
+
return prev;
|
|
638
|
+
const last = prev[prev.length - 1];
|
|
639
|
+
if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
|
|
640
|
+
const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
|
|
641
|
+
return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
|
|
642
|
+
}
|
|
643
|
+
return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
const agentPartialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
|
|
648
|
+
const userPartialText = voice.partial?.speaker === "user" ? voice.partial.text.trim() : "";
|
|
649
|
+
if (agentPartialText) {
|
|
650
|
+
if (voiceUserPartialIdRef.current) {
|
|
651
|
+
const doneId = voiceUserPartialIdRef.current;
|
|
652
|
+
voiceUserPartialIdRef.current = null;
|
|
653
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
654
|
+
}
|
|
655
|
+
setMessages((prev) => {
|
|
656
|
+
const last = prev[prev.length - 1];
|
|
657
|
+
if (last?.id.startsWith("voice-") && last.role === "assistant") {
|
|
658
|
+
voiceStreamingIdRef.current = last.id;
|
|
659
|
+
return [...prev.slice(0, -1), { ...last, content: agentPartialText, isStreaming: true }];
|
|
660
|
+
}
|
|
661
|
+
const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
|
|
662
|
+
voiceStreamingIdRef.current = id;
|
|
663
|
+
return [...prev, { id, role: "assistant", content: agentPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
664
|
+
});
|
|
665
|
+
} else if (userPartialText) {
|
|
666
|
+
if (voiceStreamingIdRef.current) {
|
|
667
|
+
const doneId = voiceStreamingIdRef.current;
|
|
668
|
+
voiceStreamingIdRef.current = null;
|
|
669
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
670
|
+
}
|
|
671
|
+
setMessages((prev) => {
|
|
672
|
+
const last = prev[prev.length - 1];
|
|
673
|
+
if (last?.id.startsWith("voice-") && last.role === "user" && last.isStreaming) {
|
|
674
|
+
voiceUserPartialIdRef.current = last.id;
|
|
675
|
+
return [...prev.slice(0, -1), { ...last, content: userPartialText, isStreaming: true }];
|
|
676
|
+
}
|
|
677
|
+
const id = voiceUserPartialIdRef.current || `voice-partial-user-${Date.now()}`;
|
|
678
|
+
voiceUserPartialIdRef.current = id;
|
|
679
|
+
return [...prev, { id, role: "user", content: userPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
680
|
+
});
|
|
681
|
+
} else {
|
|
682
|
+
if (voiceStreamingIdRef.current && voice.state !== "speaking") {
|
|
683
|
+
const doneId = voiceStreamingIdRef.current;
|
|
684
|
+
voiceStreamingIdRef.current = null;
|
|
685
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
686
|
+
}
|
|
687
|
+
if (voiceUserPartialIdRef.current) {
|
|
688
|
+
const doneId = voiceUserPartialIdRef.current;
|
|
689
|
+
voiceUserPartialIdRef.current = null;
|
|
690
|
+
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}, [voice?.transcript, voice?.partial, voice?.state, effectiveThreadId]);
|
|
694
|
+
const doSend = useCallback(
|
|
695
|
+
async (prompt, baseMessages, overrideOptions) => {
|
|
696
|
+
const targetAgentId = overrideOptions?.agentId || agentId;
|
|
697
|
+
if (!targetAgentId) {
|
|
698
|
+
const err = new Error("No Agent ID specified for useChat.");
|
|
699
|
+
chatLogger.warn("sendMessage no agentId", {});
|
|
700
|
+
chatLogger.error("sendMessage failed \u2014 no agent", { error: err.message });
|
|
701
|
+
setError(err);
|
|
702
|
+
options.onError?.(err);
|
|
703
|
+
return false;
|
|
704
|
+
}
|
|
705
|
+
chatLogger.debug("sendMessage start", {
|
|
706
|
+
promptPreview: prompt.slice(0, 100),
|
|
707
|
+
agentId: targetAgentId,
|
|
708
|
+
threadId: effectiveThreadId ?? overrideOptions?.threadId,
|
|
709
|
+
hasResume: !!overrideOptions?.resume,
|
|
710
|
+
hasContextOverride: !!overrideOptions?.contextOverride,
|
|
711
|
+
messageCount: baseMessages.length
|
|
712
|
+
});
|
|
713
|
+
chatLogger.trace("sendMessage details", {
|
|
714
|
+
agentId: targetAgentId,
|
|
715
|
+
promptPreview: prompt.slice(0, 200),
|
|
716
|
+
hasResume: !!overrideOptions?.resume,
|
|
717
|
+
hasContextOverride: !!overrideOptions?.contextOverride
|
|
718
|
+
});
|
|
719
|
+
const userMessage = {
|
|
720
|
+
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
721
|
+
role: "user",
|
|
722
|
+
content: prompt,
|
|
723
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
724
|
+
};
|
|
725
|
+
const assistantMessageId = `assistant-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
726
|
+
const placeholderAssistant = {
|
|
727
|
+
id: assistantMessageId,
|
|
728
|
+
role: "assistant",
|
|
729
|
+
content: "",
|
|
730
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
731
|
+
isStreaming: true,
|
|
732
|
+
toolCalls: []
|
|
733
|
+
};
|
|
734
|
+
const nextMessages = [...baseMessages, userMessage];
|
|
735
|
+
setMessages([...nextMessages, placeholderAssistant]);
|
|
736
|
+
setInput("");
|
|
737
|
+
setIsStreaming(true);
|
|
738
|
+
setError(null);
|
|
739
|
+
setInterrupt(null);
|
|
740
|
+
const controller = new AbortController();
|
|
741
|
+
abortControllerRef.current = controller;
|
|
742
|
+
const finalizeReasoning = () => {
|
|
743
|
+
setMessages(
|
|
744
|
+
(prev) => prev.map((m) => m.role === "reasoning" && m.isStreaming ? { ...m, isStreaming: false } : m)
|
|
745
|
+
);
|
|
746
|
+
};
|
|
747
|
+
try {
|
|
748
|
+
const payloadMessages = nextMessages.filter((m) => m.role !== "reasoning").map((m) => ({
|
|
749
|
+
role: m.role === "assistant" ? "assistant" : "user",
|
|
750
|
+
content: m.content
|
|
751
|
+
}));
|
|
752
|
+
let resolvedThreadId = await overrideOptions?.threadId ?? effectiveThreadId;
|
|
753
|
+
if (!resolvedThreadId) {
|
|
754
|
+
chatLogger.info("minting real thread for ephemeral chat", { agentId: targetAgentId });
|
|
755
|
+
const createRes = await fetchWithAuth("/threads", {
|
|
756
|
+
method: "POST",
|
|
757
|
+
headers: { "Content-Type": "application/json" },
|
|
758
|
+
body: JSON.stringify({ agentId: targetAgentId })
|
|
759
|
+
});
|
|
760
|
+
if (!createRes.ok) throw new Error(`Failed to create thread: ${createRes.statusText}`);
|
|
761
|
+
const createBody = await createRes.json();
|
|
762
|
+
const created = createBody?.data ?? createBody;
|
|
763
|
+
resolvedThreadId = created._id ?? created.id;
|
|
764
|
+
if (!resolvedThreadId) throw new Error("Thread creation returned no id");
|
|
765
|
+
setInternalThreadId(resolvedThreadId);
|
|
766
|
+
loadedThreadIdRef.current = resolvedThreadId;
|
|
767
|
+
options.onThreadCreated?.(resolvedThreadId);
|
|
768
|
+
chatLogger.info("ephemeral chat minted", { threadId: resolvedThreadId });
|
|
769
|
+
}
|
|
770
|
+
chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
|
|
771
|
+
const token = getAuthToken ? await getAuthToken() : null;
|
|
772
|
+
const resolvedHookContext = typeof options.context === "function" ? options.context() : options.context;
|
|
773
|
+
const mergedContext = { ...resolvedHookContext, ...overrideOptions?.context };
|
|
774
|
+
const hasContext = Object.keys(mergedContext).length > 0;
|
|
775
|
+
chatLogger.debug("opening SSE stream", {
|
|
776
|
+
agentId: targetAgentId,
|
|
777
|
+
hasToken: !!token,
|
|
778
|
+
hasThreadId: !!resolvedThreadId,
|
|
779
|
+
hasContext
|
|
780
|
+
});
|
|
781
|
+
const stream = await openSSEStream({
|
|
782
|
+
url: `${baseUrl}/chat`,
|
|
783
|
+
headers: {
|
|
784
|
+
"Content-Type": "application/json",
|
|
785
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
786
|
+
},
|
|
787
|
+
body: JSON.stringify({
|
|
788
|
+
agentId: targetAgentId,
|
|
789
|
+
messages: payloadMessages,
|
|
790
|
+
threadId: resolvedThreadId,
|
|
791
|
+
resume: overrideOptions?.resume,
|
|
792
|
+
contextOverride: overrideOptions?.contextOverride,
|
|
793
|
+
...hasContext ? { context: mergedContext } : {}
|
|
794
|
+
}),
|
|
795
|
+
signal: controller.signal
|
|
796
|
+
});
|
|
797
|
+
if (!stream.ok) {
|
|
798
|
+
chatLogger.warn("SSE stream not ok", { status: stream.status, errorText: stream.errorText });
|
|
799
|
+
chatLogger.error("chat stream failed", { agentId: targetAgentId, status: stream.status, error: stream.errorText });
|
|
800
|
+
throw new Error(`Chat error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
|
|
801
|
+
}
|
|
802
|
+
chatLogger.debug("SSE stream opened", { agentId: targetAgentId, status: stream.status });
|
|
803
|
+
chatLogger.info("chat stream started", { agentId: targetAgentId, threadId: resolvedThreadId });
|
|
804
|
+
const reader = stream.reader;
|
|
805
|
+
let buffer = "";
|
|
806
|
+
let accumulatedText = "";
|
|
807
|
+
const toolCallsMap = /* @__PURE__ */ new Map();
|
|
808
|
+
let streamSeq = 0;
|
|
809
|
+
let activeReasoningId = null;
|
|
810
|
+
const reasoningById = /* @__PURE__ */ new Map();
|
|
811
|
+
const patchAssistant = (patch) => {
|
|
812
|
+
setMessages(
|
|
813
|
+
(prev) => prev.map(
|
|
814
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, toolCalls: Array.from(toolCallsMap.values()), ...patch } : msg
|
|
815
|
+
)
|
|
816
|
+
);
|
|
817
|
+
};
|
|
818
|
+
const insertReasoningMessage = (id, seq) => {
|
|
819
|
+
const msg = { id, role: "reasoning", content: "", createdAt: /* @__PURE__ */ new Date(), isStreaming: true, seq };
|
|
820
|
+
setMessages((prev) => {
|
|
821
|
+
const idx = prev.findIndex((m) => m.id === assistantMessageId);
|
|
822
|
+
if (idx === -1) return [...prev, msg];
|
|
823
|
+
const next = [...prev];
|
|
824
|
+
next.splice(idx, 0, msg);
|
|
825
|
+
return next;
|
|
826
|
+
});
|
|
827
|
+
};
|
|
828
|
+
while (true) {
|
|
829
|
+
const { done, value } = await reader.read();
|
|
830
|
+
if (done) break;
|
|
831
|
+
buffer += value ?? "";
|
|
832
|
+
const lines = buffer.split("\n");
|
|
833
|
+
buffer = lines.pop() ?? "";
|
|
834
|
+
for (const line of lines) {
|
|
835
|
+
const trimmed = line.trim();
|
|
836
|
+
if (!trimmed || !trimmed.startsWith("data:")) continue;
|
|
837
|
+
const raw = trimmed.replace(/^data:\s*/, "");
|
|
838
|
+
if (raw === "[DONE]") break;
|
|
839
|
+
try {
|
|
840
|
+
const event = JSON.parse(raw);
|
|
841
|
+
chatLogger.trace("stream event", { type: event.type, event });
|
|
842
|
+
options.onEvent?.(event);
|
|
843
|
+
if (event.type === "TEXT_MESSAGE_CHUNK" && event.delta) {
|
|
844
|
+
chatLogger.debug("text chunk", { deltaLength: event.delta.length });
|
|
845
|
+
accumulatedText += event.delta;
|
|
846
|
+
patchAssistant({ content: accumulatedText, isStreaming: true });
|
|
847
|
+
} else if (event.type === "TOOL_CALL_CHUNK" && event.toolCallId) {
|
|
848
|
+
chatLogger.debug("tool call chunk", { toolCallId: event.toolCallId, toolCallName: event.toolCallName });
|
|
849
|
+
const existing = toolCallsMap.get(event.toolCallId);
|
|
850
|
+
if (existing) {
|
|
851
|
+
existing.args = (existing.args || "") + (event.delta || "");
|
|
852
|
+
} else {
|
|
853
|
+
toolCallsMap.set(event.toolCallId, {
|
|
854
|
+
toolCallId: event.toolCallId,
|
|
855
|
+
toolName: event.toolCallName || "",
|
|
856
|
+
args: event.delta || "",
|
|
857
|
+
seq: streamSeq++
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
patchAssistant({});
|
|
861
|
+
} else if (event.type === "TOOL_CALL_RESULT") {
|
|
862
|
+
chatLogger.debug("tool call result", { toolCallId: event.toolCallId });
|
|
863
|
+
const existing = toolCallsMap.get(event.toolCallId);
|
|
864
|
+
if (existing) {
|
|
865
|
+
existing.result = event.content;
|
|
866
|
+
existing.isError = isErrorToolContent(event.content);
|
|
867
|
+
if (existing.isError) chatLogger.warn("tool call error", { toolCallId: event.toolCallId });
|
|
868
|
+
if (existing.toolName === "present_file" && !existing.isError) {
|
|
869
|
+
const presented = parsePresentedFile(event.content);
|
|
870
|
+
if (presented) {
|
|
871
|
+
chatLogger.info("present_file", { path: presented.path });
|
|
872
|
+
setPresentedFile(presented);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
patchAssistant({});
|
|
876
|
+
}
|
|
877
|
+
} else if (event.type === "STATE_SNAPSHOT") {
|
|
878
|
+
chatLogger.debug("state snapshot", {
|
|
879
|
+
fileCount: Object.keys(event.snapshot.files ?? {}).length,
|
|
880
|
+
todoCount: event.snapshot.todos?.length ?? 0
|
|
881
|
+
});
|
|
882
|
+
setFiles(normalizeWorkspaceFiles(event.snapshot.files ?? {}));
|
|
883
|
+
setTodos(event.snapshot.todos ?? []);
|
|
884
|
+
} else if (event.type === "REASONING_MESSAGE_START" && event.messageId) {
|
|
885
|
+
chatLogger.debug("reasoning start", { messageId: event.messageId });
|
|
886
|
+
activeReasoningId = event.messageId;
|
|
887
|
+
reasoningById.set(event.messageId, { content: "" });
|
|
888
|
+
insertReasoningMessage(event.messageId, streamSeq++);
|
|
889
|
+
} else if (event.type === "REASONING_MESSAGE_CONTENT") {
|
|
890
|
+
let rid = event.messageId || activeReasoningId || "";
|
|
891
|
+
if (!rid || !reasoningById.has(rid)) {
|
|
892
|
+
rid = rid || `reasoning-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
893
|
+
activeReasoningId = rid;
|
|
894
|
+
reasoningById.set(rid, { content: "" });
|
|
895
|
+
insertReasoningMessage(rid, streamSeq++);
|
|
896
|
+
chatLogger.debug("reasoning lazy start", { messageId: rid });
|
|
897
|
+
}
|
|
898
|
+
const entry = reasoningById.get(rid);
|
|
899
|
+
entry.content += event.delta;
|
|
900
|
+
chatLogger.trace("reasoning content", { messageId: rid, deltaLength: event.delta?.length ?? 0 });
|
|
901
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, content: entry.content, isStreaming: true } : m));
|
|
902
|
+
} else if (event.type === "REASONING_END") {
|
|
903
|
+
chatLogger.debug("reasoning end", { messageId: activeReasoningId });
|
|
904
|
+
const rid = activeReasoningId;
|
|
905
|
+
activeReasoningId = null;
|
|
906
|
+
if (rid) {
|
|
907
|
+
setMessages((prev) => prev.map((m) => m.id === rid ? { ...m, isStreaming: false } : m));
|
|
908
|
+
}
|
|
909
|
+
} else if (event.type === "CUSTOM") {
|
|
910
|
+
chatLogger.debug("custom event", { name: event.name });
|
|
911
|
+
if (event.name === "hitl_request") {
|
|
912
|
+
const value2 = event.value;
|
|
913
|
+
chatLogger.info("hitl interrupt", { actionCount: value2.actionRequests?.length ?? 0 });
|
|
914
|
+
setInterrupt({ kind: "hitl", actionRequests: value2.actionRequests, reviewConfigs: value2.reviewConfigs });
|
|
915
|
+
} else if (event.name === "clarification_request") {
|
|
916
|
+
const value2 = event.value;
|
|
917
|
+
chatLogger.info("clarification interrupt", { questionCount: value2.questions?.length ?? 0 });
|
|
918
|
+
setInterrupt({ kind: "clarification", questions: value2.questions });
|
|
919
|
+
} else if (event.name === "subagent_activity") {
|
|
920
|
+
const { toolCallId, ...entry } = event.value;
|
|
921
|
+
chatLogger.trace("subagent activity", { toolCallId, kind: entry.kind });
|
|
922
|
+
const existing = toolCallsMap.get(toolCallId);
|
|
923
|
+
if (existing) {
|
|
924
|
+
existing.subagentActivity = [...existing.subagentActivity || [], entry];
|
|
925
|
+
patchAssistant({});
|
|
926
|
+
}
|
|
927
|
+
} else if (event.name === "mcp_app") {
|
|
928
|
+
const val = event.value;
|
|
929
|
+
if (val?.toolCallId && val?.resourceUri && val?.mcpId) {
|
|
930
|
+
const existing = toolCallsMap.get(val.toolCallId);
|
|
931
|
+
if (existing) {
|
|
932
|
+
existing.mcpApp = { resourceUri: val.resourceUri, mcpId: val.mcpId };
|
|
933
|
+
patchAssistant({});
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
} else if (event.type === "title") {
|
|
938
|
+
const title = event.title;
|
|
939
|
+
if (typeof title === "string" && title.trim()) {
|
|
940
|
+
chatLogger.info("auto title", { title });
|
|
941
|
+
options.onTitle?.(title);
|
|
942
|
+
}
|
|
943
|
+
} else if (event.type === "RUN_ERROR") {
|
|
944
|
+
chatLogger.warn("run error", { message: event.message, code: event.code });
|
|
945
|
+
chatLogger.error("stream run error", { code: event.code, message: event.message });
|
|
946
|
+
throw new Error(event.message || "Stream error from agent");
|
|
947
|
+
} else {
|
|
948
|
+
chatLogger.trace("unhandled event", { type: event.type });
|
|
949
|
+
}
|
|
950
|
+
} catch (e) {
|
|
951
|
+
if (e instanceof Error && e.message.startsWith("Stream error")) throw e;
|
|
952
|
+
chatLogger.warn("event parse error", { raw: raw.slice(0, 200), error: e instanceof Error ? e.message : String(e) });
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
const finalMessage = {
|
|
957
|
+
id: assistantMessageId,
|
|
958
|
+
role: "assistant",
|
|
959
|
+
content: accumulatedText,
|
|
960
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
961
|
+
isStreaming: false,
|
|
962
|
+
toolCalls: Array.from(toolCallsMap.values())
|
|
963
|
+
};
|
|
964
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg));
|
|
965
|
+
chatLogger.info("sendMessage succeeded", { agentId: targetAgentId, textLength: accumulatedText.length, toolCallCount: toolCallsMap.size });
|
|
966
|
+
chatLogger.debug("sendMessage completed", { agentId: targetAgentId, textLength: accumulatedText.length, eventCount: toolCallsMap.size + 1 });
|
|
967
|
+
options.onFinish?.(finalMessage);
|
|
968
|
+
return true;
|
|
969
|
+
} catch (err) {
|
|
970
|
+
if (controller.signal.aborted) {
|
|
971
|
+
chatLogger.info("sendMessage aborted", { agentId: targetAgentId });
|
|
972
|
+
chatLogger.debug("stream aborted", { agentId: targetAgentId });
|
|
973
|
+
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
977
|
+
chatLogger.warn("sendMessage failed", { agentId: targetAgentId, error: errorObj.message });
|
|
978
|
+
chatLogger.error("sendMessage error", { agentId: targetAgentId, error: errorObj.message });
|
|
979
|
+
setError(errorObj);
|
|
980
|
+
options.onError?.(errorObj);
|
|
981
|
+
setMessages(
|
|
982
|
+
(prev) => prev.map(
|
|
983
|
+
(msg) => msg.id === assistantMessageId ? { ...msg, content: msg.content || `\u26A0\uFE0F Error: ${errorObj.message || "Failed to get response."}`, isStreaming: false } : msg
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
return false;
|
|
987
|
+
} finally {
|
|
988
|
+
setIsStreaming(false);
|
|
989
|
+
abortControllerRef.current = null;
|
|
990
|
+
finalizeReasoning();
|
|
991
|
+
chatLogger.debug("sendMessage finally", { agentId: targetAgentId, isStreaming: false });
|
|
992
|
+
chatLogger.trace("sendMessage end", { agentId: targetAgentId });
|
|
993
|
+
}
|
|
994
|
+
},
|
|
995
|
+
[agentId, baseUrl, getAuthToken, fetchWithAuth, options, chatLogger, effectiveThreadId]
|
|
996
|
+
);
|
|
997
|
+
const sendMessage = useCallback(
|
|
998
|
+
async (contentToSend, overrideOptions) => {
|
|
999
|
+
const prompt = (contentToSend ?? input).trim();
|
|
1000
|
+
if (!prompt || isStreaming) {
|
|
1001
|
+
chatLogger.trace("sendMessage skipped", {
|
|
1002
|
+
hasPrompt: !!prompt,
|
|
1003
|
+
isStreaming
|
|
1004
|
+
});
|
|
1005
|
+
if (isStreaming) {
|
|
1006
|
+
chatLogger.warn("sendMessage dropped while streaming \u2014 caller should queue or disable send", {});
|
|
1007
|
+
}
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
return doSend(prompt, messages, overrideOptions);
|
|
1011
|
+
},
|
|
1012
|
+
[input, isStreaming, messages, doSend, chatLogger]
|
|
1013
|
+
);
|
|
1014
|
+
const handleInputChange = useCallback(
|
|
1015
|
+
(e) => {
|
|
1016
|
+
chatLogger.trace("handleInputChange", { length: e.target.value.length });
|
|
1017
|
+
setInput(e.target.value);
|
|
1018
|
+
},
|
|
1019
|
+
[chatLogger]
|
|
1020
|
+
);
|
|
1021
|
+
const handleSubmit = useCallback(
|
|
1022
|
+
(e) => {
|
|
1023
|
+
if (e) e.preventDefault();
|
|
1024
|
+
chatLogger.debug("handleSubmit", {});
|
|
1025
|
+
void sendMessage();
|
|
1026
|
+
},
|
|
1027
|
+
[sendMessage, chatLogger]
|
|
1028
|
+
);
|
|
1029
|
+
const reload = useCallback(async () => {
|
|
1030
|
+
if (messages.length === 0 || isStreaming) {
|
|
1031
|
+
chatLogger.trace("reload skipped", {
|
|
1032
|
+
messageCount: messages.length,
|
|
1033
|
+
isStreaming
|
|
1034
|
+
});
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
let lastUserIndex = -1;
|
|
1038
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1039
|
+
if (messages[i].role === "user") {
|
|
1040
|
+
lastUserIndex = i;
|
|
1041
|
+
break;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (lastUserIndex === -1) {
|
|
1045
|
+
chatLogger.warn("reload no user message", {});
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
const lastUserMessage = messages[lastUserIndex];
|
|
1049
|
+
const truncated = messages.slice(0, lastUserIndex);
|
|
1050
|
+
chatLogger.info("reload", { messageId: lastUserMessage.id });
|
|
1051
|
+
chatLogger.debug("reload last user", {
|
|
1052
|
+
preview: lastUserMessage.content.slice(0, 100)
|
|
1053
|
+
});
|
|
1054
|
+
return doSend(lastUserMessage.content, truncated);
|
|
1055
|
+
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1056
|
+
const resumeInterrupt = useCallback(
|
|
1057
|
+
(resume, displayContent) => {
|
|
1058
|
+
chatLogger.info("resumeInterrupt", {
|
|
1059
|
+
kind: resume.decisions ? "hitl" : "clarification",
|
|
1060
|
+
preview: displayContent.slice(0, 50)
|
|
1061
|
+
});
|
|
1062
|
+
return sendMessage(displayContent, { resume });
|
|
1063
|
+
},
|
|
1064
|
+
[sendMessage, chatLogger]
|
|
1065
|
+
);
|
|
1066
|
+
const dismissPresentedFile = useCallback(() => {
|
|
1067
|
+
chatLogger.debug("dismissPresentedFile", {});
|
|
1068
|
+
setPresentedFile(null);
|
|
1069
|
+
}, [chatLogger]);
|
|
1070
|
+
const openWorkspaceFile = useCallback(
|
|
1071
|
+
(path) => {
|
|
1072
|
+
chatLogger.debug("openWorkspaceFile", { path });
|
|
1073
|
+
setPresentedFile({
|
|
1074
|
+
path,
|
|
1075
|
+
title: path.split("/").pop() || path,
|
|
1076
|
+
description: ""
|
|
1077
|
+
});
|
|
1078
|
+
},
|
|
1079
|
+
[chatLogger]
|
|
1080
|
+
);
|
|
1081
|
+
return {
|
|
1082
|
+
messages,
|
|
1083
|
+
input,
|
|
1084
|
+
setInput,
|
|
1085
|
+
handleInputChange,
|
|
1086
|
+
handleSubmit,
|
|
1087
|
+
sendMessage,
|
|
1088
|
+
isStreaming,
|
|
1089
|
+
isLoading: isStreaming,
|
|
1090
|
+
isLoadingHistory,
|
|
1091
|
+
error,
|
|
1092
|
+
interrupt,
|
|
1093
|
+
resumeInterrupt,
|
|
1094
|
+
files,
|
|
1095
|
+
todos,
|
|
1096
|
+
presentedFile,
|
|
1097
|
+
dismissPresentedFile,
|
|
1098
|
+
openWorkspaceFile,
|
|
1099
|
+
sandboxCommands,
|
|
1100
|
+
stop,
|
|
1101
|
+
reload,
|
|
1102
|
+
clear,
|
|
1103
|
+
startNewChat,
|
|
1104
|
+
currentThreadId: effectiveThreadId,
|
|
1105
|
+
isEphemeral: !effectiveThreadId,
|
|
1106
|
+
setMessages,
|
|
1107
|
+
loadThreadMessages
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/hooks/useArchitectChat.ts
|
|
1112
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
|
|
1113
|
+
var ARCHITECT_AGENT_ID = "000000000000000000000002";
|
|
1114
|
+
function useArchitectChat(options = {}) {
|
|
1115
|
+
const { fetchWithAuth, baseUrl, getAuthToken, logger } = usePersonaContext();
|
|
1116
|
+
const chatLogger = useMemo3(() => logger.child("architect"), [logger]);
|
|
1117
|
+
const threadId = options.threadId;
|
|
1118
|
+
const [internalThreadId, setInternalThreadId] = useState2(void 0);
|
|
1119
|
+
const effectiveThreadId = threadId ?? internalThreadId;
|
|
1120
|
+
useEffect2(() => {
|
|
1121
|
+
if (threadId !== void 0) setInternalThreadId(threadId);
|
|
1122
|
+
}, [threadId]);
|
|
1123
|
+
const didLogInitRef = useRef2(false);
|
|
1124
|
+
if (!didLogInitRef.current) {
|
|
1125
|
+
didLogInitRef.current = true;
|
|
1126
|
+
chatLogger.debug("useArchitectChat init", {
|
|
1127
|
+
threadId,
|
|
1128
|
+
hasInitialMessages: !!options.initialMessages?.length
|
|
1129
|
+
});
|
|
1130
|
+
chatLogger.trace("useArchitectChat options", {
|
|
1131
|
+
threadId,
|
|
1132
|
+
initialMessageCount: options.initialMessages?.length ?? 0
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
const [messages, setMessages] = useState2(
|
|
1136
|
+
options.initialMessages || []
|
|
1137
|
+
);
|
|
1138
|
+
const [input, setInput] = useState2("");
|
|
1139
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
1140
|
+
const [isLoadingHistory, setIsLoadingHistory] = useState2(false);
|
|
1141
|
+
const [error, setError] = useState2(null);
|
|
1142
|
+
const [interrupt, setInterrupt] = useState2(null);
|
|
1143
|
+
const [files, setFiles] = useState2({});
|
|
1144
|
+
const [todos, setTodos] = useState2([]);
|
|
1145
|
+
const [presentedFile, setPresentedFile] = useState2(null);
|
|
1146
|
+
const abortControllerRef = useRef2(null);
|
|
1147
|
+
const loadedThreadIdRef = useRef2(void 0);
|
|
1148
|
+
const stop = useCallback2(() => {
|
|
1149
|
+
if (abortControllerRef.current) {
|
|
1150
|
+
chatLogger.info("stop streaming", {});
|
|
1151
|
+
chatLogger.debug("abort controller", {
|
|
1152
|
+
hasController: !!abortControllerRef.current
|
|
1153
|
+
});
|
|
1154
|
+
abortControllerRef.current.abort();
|
|
1155
|
+
abortControllerRef.current = null;
|
|
1156
|
+
setIsStreaming(false);
|
|
1157
|
+
chatLogger.debug("streaming stopped");
|
|
1158
|
+
} else {
|
|
1159
|
+
chatLogger.trace("stop called \u2014 no active stream");
|
|
1160
|
+
}
|
|
1161
|
+
}, [chatLogger]);
|
|
1162
|
+
const clear = useCallback2(() => {
|
|
461
1163
|
chatLogger.info("clear chat", { messageCount: messages.length });
|
|
462
1164
|
chatLogger.debug("clear", { messageCount: messages.length });
|
|
463
1165
|
stop();
|
|
@@ -469,7 +1171,7 @@ function useChat(options = {}) {
|
|
|
469
1171
|
setPresentedFile(null);
|
|
470
1172
|
loadedThreadIdRef.current = void 0;
|
|
471
1173
|
}, [stop, chatLogger, messages.length]);
|
|
472
|
-
const startNewChat =
|
|
1174
|
+
const startNewChat = useCallback2(() => {
|
|
473
1175
|
chatLogger.info("startNewChat \u2014 entering ephemeral mode");
|
|
474
1176
|
stop();
|
|
475
1177
|
setMessages([]);
|
|
@@ -480,13 +1182,9 @@ function useChat(options = {}) {
|
|
|
480
1182
|
setPresentedFile(null);
|
|
481
1183
|
setInput("");
|
|
482
1184
|
loadedThreadIdRef.current = void 0;
|
|
483
|
-
voiceThreadRef.current = void 0;
|
|
484
|
-
voicePrevLenRef.current = 0;
|
|
485
|
-
voiceStreamingIdRef.current = null;
|
|
486
|
-
voiceUserPartialIdRef.current = null;
|
|
487
1185
|
setInternalThreadId(void 0);
|
|
488
1186
|
}, [stop, chatLogger]);
|
|
489
|
-
const loadThreadMessages =
|
|
1187
|
+
const loadThreadMessages = useCallback2(
|
|
490
1188
|
async (id) => {
|
|
491
1189
|
chatLogger.debug("loadThreadMessages start", { threadId: id });
|
|
492
1190
|
chatLogger.trace("loadThreadMessages", { threadId: id });
|
|
@@ -547,7 +1245,7 @@ function useChat(options = {}) {
|
|
|
547
1245
|
},
|
|
548
1246
|
[fetchWithAuth, chatLogger]
|
|
549
1247
|
);
|
|
550
|
-
|
|
1248
|
+
useEffect2(() => {
|
|
551
1249
|
if (!effectiveThreadId || isStreaming) {
|
|
552
1250
|
chatLogger.trace("auto-load skipped", { threadId: effectiveThreadId, isStreaming });
|
|
553
1251
|
return;
|
|
@@ -567,152 +1265,17 @@ function useChat(options = {}) {
|
|
|
567
1265
|
chatLogger.debug("loadThreadMessages trigger", { threadId: effectiveThreadId });
|
|
568
1266
|
void loadThreadMessages(effectiveThreadId);
|
|
569
1267
|
}, [effectiveThreadId, isStreaming, messages.length, loadThreadMessages, chatLogger]);
|
|
570
|
-
const
|
|
571
|
-
useEffect(() => {
|
|
572
|
-
if (!voice) return;
|
|
573
|
-
if (voiceThreadRef.current !== effectiveThreadId) {
|
|
574
|
-
voiceThreadRef.current = effectiveThreadId;
|
|
575
|
-
voicePrevLenRef.current = voice.transcript.length;
|
|
576
|
-
voiceStreamingIdRef.current = null;
|
|
577
|
-
voiceUserPartialIdRef.current = null;
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
const isVoiceActive = voice.state !== "idle" && voice.state !== "ended" && voice.state !== "error";
|
|
581
|
-
const curLen = voice.transcript.length;
|
|
582
|
-
if (curLen < voicePrevLenRef.current) {
|
|
583
|
-
voicePrevLenRef.current = 0;
|
|
584
|
-
voiceStreamingIdRef.current = null;
|
|
585
|
-
voiceUserPartialIdRef.current = null;
|
|
586
|
-
}
|
|
587
|
-
if (!isVoiceActive) {
|
|
588
|
-
if (curLen > voicePrevLenRef.current) {
|
|
589
|
-
const pendingLines = voice.transcript.slice(voicePrevLenRef.current);
|
|
590
|
-
voicePrevLenRef.current = curLen;
|
|
591
|
-
for (const line of pendingLines) {
|
|
592
|
-
const text = (line.text || "").trim();
|
|
593
|
-
if (!text) continue;
|
|
594
|
-
const role = line.speaker === "user" ? "user" : "assistant";
|
|
595
|
-
const voiceId = `voice-${line.id}`;
|
|
596
|
-
setMessages((prev) => {
|
|
597
|
-
if (prev.some((m) => m.id === voiceId)) return prev;
|
|
598
|
-
if (prev.some((m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text)) return prev;
|
|
599
|
-
const last = prev[prev.length - 1];
|
|
600
|
-
if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
|
|
601
|
-
const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
|
|
602
|
-
return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
|
|
603
|
-
}
|
|
604
|
-
return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
|
|
605
|
-
});
|
|
606
|
-
}
|
|
607
|
-
} else {
|
|
608
|
-
voicePrevLenRef.current = voice.transcript.length;
|
|
609
|
-
}
|
|
610
|
-
if (voiceStreamingIdRef.current) {
|
|
611
|
-
const doneId = voiceStreamingIdRef.current;
|
|
612
|
-
voiceStreamingIdRef.current = null;
|
|
613
|
-
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
614
|
-
}
|
|
615
|
-
if (voiceUserPartialIdRef.current) {
|
|
616
|
-
const doneId = voiceUserPartialIdRef.current;
|
|
617
|
-
voiceUserPartialIdRef.current = null;
|
|
618
|
-
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
619
|
-
}
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (curLen > voicePrevLenRef.current) {
|
|
623
|
-
const newLines = voice.transcript.slice(voicePrevLenRef.current);
|
|
624
|
-
voicePrevLenRef.current = curLen;
|
|
625
|
-
for (const line of newLines) {
|
|
626
|
-
const text = (line.text || "").trim();
|
|
627
|
-
if (!text) continue;
|
|
628
|
-
const role = line.speaker === "user" ? "user" : "assistant";
|
|
629
|
-
const voiceId = `voice-${line.id}`;
|
|
630
|
-
setMessages((prev) => {
|
|
631
|
-
if (prev.some((m) => m.id === voiceId)) return prev;
|
|
632
|
-
if (prev.some(
|
|
633
|
-
(m) => !m.id.startsWith("voice-") && m.role === role && m.content.trim() === text
|
|
634
|
-
))
|
|
635
|
-
return prev;
|
|
636
|
-
const last = prev[prev.length - 1];
|
|
637
|
-
if (last?.id.startsWith("voice-") && last.role === "assistant" && role === "assistant") {
|
|
638
|
-
const merged = text.startsWith(last.content) ? text : `${last.content} ${text}`.trim();
|
|
639
|
-
return [...prev.slice(0, -1), { ...last, content: merged, isStreaming: false }];
|
|
640
|
-
}
|
|
641
|
-
return [...prev, { id: voiceId, role, content: text, createdAt: /* @__PURE__ */ new Date() }];
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
const agentPartialText = voice.partial?.speaker === "agent" ? voice.partial.text.trim() : "";
|
|
646
|
-
const userPartialText = voice.partial?.speaker === "user" ? voice.partial.text.trim() : "";
|
|
647
|
-
if (agentPartialText) {
|
|
648
|
-
if (voiceUserPartialIdRef.current) {
|
|
649
|
-
const doneId = voiceUserPartialIdRef.current;
|
|
650
|
-
voiceUserPartialIdRef.current = null;
|
|
651
|
-
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
652
|
-
}
|
|
653
|
-
setMessages((prev) => {
|
|
654
|
-
const last = prev[prev.length - 1];
|
|
655
|
-
if (last?.id.startsWith("voice-") && last.role === "assistant") {
|
|
656
|
-
voiceStreamingIdRef.current = last.id;
|
|
657
|
-
return [...prev.slice(0, -1), { ...last, content: agentPartialText, isStreaming: true }];
|
|
658
|
-
}
|
|
659
|
-
const id = voiceStreamingIdRef.current || `voice-partial-${Date.now()}`;
|
|
660
|
-
voiceStreamingIdRef.current = id;
|
|
661
|
-
return [...prev, { id, role: "assistant", content: agentPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
662
|
-
});
|
|
663
|
-
} else if (userPartialText) {
|
|
664
|
-
if (voiceStreamingIdRef.current) {
|
|
665
|
-
const doneId = voiceStreamingIdRef.current;
|
|
666
|
-
voiceStreamingIdRef.current = null;
|
|
667
|
-
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
668
|
-
}
|
|
669
|
-
setMessages((prev) => {
|
|
670
|
-
const last = prev[prev.length - 1];
|
|
671
|
-
if (last?.id.startsWith("voice-") && last.role === "user" && last.isStreaming) {
|
|
672
|
-
voiceUserPartialIdRef.current = last.id;
|
|
673
|
-
return [...prev.slice(0, -1), { ...last, content: userPartialText, isStreaming: true }];
|
|
674
|
-
}
|
|
675
|
-
const id = voiceUserPartialIdRef.current || `voice-partial-user-${Date.now()}`;
|
|
676
|
-
voiceUserPartialIdRef.current = id;
|
|
677
|
-
return [...prev, { id, role: "user", content: userPartialText, isStreaming: true, createdAt: /* @__PURE__ */ new Date() }];
|
|
678
|
-
});
|
|
679
|
-
} else {
|
|
680
|
-
if (voiceStreamingIdRef.current && voice.state !== "speaking") {
|
|
681
|
-
const doneId = voiceStreamingIdRef.current;
|
|
682
|
-
voiceStreamingIdRef.current = null;
|
|
683
|
-
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
684
|
-
}
|
|
685
|
-
if (voiceUserPartialIdRef.current) {
|
|
686
|
-
const doneId = voiceUserPartialIdRef.current;
|
|
687
|
-
voiceUserPartialIdRef.current = null;
|
|
688
|
-
setMessages((prev) => prev.map((m) => m.id === doneId ? { ...m, isStreaming: false } : m));
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}, [voice?.transcript, voice?.partial, voice?.state, effectiveThreadId]);
|
|
692
|
-
const doSend = useCallback(
|
|
1268
|
+
const doSend = useCallback2(
|
|
693
1269
|
async (prompt, baseMessages, overrideOptions) => {
|
|
694
|
-
const targetAgentId = overrideOptions?.agentId || agentId;
|
|
695
|
-
if (!targetAgentId) {
|
|
696
|
-
const err = new Error("No Agent ID specified for useChat.");
|
|
697
|
-
chatLogger.warn("sendMessage no agentId", {});
|
|
698
|
-
chatLogger.error("sendMessage failed \u2014 no agent", { error: err.message });
|
|
699
|
-
setError(err);
|
|
700
|
-
options.onError?.(err);
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
1270
|
chatLogger.debug("sendMessage start", {
|
|
704
1271
|
promptPreview: prompt.slice(0, 100),
|
|
705
|
-
agentId: targetAgentId,
|
|
706
1272
|
threadId: effectiveThreadId ?? overrideOptions?.threadId,
|
|
707
1273
|
hasResume: !!overrideOptions?.resume,
|
|
708
|
-
hasContextOverride: !!overrideOptions?.contextOverride,
|
|
709
1274
|
messageCount: baseMessages.length
|
|
710
1275
|
});
|
|
711
1276
|
chatLogger.trace("sendMessage details", {
|
|
712
|
-
agentId: targetAgentId,
|
|
713
1277
|
promptPreview: prompt.slice(0, 200),
|
|
714
|
-
hasResume: !!overrideOptions?.resume
|
|
715
|
-
hasContextOverride: !!overrideOptions?.contextOverride
|
|
1278
|
+
hasResume: !!overrideOptions?.resume
|
|
716
1279
|
});
|
|
717
1280
|
const userMessage = {
|
|
718
1281
|
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
|
|
@@ -749,11 +1312,11 @@ function useChat(options = {}) {
|
|
|
749
1312
|
}));
|
|
750
1313
|
let resolvedThreadId = await overrideOptions?.threadId ?? effectiveThreadId;
|
|
751
1314
|
if (!resolvedThreadId) {
|
|
752
|
-
chatLogger.info("minting real thread for ephemeral chat", {
|
|
1315
|
+
chatLogger.info("minting real thread for ephemeral architect chat", {});
|
|
753
1316
|
const createRes = await fetchWithAuth("/threads", {
|
|
754
1317
|
method: "POST",
|
|
755
1318
|
headers: { "Content-Type": "application/json" },
|
|
756
|
-
body: JSON.stringify({ agentId:
|
|
1319
|
+
body: JSON.stringify({ agentId: ARCHITECT_AGENT_ID })
|
|
757
1320
|
});
|
|
758
1321
|
if (!createRes.ok) throw new Error(`Failed to create thread: ${createRes.statusText}`);
|
|
759
1322
|
const createBody = await createRes.json();
|
|
@@ -763,42 +1326,34 @@ function useChat(options = {}) {
|
|
|
763
1326
|
setInternalThreadId(resolvedThreadId);
|
|
764
1327
|
loadedThreadIdRef.current = resolvedThreadId;
|
|
765
1328
|
options.onThreadCreated?.(resolvedThreadId);
|
|
766
|
-
chatLogger.info("ephemeral chat minted", { threadId: resolvedThreadId });
|
|
1329
|
+
chatLogger.info("ephemeral architect chat minted", { threadId: resolvedThreadId });
|
|
767
1330
|
}
|
|
768
1331
|
chatLogger.trace("resolved threadId", { threadId: resolvedThreadId });
|
|
769
1332
|
const token = getAuthToken ? await getAuthToken() : null;
|
|
770
|
-
const resolvedHookContext = typeof options.context === "function" ? options.context() : options.context;
|
|
771
|
-
const mergedContext = { ...resolvedHookContext, ...overrideOptions?.context };
|
|
772
|
-
const hasContext = Object.keys(mergedContext).length > 0;
|
|
773
1333
|
chatLogger.debug("opening SSE stream", {
|
|
774
|
-
agentId: targetAgentId,
|
|
775
1334
|
hasToken: !!token,
|
|
776
|
-
hasThreadId: !!resolvedThreadId
|
|
777
|
-
hasContext
|
|
1335
|
+
hasThreadId: !!resolvedThreadId
|
|
778
1336
|
});
|
|
779
1337
|
const stream = await openSSEStream({
|
|
780
|
-
url: `${baseUrl}/
|
|
1338
|
+
url: `${baseUrl}/architect`,
|
|
781
1339
|
headers: {
|
|
782
1340
|
"Content-Type": "application/json",
|
|
783
1341
|
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
784
1342
|
},
|
|
785
1343
|
body: JSON.stringify({
|
|
786
|
-
agentId: targetAgentId,
|
|
787
1344
|
messages: payloadMessages,
|
|
788
1345
|
threadId: resolvedThreadId,
|
|
789
|
-
resume: overrideOptions?.resume
|
|
790
|
-
contextOverride: overrideOptions?.contextOverride,
|
|
791
|
-
...hasContext ? { context: mergedContext } : {}
|
|
1346
|
+
resume: overrideOptions?.resume
|
|
792
1347
|
}),
|
|
793
1348
|
signal: controller.signal
|
|
794
1349
|
});
|
|
795
1350
|
if (!stream.ok) {
|
|
796
1351
|
chatLogger.warn("SSE stream not ok", { status: stream.status, errorText: stream.errorText });
|
|
797
|
-
chatLogger.error("
|
|
798
|
-
throw new Error(`
|
|
1352
|
+
chatLogger.error("architect stream failed", { status: stream.status, error: stream.errorText });
|
|
1353
|
+
throw new Error(`Architect error (${stream.status}): ${stream.errorText ?? "Stream failed"}`);
|
|
799
1354
|
}
|
|
800
|
-
chatLogger.debug("SSE stream opened", {
|
|
801
|
-
chatLogger.info("
|
|
1355
|
+
chatLogger.debug("SSE stream opened", { status: stream.status });
|
|
1356
|
+
chatLogger.info("architect stream started", { threadId: resolvedThreadId });
|
|
802
1357
|
const reader = stream.reader;
|
|
803
1358
|
let buffer = "";
|
|
804
1359
|
let accumulatedText = "";
|
|
@@ -960,20 +1515,20 @@ function useChat(options = {}) {
|
|
|
960
1515
|
toolCalls: Array.from(toolCallsMap.values())
|
|
961
1516
|
};
|
|
962
1517
|
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? finalMessage : msg));
|
|
963
|
-
chatLogger.info("sendMessage succeeded", {
|
|
964
|
-
chatLogger.debug("sendMessage completed", {
|
|
1518
|
+
chatLogger.info("sendMessage succeeded", { textLength: accumulatedText.length, toolCallCount: toolCallsMap.size });
|
|
1519
|
+
chatLogger.debug("sendMessage completed", { textLength: accumulatedText.length, eventCount: toolCallsMap.size + 1 });
|
|
965
1520
|
options.onFinish?.(finalMessage);
|
|
966
1521
|
return true;
|
|
967
1522
|
} catch (err) {
|
|
968
1523
|
if (controller.signal.aborted) {
|
|
969
|
-
chatLogger.info("sendMessage aborted", {
|
|
970
|
-
chatLogger.debug("stream aborted", {
|
|
1524
|
+
chatLogger.info("sendMessage aborted", {});
|
|
1525
|
+
chatLogger.debug("stream aborted", {});
|
|
971
1526
|
setMessages((prev) => prev.map((msg) => msg.id === assistantMessageId ? { ...msg, isStreaming: false } : msg));
|
|
972
1527
|
return false;
|
|
973
1528
|
}
|
|
974
1529
|
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
975
|
-
chatLogger.warn("sendMessage failed", {
|
|
976
|
-
chatLogger.error("sendMessage error", {
|
|
1530
|
+
chatLogger.warn("sendMessage failed", { error: errorObj.message });
|
|
1531
|
+
chatLogger.error("sendMessage error", { error: errorObj.message });
|
|
977
1532
|
setError(errorObj);
|
|
978
1533
|
options.onError?.(errorObj);
|
|
979
1534
|
setMessages(
|
|
@@ -986,13 +1541,13 @@ function useChat(options = {}) {
|
|
|
986
1541
|
setIsStreaming(false);
|
|
987
1542
|
abortControllerRef.current = null;
|
|
988
1543
|
finalizeReasoning();
|
|
989
|
-
chatLogger.debug("sendMessage finally", {
|
|
990
|
-
chatLogger.trace("sendMessage end", {
|
|
1544
|
+
chatLogger.debug("sendMessage finally", { isStreaming: false });
|
|
1545
|
+
chatLogger.trace("sendMessage end", {});
|
|
991
1546
|
}
|
|
992
1547
|
},
|
|
993
|
-
[
|
|
1548
|
+
[baseUrl, getAuthToken, fetchWithAuth, options, chatLogger, effectiveThreadId]
|
|
994
1549
|
);
|
|
995
|
-
const sendMessage =
|
|
1550
|
+
const sendMessage = useCallback2(
|
|
996
1551
|
async (contentToSend, overrideOptions) => {
|
|
997
1552
|
const prompt = (contentToSend ?? input).trim();
|
|
998
1553
|
if (!prompt || isStreaming) {
|
|
@@ -1009,14 +1564,14 @@ function useChat(options = {}) {
|
|
|
1009
1564
|
},
|
|
1010
1565
|
[input, isStreaming, messages, doSend, chatLogger]
|
|
1011
1566
|
);
|
|
1012
|
-
const handleInputChange =
|
|
1567
|
+
const handleInputChange = useCallback2(
|
|
1013
1568
|
(e) => {
|
|
1014
1569
|
chatLogger.trace("handleInputChange", { length: e.target.value.length });
|
|
1015
1570
|
setInput(e.target.value);
|
|
1016
1571
|
},
|
|
1017
1572
|
[chatLogger]
|
|
1018
1573
|
);
|
|
1019
|
-
const handleSubmit =
|
|
1574
|
+
const handleSubmit = useCallback2(
|
|
1020
1575
|
(e) => {
|
|
1021
1576
|
if (e) e.preventDefault();
|
|
1022
1577
|
chatLogger.debug("handleSubmit", {});
|
|
@@ -1024,7 +1579,7 @@ function useChat(options = {}) {
|
|
|
1024
1579
|
},
|
|
1025
1580
|
[sendMessage, chatLogger]
|
|
1026
1581
|
);
|
|
1027
|
-
const reload =
|
|
1582
|
+
const reload = useCallback2(async () => {
|
|
1028
1583
|
if (messages.length === 0 || isStreaming) {
|
|
1029
1584
|
chatLogger.trace("reload skipped", {
|
|
1030
1585
|
messageCount: messages.length,
|
|
@@ -1051,7 +1606,7 @@ function useChat(options = {}) {
|
|
|
1051
1606
|
});
|
|
1052
1607
|
return doSend(lastUserMessage.content, truncated);
|
|
1053
1608
|
}, [isStreaming, messages, doSend, chatLogger]);
|
|
1054
|
-
const resumeInterrupt =
|
|
1609
|
+
const resumeInterrupt = useCallback2(
|
|
1055
1610
|
(resume, displayContent) => {
|
|
1056
1611
|
chatLogger.info("resumeInterrupt", {
|
|
1057
1612
|
kind: resume.decisions ? "hitl" : "clarification",
|
|
@@ -1061,11 +1616,11 @@ function useChat(options = {}) {
|
|
|
1061
1616
|
},
|
|
1062
1617
|
[sendMessage, chatLogger]
|
|
1063
1618
|
);
|
|
1064
|
-
const dismissPresentedFile =
|
|
1619
|
+
const dismissPresentedFile = useCallback2(() => {
|
|
1065
1620
|
chatLogger.debug("dismissPresentedFile", {});
|
|
1066
1621
|
setPresentedFile(null);
|
|
1067
1622
|
}, [chatLogger]);
|
|
1068
|
-
const openWorkspaceFile =
|
|
1623
|
+
const openWorkspaceFile = useCallback2(
|
|
1069
1624
|
(path) => {
|
|
1070
1625
|
chatLogger.debug("openWorkspaceFile", { path });
|
|
1071
1626
|
setPresentedFile({
|
|
@@ -1094,7 +1649,6 @@ function useChat(options = {}) {
|
|
|
1094
1649
|
presentedFile,
|
|
1095
1650
|
dismissPresentedFile,
|
|
1096
1651
|
openWorkspaceFile,
|
|
1097
|
-
sandboxCommands,
|
|
1098
1652
|
stop,
|
|
1099
1653
|
reload,
|
|
1100
1654
|
clear,
|
|
@@ -1107,16 +1661,16 @@ function useChat(options = {}) {
|
|
|
1107
1661
|
}
|
|
1108
1662
|
|
|
1109
1663
|
// src/hooks/useMemory.ts
|
|
1110
|
-
import { useCallback as
|
|
1664
|
+
import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
|
|
1111
1665
|
function useMemory(autoFetch = true) {
|
|
1112
1666
|
const { fetchWithAuth } = usePersonaContext();
|
|
1113
|
-
const [memory, setMemory] =
|
|
1667
|
+
const [memory, setMemory] = useState3({
|
|
1114
1668
|
userFiles: [],
|
|
1115
1669
|
agentMemories: []
|
|
1116
1670
|
});
|
|
1117
|
-
const [isLoading, setIsLoading] =
|
|
1118
|
-
const [error, setError] =
|
|
1119
|
-
const fetchMemory =
|
|
1671
|
+
const [isLoading, setIsLoading] = useState3(false);
|
|
1672
|
+
const [error, setError] = useState3(null);
|
|
1673
|
+
const fetchMemory = useCallback3(async () => {
|
|
1120
1674
|
setIsLoading(true);
|
|
1121
1675
|
setError(null);
|
|
1122
1676
|
try {
|
|
@@ -1135,7 +1689,7 @@ function useMemory(autoFetch = true) {
|
|
|
1135
1689
|
setIsLoading(false);
|
|
1136
1690
|
}
|
|
1137
1691
|
}, [fetchWithAuth]);
|
|
1138
|
-
const getFile =
|
|
1692
|
+
const getFile = useCallback3(
|
|
1139
1693
|
async (params) => {
|
|
1140
1694
|
const query = new URLSearchParams({ path: params.path });
|
|
1141
1695
|
if (params.scope) query.set("scope", params.scope);
|
|
@@ -1147,7 +1701,7 @@ function useMemory(autoFetch = true) {
|
|
|
1147
1701
|
},
|
|
1148
1702
|
[fetchWithAuth]
|
|
1149
1703
|
);
|
|
1150
|
-
const writeFile =
|
|
1704
|
+
const writeFile = useCallback3(
|
|
1151
1705
|
async (params) => {
|
|
1152
1706
|
const res = await fetchWithAuth("/memory/file", {
|
|
1153
1707
|
method: "PUT",
|
|
@@ -1162,7 +1716,7 @@ function useMemory(autoFetch = true) {
|
|
|
1162
1716
|
},
|
|
1163
1717
|
[fetchWithAuth, fetchMemory]
|
|
1164
1718
|
);
|
|
1165
|
-
const deleteFile =
|
|
1719
|
+
const deleteFile = useCallback3(
|
|
1166
1720
|
async (params) => {
|
|
1167
1721
|
const query = new URLSearchParams({ path: params.path });
|
|
1168
1722
|
if (params.scope) query.set("scope", params.scope);
|
|
@@ -1176,7 +1730,7 @@ function useMemory(autoFetch = true) {
|
|
|
1176
1730
|
},
|
|
1177
1731
|
[fetchWithAuth, fetchMemory]
|
|
1178
1732
|
);
|
|
1179
|
-
|
|
1733
|
+
useEffect3(() => {
|
|
1180
1734
|
if (autoFetch) {
|
|
1181
1735
|
void fetchMemory();
|
|
1182
1736
|
}
|
|
@@ -1193,13 +1747,13 @@ function useMemory(autoFetch = true) {
|
|
|
1193
1747
|
}
|
|
1194
1748
|
|
|
1195
1749
|
// src/hooks/useThreads.ts
|
|
1196
|
-
import { useCallback as
|
|
1750
|
+
import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
|
|
1197
1751
|
function useThreads(autoFetch = true) {
|
|
1198
1752
|
const { fetchWithAuth, defaultAgentId } = usePersonaContext();
|
|
1199
|
-
const [threads, setThreads] =
|
|
1200
|
-
const [isLoading, setIsLoading] =
|
|
1201
|
-
const [error, setError] =
|
|
1202
|
-
const fetchThreads =
|
|
1753
|
+
const [threads, setThreads] = useState4([]);
|
|
1754
|
+
const [isLoading, setIsLoading] = useState4(false);
|
|
1755
|
+
const [error, setError] = useState4(null);
|
|
1756
|
+
const fetchThreads = useCallback4(async () => {
|
|
1203
1757
|
setIsLoading(true);
|
|
1204
1758
|
setError(null);
|
|
1205
1759
|
try {
|
|
@@ -1217,7 +1771,7 @@ function useThreads(autoFetch = true) {
|
|
|
1217
1771
|
setIsLoading(false);
|
|
1218
1772
|
}
|
|
1219
1773
|
}, [fetchWithAuth]);
|
|
1220
|
-
const createThread =
|
|
1774
|
+
const createThread = useCallback4(
|
|
1221
1775
|
async (agentId) => {
|
|
1222
1776
|
const targetAgentId = agentId || defaultAgentId;
|
|
1223
1777
|
const res = await fetchWithAuth("/threads", {
|
|
@@ -1234,7 +1788,7 @@ function useThreads(autoFetch = true) {
|
|
|
1234
1788
|
},
|
|
1235
1789
|
[fetchWithAuth, defaultAgentId, fetchThreads]
|
|
1236
1790
|
);
|
|
1237
|
-
const deleteThread =
|
|
1791
|
+
const deleteThread = useCallback4(
|
|
1238
1792
|
async (threadId) => {
|
|
1239
1793
|
const res = await fetchWithAuth(`/threads/${threadId}`, {
|
|
1240
1794
|
method: "DELETE"
|
|
@@ -1245,7 +1799,7 @@ function useThreads(autoFetch = true) {
|
|
|
1245
1799
|
},
|
|
1246
1800
|
[fetchWithAuth]
|
|
1247
1801
|
);
|
|
1248
|
-
const bulkDeleteThreads =
|
|
1802
|
+
const bulkDeleteThreads = useCallback4(
|
|
1249
1803
|
async (threadIds) => {
|
|
1250
1804
|
const res = await fetchWithAuth("/threads/bulk-delete", {
|
|
1251
1805
|
method: "POST",
|
|
@@ -1262,13 +1816,13 @@ function useThreads(autoFetch = true) {
|
|
|
1262
1816
|
},
|
|
1263
1817
|
[fetchWithAuth]
|
|
1264
1818
|
);
|
|
1265
|
-
const deleteAllThreads =
|
|
1819
|
+
const deleteAllThreads = useCallback4(async () => {
|
|
1266
1820
|
const ids = threads.map((t) => t._id);
|
|
1267
1821
|
for (let i = 0; i < ids.length; i += 100) {
|
|
1268
1822
|
await bulkDeleteThreads(ids.slice(i, i + 100));
|
|
1269
1823
|
}
|
|
1270
1824
|
}, [threads, bulkDeleteThreads]);
|
|
1271
|
-
const updateThread =
|
|
1825
|
+
const updateThread = useCallback4(
|
|
1272
1826
|
async (threadId, input) => {
|
|
1273
1827
|
const res = await fetchWithAuth(`/threads/${threadId}`, {
|
|
1274
1828
|
method: "PATCH",
|
|
@@ -1286,11 +1840,11 @@ function useThreads(autoFetch = true) {
|
|
|
1286
1840
|
},
|
|
1287
1841
|
[fetchWithAuth]
|
|
1288
1842
|
);
|
|
1289
|
-
const renameThread =
|
|
1843
|
+
const renameThread = useCallback4(
|
|
1290
1844
|
(threadId, title) => updateThread(threadId, { title }),
|
|
1291
1845
|
[updateThread]
|
|
1292
1846
|
);
|
|
1293
|
-
const resetThread =
|
|
1847
|
+
const resetThread = useCallback4(
|
|
1294
1848
|
async (threadId) => {
|
|
1295
1849
|
const res = await fetchWithAuth(`/threads/${threadId}/reset`, {
|
|
1296
1850
|
method: "POST"
|
|
@@ -1305,7 +1859,7 @@ function useThreads(autoFetch = true) {
|
|
|
1305
1859
|
},
|
|
1306
1860
|
[fetchWithAuth]
|
|
1307
1861
|
);
|
|
1308
|
-
const getThread =
|
|
1862
|
+
const getThread = useCallback4(
|
|
1309
1863
|
async (threadId) => {
|
|
1310
1864
|
const res = await fetchWithAuth(`/threads/${threadId}`);
|
|
1311
1865
|
if (!res.ok) throw new Error(`Failed to fetch thread: ${res.statusText}`);
|
|
@@ -1314,7 +1868,7 @@ function useThreads(autoFetch = true) {
|
|
|
1314
1868
|
},
|
|
1315
1869
|
[fetchWithAuth]
|
|
1316
1870
|
);
|
|
1317
|
-
|
|
1871
|
+
useEffect4(() => {
|
|
1318
1872
|
if (autoFetch) {
|
|
1319
1873
|
void fetchThreads();
|
|
1320
1874
|
}
|
|
@@ -1336,7 +1890,7 @@ function useThreads(autoFetch = true) {
|
|
|
1336
1890
|
}
|
|
1337
1891
|
|
|
1338
1892
|
// src/hooks/useVoice.ts
|
|
1339
|
-
import { useCallback as
|
|
1893
|
+
import { useCallback as useCallback5, useEffect as useEffect5, useMemo as useMemo4, useRef as useRef3, useState as useState5 } from "react";
|
|
1340
1894
|
|
|
1341
1895
|
// src/hooks/voiceWorklets.ts
|
|
1342
1896
|
var RECORDER_WORKLET_SOURCE = `
|
|
@@ -1439,42 +1993,42 @@ function mergeTranscriptText(prev, next) {
|
|
|
1439
1993
|
}
|
|
1440
1994
|
function useVoice(options = {}) {
|
|
1441
1995
|
const { defaultAgentId, fetchWithAuth, logger } = usePersonaContext();
|
|
1442
|
-
const voiceLogger =
|
|
1996
|
+
const voiceLogger = useMemo4(() => logger.child("voice"), [logger]);
|
|
1443
1997
|
const agentId = options.agentId || defaultAgentId;
|
|
1444
1998
|
const threadId = options.threadId;
|
|
1445
1999
|
const contextOverride = options.contextOverride;
|
|
1446
2000
|
const context = options.context;
|
|
1447
|
-
const [state, setState] =
|
|
1448
|
-
const [isMuted, setIsMuted] =
|
|
1449
|
-
const [transcript, setTranscript] =
|
|
2001
|
+
const [state, setState] = useState5("idle");
|
|
2002
|
+
const [isMuted, setIsMuted] = useState5(false);
|
|
2003
|
+
const [transcript, setTranscript] = useState5(
|
|
1450
2004
|
[]
|
|
1451
2005
|
);
|
|
1452
|
-
const [partial, setPartial] =
|
|
2006
|
+
const [partial, setPartial] = useState5(
|
|
1453
2007
|
null
|
|
1454
2008
|
);
|
|
1455
|
-
const [toolCalls, setToolCalls] =
|
|
1456
|
-
const [error, setError] =
|
|
1457
|
-
const [endReason, setEndReason] =
|
|
2009
|
+
const [toolCalls, setToolCalls] = useState5([]);
|
|
2010
|
+
const [error, setError] = useState5(null);
|
|
2011
|
+
const [endReason, setEndReason] = useState5(
|
|
1458
2012
|
null
|
|
1459
2013
|
);
|
|
1460
|
-
const wsRef =
|
|
1461
|
-
const streamRef =
|
|
1462
|
-
const inputCtxRef =
|
|
1463
|
-
const outputCtxRef =
|
|
1464
|
-
const recorderNodeRef =
|
|
1465
|
-
const playerNodeRef =
|
|
1466
|
-
const acceptedTurnSeqRef =
|
|
1467
|
-
const mountedRef =
|
|
1468
|
-
const lastFinalTurnRef =
|
|
2014
|
+
const wsRef = useRef3(null);
|
|
2015
|
+
const streamRef = useRef3(null);
|
|
2016
|
+
const inputCtxRef = useRef3(null);
|
|
2017
|
+
const outputCtxRef = useRef3(null);
|
|
2018
|
+
const recorderNodeRef = useRef3(null);
|
|
2019
|
+
const playerNodeRef = useRef3(null);
|
|
2020
|
+
const acceptedTurnSeqRef = useRef3(0);
|
|
2021
|
+
const mountedRef = useRef3(true);
|
|
2022
|
+
const lastFinalTurnRef = useRef3(
|
|
1469
2023
|
null
|
|
1470
2024
|
);
|
|
1471
|
-
|
|
2025
|
+
useEffect5(() => {
|
|
1472
2026
|
mountedRef.current = true;
|
|
1473
2027
|
return () => {
|
|
1474
2028
|
mountedRef.current = false;
|
|
1475
2029
|
};
|
|
1476
2030
|
}, []);
|
|
1477
|
-
const teardownAudio =
|
|
2031
|
+
const teardownAudio = useCallback5(() => {
|
|
1478
2032
|
try {
|
|
1479
2033
|
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
1480
2034
|
} catch {
|
|
@@ -1501,7 +2055,7 @@ function useVoice(options = {}) {
|
|
|
1501
2055
|
}
|
|
1502
2056
|
outputCtxRef.current = null;
|
|
1503
2057
|
}, []);
|
|
1504
|
-
const stop =
|
|
2058
|
+
const stop = useCallback5(() => {
|
|
1505
2059
|
voiceLogger.debug("stop() called");
|
|
1506
2060
|
try {
|
|
1507
2061
|
wsRef.current?.close(1e3, "client_stop");
|
|
@@ -1511,8 +2065,8 @@ function useVoice(options = {}) {
|
|
|
1511
2065
|
teardownAudio();
|
|
1512
2066
|
if (mountedRef.current) setState("idle");
|
|
1513
2067
|
}, [teardownAudio, voiceLogger]);
|
|
1514
|
-
|
|
1515
|
-
const startCapture =
|
|
2068
|
+
useEffect5(() => stop, [stop]);
|
|
2069
|
+
const startCapture = useCallback5(async (inputSampleRate) => {
|
|
1516
2070
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
1517
2071
|
audio: {
|
|
1518
2072
|
echoCancellation: true,
|
|
@@ -1538,7 +2092,7 @@ function useVoice(options = {}) {
|
|
|
1538
2092
|
recorderNodeRef.current = recorderNode;
|
|
1539
2093
|
source.connect(recorderNode);
|
|
1540
2094
|
}, []);
|
|
1541
|
-
const setupPlayback =
|
|
2095
|
+
const setupPlayback = useCallback5(async (outputSampleRate) => {
|
|
1542
2096
|
const outputCtx = new AudioContext({ sampleRate: outputSampleRate });
|
|
1543
2097
|
outputCtxRef.current = outputCtx;
|
|
1544
2098
|
await outputCtx.audioWorklet.addModule(playerWorkletUrl());
|
|
@@ -1546,7 +2100,7 @@ function useVoice(options = {}) {
|
|
|
1546
2100
|
playerNode.connect(outputCtx.destination);
|
|
1547
2101
|
playerNodeRef.current = playerNode;
|
|
1548
2102
|
}, []);
|
|
1549
|
-
const upsertToolCall =
|
|
2103
|
+
const upsertToolCall = useCallback5(
|
|
1550
2104
|
(id, patch) => {
|
|
1551
2105
|
setToolCalls((prev) => {
|
|
1552
2106
|
const idx = prev.findIndex((t) => t.id === id);
|
|
@@ -1558,7 +2112,7 @@ function useVoice(options = {}) {
|
|
|
1558
2112
|
},
|
|
1559
2113
|
[]
|
|
1560
2114
|
);
|
|
1561
|
-
const handleTranscript =
|
|
2115
|
+
const handleTranscript = useCallback5((value) => {
|
|
1562
2116
|
const { speaker, text, isFinal, turnSeq } = value;
|
|
1563
2117
|
if (!isFinal) {
|
|
1564
2118
|
setPartial({ id: "partial", speaker, text });
|
|
@@ -1581,7 +2135,7 @@ function useVoice(options = {}) {
|
|
|
1581
2135
|
});
|
|
1582
2136
|
lastFinalTurnRef.current = { speaker, turnSeq };
|
|
1583
2137
|
}, []);
|
|
1584
|
-
const handleCustomEvent =
|
|
2138
|
+
const handleCustomEvent = useCallback5(
|
|
1585
2139
|
(name, value) => {
|
|
1586
2140
|
switch (name) {
|
|
1587
2141
|
case "voice_session_ready":
|
|
@@ -1639,7 +2193,7 @@ function useVoice(options = {}) {
|
|
|
1639
2193
|
},
|
|
1640
2194
|
[handleTranscript, setupPlayback, startCapture, teardownAudio, voiceLogger]
|
|
1641
2195
|
);
|
|
1642
|
-
const handleMessage =
|
|
2196
|
+
const handleMessage = useCallback5(
|
|
1643
2197
|
(event) => {
|
|
1644
2198
|
if (typeof event.data !== "string") {
|
|
1645
2199
|
const buf = event.data;
|
|
@@ -1697,7 +2251,7 @@ function useVoice(options = {}) {
|
|
|
1697
2251
|
},
|
|
1698
2252
|
[handleCustomEvent, upsertToolCall, voiceLogger]
|
|
1699
2253
|
);
|
|
1700
|
-
const start =
|
|
2254
|
+
const start = useCallback5(async () => {
|
|
1701
2255
|
if (!agentId) {
|
|
1702
2256
|
const err = new Error(
|
|
1703
2257
|
"useVoice: no agentId provided and no defaultAgentId set on PersonaProvider"
|
|
@@ -1764,17 +2318,17 @@ function useVoice(options = {}) {
|
|
|
1764
2318
|
teardownAudio();
|
|
1765
2319
|
}
|
|
1766
2320
|
}, [agentId, threadId, contextOverride, context, fetchWithAuth, handleMessage, teardownAudio, voiceLogger]);
|
|
1767
|
-
const mute =
|
|
2321
|
+
const mute = useCallback5((muted) => {
|
|
1768
2322
|
setIsMuted(muted);
|
|
1769
2323
|
streamRef.current?.getAudioTracks().forEach((track) => {
|
|
1770
2324
|
track.enabled = !muted;
|
|
1771
2325
|
});
|
|
1772
2326
|
}, []);
|
|
1773
|
-
const sendText =
|
|
2327
|
+
const sendText = useCallback5((text) => {
|
|
1774
2328
|
if (!text?.trim() || wsRef.current?.readyState !== WebSocket.OPEN) return;
|
|
1775
2329
|
wsRef.current.send(JSON.stringify({ type: "voice.text", text }));
|
|
1776
2330
|
}, []);
|
|
1777
|
-
const updateContext =
|
|
2331
|
+
const updateContext = useCallback5((newContext) => {
|
|
1778
2332
|
if (wsRef.current?.readyState !== WebSocket.OPEN) return;
|
|
1779
2333
|
wsRef.current.send(JSON.stringify({ type: "voice.context", context: newContext }));
|
|
1780
2334
|
}, []);
|
|
@@ -1795,14 +2349,14 @@ function useVoice(options = {}) {
|
|
|
1795
2349
|
}
|
|
1796
2350
|
|
|
1797
2351
|
// src/hooks/useFiles.ts
|
|
1798
|
-
import { useCallback as
|
|
2352
|
+
import { useCallback as useCallback6, useEffect as useEffect6, useState as useState6 } from "react";
|
|
1799
2353
|
function useFiles(autoFetch = true) {
|
|
1800
2354
|
const { fetchWithAuth } = usePersonaContext();
|
|
1801
|
-
const [files, setFiles] =
|
|
1802
|
-
const [isLoading, setIsLoading] =
|
|
1803
|
-
const [isUploading, setIsUploading] =
|
|
1804
|
-
const [error, setError] =
|
|
1805
|
-
const fetchFiles =
|
|
2355
|
+
const [files, setFiles] = useState6([]);
|
|
2356
|
+
const [isLoading, setIsLoading] = useState6(false);
|
|
2357
|
+
const [isUploading, setIsUploading] = useState6(false);
|
|
2358
|
+
const [error, setError] = useState6(null);
|
|
2359
|
+
const fetchFiles = useCallback6(async () => {
|
|
1806
2360
|
setIsLoading(true);
|
|
1807
2361
|
setError(null);
|
|
1808
2362
|
try {
|
|
@@ -1820,7 +2374,7 @@ function useFiles(autoFetch = true) {
|
|
|
1820
2374
|
setIsLoading(false);
|
|
1821
2375
|
}
|
|
1822
2376
|
}, [fetchWithAuth]);
|
|
1823
|
-
const uploadFile =
|
|
2377
|
+
const uploadFile = useCallback6(
|
|
1824
2378
|
async (fileOrFormData) => {
|
|
1825
2379
|
setIsUploading(true);
|
|
1826
2380
|
setError(null);
|
|
@@ -1853,7 +2407,7 @@ function useFiles(autoFetch = true) {
|
|
|
1853
2407
|
},
|
|
1854
2408
|
[fetchWithAuth, fetchFiles]
|
|
1855
2409
|
);
|
|
1856
|
-
const deleteFile =
|
|
2410
|
+
const deleteFile = useCallback6(
|
|
1857
2411
|
async (fileId) => {
|
|
1858
2412
|
const res = await fetchWithAuth(`/files/${fileId}`, { method: "DELETE" });
|
|
1859
2413
|
if (!res.ok) throw new Error(`Failed to delete file: ${res.statusText}`);
|
|
@@ -1861,7 +2415,7 @@ function useFiles(autoFetch = true) {
|
|
|
1861
2415
|
},
|
|
1862
2416
|
[fetchWithAuth]
|
|
1863
2417
|
);
|
|
1864
|
-
const bulkDeleteFiles =
|
|
2418
|
+
const bulkDeleteFiles = useCallback6(
|
|
1865
2419
|
async (fileIds) => {
|
|
1866
2420
|
const res = await fetchWithAuth("/files/bulk-delete", {
|
|
1867
2421
|
method: "POST",
|
|
@@ -1876,10 +2430,10 @@ function useFiles(autoFetch = true) {
|
|
|
1876
2430
|
},
|
|
1877
2431
|
[fetchWithAuth]
|
|
1878
2432
|
);
|
|
1879
|
-
const getDownloadUrl =
|
|
2433
|
+
const getDownloadUrl = useCallback6((fileId) => {
|
|
1880
2434
|
return `/files/${fileId}`;
|
|
1881
2435
|
}, []);
|
|
1882
|
-
|
|
2436
|
+
useEffect6(() => {
|
|
1883
2437
|
if (autoFetch) {
|
|
1884
2438
|
void fetchFiles();
|
|
1885
2439
|
}
|
|
@@ -1898,13 +2452,13 @@ function useFiles(autoFetch = true) {
|
|
|
1898
2452
|
}
|
|
1899
2453
|
|
|
1900
2454
|
// src/hooks/useAgents.ts
|
|
1901
|
-
import { useCallback as
|
|
2455
|
+
import { useCallback as useCallback7, useEffect as useEffect7, useState as useState7 } from "react";
|
|
1902
2456
|
function useAgents(autoFetch = true) {
|
|
1903
2457
|
const { fetchWithAuth } = usePersonaContext();
|
|
1904
|
-
const [agents, setAgents] =
|
|
1905
|
-
const [isLoading, setIsLoading] =
|
|
1906
|
-
const [error, setError] =
|
|
1907
|
-
const fetchAgents =
|
|
2458
|
+
const [agents, setAgents] = useState7([]);
|
|
2459
|
+
const [isLoading, setIsLoading] = useState7(false);
|
|
2460
|
+
const [error, setError] = useState7(null);
|
|
2461
|
+
const fetchAgents = useCallback7(async () => {
|
|
1908
2462
|
setIsLoading(true);
|
|
1909
2463
|
setError(null);
|
|
1910
2464
|
try {
|
|
@@ -1922,7 +2476,7 @@ function useAgents(autoFetch = true) {
|
|
|
1922
2476
|
setIsLoading(false);
|
|
1923
2477
|
}
|
|
1924
2478
|
}, [fetchWithAuth]);
|
|
1925
|
-
|
|
2479
|
+
useEffect7(() => {
|
|
1926
2480
|
if (autoFetch) {
|
|
1927
2481
|
void fetchAgents();
|
|
1928
2482
|
}
|
|
@@ -1936,13 +2490,13 @@ function useAgents(autoFetch = true) {
|
|
|
1936
2490
|
}
|
|
1937
2491
|
|
|
1938
2492
|
// src/hooks/useConnection.ts
|
|
1939
|
-
import { useCallback as
|
|
2493
|
+
import { useCallback as useCallback8, useEffect as useEffect8, useState as useState8 } from "react";
|
|
1940
2494
|
function useConnection(autoCheck = true) {
|
|
1941
2495
|
const { fetchWithAuth } = usePersonaContext();
|
|
1942
|
-
const [health, setHealth] =
|
|
1943
|
-
const [isConnected, setIsConnected] =
|
|
1944
|
-
const [isLoading, setIsLoading] =
|
|
1945
|
-
const checkHealth =
|
|
2496
|
+
const [health, setHealth] = useState8(null);
|
|
2497
|
+
const [isConnected, setIsConnected] = useState8(false);
|
|
2498
|
+
const [isLoading, setIsLoading] = useState8(false);
|
|
2499
|
+
const checkHealth = useCallback8(async () => {
|
|
1946
2500
|
setIsLoading(true);
|
|
1947
2501
|
try {
|
|
1948
2502
|
const res = await fetchWithAuth("/health");
|
|
@@ -1961,7 +2515,7 @@ function useConnection(autoCheck = true) {
|
|
|
1961
2515
|
setIsLoading(false);
|
|
1962
2516
|
}
|
|
1963
2517
|
}, [fetchWithAuth]);
|
|
1964
|
-
|
|
2518
|
+
useEffect8(() => {
|
|
1965
2519
|
if (autoCheck) {
|
|
1966
2520
|
void checkHealth();
|
|
1967
2521
|
}
|
|
@@ -1975,15 +2529,15 @@ function useConnection(autoCheck = true) {
|
|
|
1975
2529
|
}
|
|
1976
2530
|
|
|
1977
2531
|
// src/hooks/useMcpConnections.ts
|
|
1978
|
-
import { useCallback as
|
|
2532
|
+
import { useCallback as useCallback9, useEffect as useEffect9, useState as useState9 } from "react";
|
|
1979
2533
|
function useMcpConnections(options = {}) {
|
|
1980
2534
|
const { defaultAgentId, fetchWithAuth } = usePersonaContext();
|
|
1981
2535
|
const agentId = options.agentId ?? defaultAgentId;
|
|
1982
2536
|
const autoFetch = options.autoFetch ?? true;
|
|
1983
|
-
const [connections, setConnections] =
|
|
1984
|
-
const [isLoading, setIsLoading] =
|
|
1985
|
-
const [error, setError] =
|
|
1986
|
-
const fetchConnections =
|
|
2537
|
+
const [connections, setConnections] = useState9([]);
|
|
2538
|
+
const [isLoading, setIsLoading] = useState9(false);
|
|
2539
|
+
const [error, setError] = useState9(null);
|
|
2540
|
+
const fetchConnections = useCallback9(async () => {
|
|
1987
2541
|
if (!agentId) return [];
|
|
1988
2542
|
setIsLoading(true);
|
|
1989
2543
|
setError(null);
|
|
@@ -2007,7 +2561,7 @@ function useMcpConnections(options = {}) {
|
|
|
2007
2561
|
setIsLoading(false);
|
|
2008
2562
|
}
|
|
2009
2563
|
}, [agentId, fetchWithAuth, options.returnTo]);
|
|
2010
|
-
|
|
2564
|
+
useEffect9(() => {
|
|
2011
2565
|
if (autoFetch) void fetchConnections();
|
|
2012
2566
|
}, [autoFetch, fetchConnections]);
|
|
2013
2567
|
return {
|
|
@@ -2021,11 +2575,11 @@ function useMcpConnections(options = {}) {
|
|
|
2021
2575
|
}
|
|
2022
2576
|
|
|
2023
2577
|
// src/hooks/useMcp.ts
|
|
2024
|
-
import { useCallback as
|
|
2578
|
+
import { useCallback as useCallback10 } from "react";
|
|
2025
2579
|
function useMcp(options = {}) {
|
|
2026
2580
|
const { fetchWithAuth } = usePersonaContext();
|
|
2027
2581
|
const defaultMcpId = options.mcpId;
|
|
2028
|
-
const readResource =
|
|
2582
|
+
const readResource = useCallback10(
|
|
2029
2583
|
async (uri, mcpId) => {
|
|
2030
2584
|
const id = mcpId ?? defaultMcpId;
|
|
2031
2585
|
if (!id) throw new Error("MCP server ID is required to read resource");
|
|
@@ -2039,7 +2593,7 @@ function useMcp(options = {}) {
|
|
|
2039
2593
|
},
|
|
2040
2594
|
[fetchWithAuth, defaultMcpId]
|
|
2041
2595
|
);
|
|
2042
|
-
const callTool =
|
|
2596
|
+
const callTool = useCallback10(
|
|
2043
2597
|
async (name, args, mcpId) => {
|
|
2044
2598
|
const id = mcpId ?? defaultMcpId;
|
|
2045
2599
|
if (!id) throw new Error("MCP server ID is required to call tool");
|
|
@@ -2062,21 +2616,21 @@ function useMcp(options = {}) {
|
|
|
2062
2616
|
}
|
|
2063
2617
|
|
|
2064
2618
|
// src/hooks/useWorkflows.ts
|
|
2065
|
-
import { useCallback as
|
|
2619
|
+
import { useCallback as useCallback11, useEffect as useEffect10, useState as useState10 } from "react";
|
|
2066
2620
|
function useWorkflows(options) {
|
|
2067
2621
|
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2068
2622
|
const { autoFetch = true, search, status, page, limit } = opts;
|
|
2069
2623
|
const { fetchWithAuth } = usePersonaContext();
|
|
2070
|
-
const [workflows, setWorkflows] =
|
|
2071
|
-
const [pagination, setPagination] =
|
|
2624
|
+
const [workflows, setWorkflows] = useState10([]);
|
|
2625
|
+
const [pagination, setPagination] = useState10({
|
|
2072
2626
|
page: page ?? 1,
|
|
2073
2627
|
limit: limit ?? 20,
|
|
2074
2628
|
total: 0,
|
|
2075
2629
|
totalPages: 0
|
|
2076
2630
|
});
|
|
2077
|
-
const [isLoading, setIsLoading] =
|
|
2078
|
-
const [error, setError] =
|
|
2079
|
-
const fetchWorkflows =
|
|
2631
|
+
const [isLoading, setIsLoading] = useState10(false);
|
|
2632
|
+
const [error, setError] = useState10(null);
|
|
2633
|
+
const fetchWorkflows = useCallback11(async () => {
|
|
2080
2634
|
setIsLoading(true);
|
|
2081
2635
|
setError(null);
|
|
2082
2636
|
try {
|
|
@@ -2117,7 +2671,7 @@ function useWorkflows(options) {
|
|
|
2117
2671
|
setIsLoading(false);
|
|
2118
2672
|
}
|
|
2119
2673
|
}, [fetchWithAuth, search, status, page, limit]);
|
|
2120
|
-
const createWorkflow =
|
|
2674
|
+
const createWorkflow = useCallback11(
|
|
2121
2675
|
async (input) => {
|
|
2122
2676
|
setError(null);
|
|
2123
2677
|
try {
|
|
@@ -2141,7 +2695,7 @@ function useWorkflows(options) {
|
|
|
2141
2695
|
},
|
|
2142
2696
|
[fetchWithAuth]
|
|
2143
2697
|
);
|
|
2144
|
-
|
|
2698
|
+
useEffect10(() => {
|
|
2145
2699
|
if (autoFetch) {
|
|
2146
2700
|
void fetchWorkflows();
|
|
2147
2701
|
}
|
|
@@ -2157,17 +2711,17 @@ function useWorkflows(options) {
|
|
|
2157
2711
|
}
|
|
2158
2712
|
|
|
2159
2713
|
// src/hooks/useWorkflow.ts
|
|
2160
|
-
import { useCallback as
|
|
2714
|
+
import { useCallback as useCallback12, useEffect as useEffect11, useState as useState11 } from "react";
|
|
2161
2715
|
function useWorkflow(workflowId, options) {
|
|
2162
2716
|
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2163
2717
|
const { autoFetch = true } = opts;
|
|
2164
2718
|
const { fetchWithAuth } = usePersonaContext();
|
|
2165
|
-
const [workflow, setWorkflow] =
|
|
2166
|
-
const [versions, setVersions] =
|
|
2167
|
-
const [mermaid, setMermaid] =
|
|
2168
|
-
const [isLoading, setIsLoading] =
|
|
2169
|
-
const [error, setError] =
|
|
2170
|
-
const fetchWorkflow =
|
|
2719
|
+
const [workflow, setWorkflow] = useState11(null);
|
|
2720
|
+
const [versions, setVersions] = useState11([]);
|
|
2721
|
+
const [mermaid, setMermaid] = useState11(null);
|
|
2722
|
+
const [isLoading, setIsLoading] = useState11(false);
|
|
2723
|
+
const [error, setError] = useState11(null);
|
|
2724
|
+
const fetchWorkflow = useCallback12(async () => {
|
|
2171
2725
|
if (!workflowId) return null;
|
|
2172
2726
|
setIsLoading(true);
|
|
2173
2727
|
setError(null);
|
|
@@ -2187,7 +2741,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2187
2741
|
setIsLoading(false);
|
|
2188
2742
|
}
|
|
2189
2743
|
}, [fetchWithAuth, workflowId]);
|
|
2190
|
-
const fetchVersions =
|
|
2744
|
+
const fetchVersions = useCallback12(async () => {
|
|
2191
2745
|
if (!workflowId) return [];
|
|
2192
2746
|
try {
|
|
2193
2747
|
const res = await fetchWithAuth(`/workflows/${workflowId}/versions`);
|
|
@@ -2200,7 +2754,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2200
2754
|
return [];
|
|
2201
2755
|
}
|
|
2202
2756
|
}, [fetchWithAuth, workflowId]);
|
|
2203
|
-
const fetchMermaid =
|
|
2757
|
+
const fetchMermaid = useCallback12(async () => {
|
|
2204
2758
|
if (!workflowId) return "";
|
|
2205
2759
|
try {
|
|
2206
2760
|
const res = await fetchWithAuth(`/workflows/${workflowId}/mermaid`);
|
|
@@ -2213,7 +2767,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2213
2767
|
return "";
|
|
2214
2768
|
}
|
|
2215
2769
|
}, [fetchWithAuth, workflowId]);
|
|
2216
|
-
const saveDraft =
|
|
2770
|
+
const saveDraft = useCallback12(
|
|
2217
2771
|
async (draft) => {
|
|
2218
2772
|
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2219
2773
|
setError(null);
|
|
@@ -2232,7 +2786,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2232
2786
|
},
|
|
2233
2787
|
[fetchWithAuth, workflowId]
|
|
2234
2788
|
);
|
|
2235
|
-
const publish =
|
|
2789
|
+
const publish = useCallback12(
|
|
2236
2790
|
async (summary) => {
|
|
2237
2791
|
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2238
2792
|
setError(null);
|
|
@@ -2252,7 +2806,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2252
2806
|
},
|
|
2253
2807
|
[fetchWithAuth, workflowId, fetchWorkflow, fetchVersions]
|
|
2254
2808
|
);
|
|
2255
|
-
const deleteWorkflow =
|
|
2809
|
+
const deleteWorkflow = useCallback12(async () => {
|
|
2256
2810
|
if (!workflowId) throw new Error("Workflow ID is required");
|
|
2257
2811
|
setError(null);
|
|
2258
2812
|
const res = await fetchWithAuth(`/workflows/${workflowId}`, {
|
|
@@ -2263,7 +2817,7 @@ function useWorkflow(workflowId, options) {
|
|
|
2263
2817
|
}
|
|
2264
2818
|
setWorkflow(null);
|
|
2265
2819
|
}, [fetchWithAuth, workflowId]);
|
|
2266
|
-
|
|
2820
|
+
useEffect11(() => {
|
|
2267
2821
|
if (autoFetch && workflowId) {
|
|
2268
2822
|
void fetchWorkflow();
|
|
2269
2823
|
}
|
|
@@ -2284,25 +2838,25 @@ function useWorkflow(workflowId, options) {
|
|
|
2284
2838
|
}
|
|
2285
2839
|
|
|
2286
2840
|
// src/hooks/useWorkflowStream.ts
|
|
2287
|
-
import { useCallback as
|
|
2841
|
+
import { useCallback as useCallback13, useEffect as useEffect12, useRef as useRef4, useState as useState12 } from "react";
|
|
2288
2842
|
function useWorkflowStream(workflowId, options) {
|
|
2289
2843
|
const { baseUrl, getAuthToken, fetchWithAuth, logger } = usePersonaContext();
|
|
2290
|
-
const [status, setStatus] =
|
|
2291
|
-
const [runId, setRunId] =
|
|
2292
|
-
const [activeNodeId, setActiveNodeId] =
|
|
2293
|
-
const [nodeRuns, setNodeRuns] =
|
|
2844
|
+
const [status, setStatus] = useState12("idle");
|
|
2845
|
+
const [runId, setRunId] = useState12(null);
|
|
2846
|
+
const [activeNodeId, setActiveNodeId] = useState12(null);
|
|
2847
|
+
const [nodeRuns, setNodeRuns] = useState12(
|
|
2294
2848
|
{}
|
|
2295
2849
|
);
|
|
2296
|
-
const [text, setText] =
|
|
2297
|
-
const [output, setOutput] =
|
|
2298
|
-
const [error, setError] =
|
|
2299
|
-
const [events, setEvents] =
|
|
2300
|
-
const abortControllerRef =
|
|
2301
|
-
const currentRunIdRef =
|
|
2850
|
+
const [text, setText] = useState12("");
|
|
2851
|
+
const [output, setOutput] = useState12(null);
|
|
2852
|
+
const [error, setError] = useState12(null);
|
|
2853
|
+
const [events, setEvents] = useState12([]);
|
|
2854
|
+
const abortControllerRef = useRef4(null);
|
|
2855
|
+
const currentRunIdRef = useRef4(null);
|
|
2302
2856
|
currentRunIdRef.current = runId;
|
|
2303
|
-
const optionsRef =
|
|
2857
|
+
const optionsRef = useRef4(options);
|
|
2304
2858
|
optionsRef.current = options;
|
|
2305
|
-
const cancel =
|
|
2859
|
+
const cancel = useCallback13(async () => {
|
|
2306
2860
|
if (abortControllerRef.current) {
|
|
2307
2861
|
abortControllerRef.current.abort();
|
|
2308
2862
|
abortControllerRef.current = null;
|
|
@@ -2323,7 +2877,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2323
2877
|
setStatus("cancelled");
|
|
2324
2878
|
setActiveNodeId(null);
|
|
2325
2879
|
}, [fetchWithAuth, logger]);
|
|
2326
|
-
const reset =
|
|
2880
|
+
const reset = useCallback13(() => {
|
|
2327
2881
|
if (abortControllerRef.current) {
|
|
2328
2882
|
abortControllerRef.current.abort();
|
|
2329
2883
|
abortControllerRef.current = null;
|
|
@@ -2337,7 +2891,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2337
2891
|
setError(null);
|
|
2338
2892
|
setEvents([]);
|
|
2339
2893
|
}, []);
|
|
2340
|
-
const processStream =
|
|
2894
|
+
const processStream = useCallback13(
|
|
2341
2895
|
async (url, method, body, targetRunId) => {
|
|
2342
2896
|
if (abortControllerRef.current) {
|
|
2343
2897
|
abortControllerRef.current.abort();
|
|
@@ -2502,7 +3056,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2502
3056
|
},
|
|
2503
3057
|
[getAuthToken]
|
|
2504
3058
|
);
|
|
2505
|
-
const start =
|
|
3059
|
+
const start = useCallback13(
|
|
2506
3060
|
async (input, opts) => {
|
|
2507
3061
|
const targetWfId = opts?.workflowId || workflowId;
|
|
2508
3062
|
if (!targetWfId) {
|
|
@@ -2523,7 +3077,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2523
3077
|
},
|
|
2524
3078
|
[workflowId, baseUrl, processStream]
|
|
2525
3079
|
);
|
|
2526
|
-
const resume =
|
|
3080
|
+
const resume = useCallback13(
|
|
2527
3081
|
async (targetRunId, sinceSeq) => {
|
|
2528
3082
|
if (!targetRunId) {
|
|
2529
3083
|
throw new Error("Run ID is required to resume stream");
|
|
@@ -2536,7 +3090,7 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2536
3090
|
},
|
|
2537
3091
|
[baseUrl, processStream]
|
|
2538
3092
|
);
|
|
2539
|
-
|
|
3093
|
+
useEffect12(() => {
|
|
2540
3094
|
return () => {
|
|
2541
3095
|
if (abortControllerRef.current) {
|
|
2542
3096
|
abortControllerRef.current.abort();
|
|
@@ -2561,21 +3115,21 @@ function useWorkflowStream(workflowId, options) {
|
|
|
2561
3115
|
}
|
|
2562
3116
|
|
|
2563
3117
|
// src/hooks/useWorkflowRuns.ts
|
|
2564
|
-
import { useCallback as
|
|
3118
|
+
import { useCallback as useCallback14, useEffect as useEffect13, useState as useState13 } from "react";
|
|
2565
3119
|
function useWorkflowRuns(workflowId, options) {
|
|
2566
3120
|
const opts = typeof options === "boolean" ? { autoFetch: options } : options ?? {};
|
|
2567
3121
|
const { autoFetch = true, page = 1, limit = 20 } = opts;
|
|
2568
3122
|
const { fetchWithAuth } = usePersonaContext();
|
|
2569
|
-
const [runs, setRuns] =
|
|
2570
|
-
const [pagination, setPagination] =
|
|
3123
|
+
const [runs, setRuns] = useState13([]);
|
|
3124
|
+
const [pagination, setPagination] = useState13({
|
|
2571
3125
|
page,
|
|
2572
3126
|
limit,
|
|
2573
3127
|
total: 0,
|
|
2574
3128
|
totalPages: 0
|
|
2575
3129
|
});
|
|
2576
|
-
const [isLoading, setIsLoading] =
|
|
2577
|
-
const [error, setError] =
|
|
2578
|
-
const fetchRuns =
|
|
3130
|
+
const [isLoading, setIsLoading] = useState13(false);
|
|
3131
|
+
const [error, setError] = useState13(null);
|
|
3132
|
+
const fetchRuns = useCallback14(async () => {
|
|
2579
3133
|
if (!workflowId) return [];
|
|
2580
3134
|
setIsLoading(true);
|
|
2581
3135
|
setError(null);
|
|
@@ -2605,7 +3159,7 @@ function useWorkflowRuns(workflowId, options) {
|
|
|
2605
3159
|
setIsLoading(false);
|
|
2606
3160
|
}
|
|
2607
3161
|
}, [fetchWithAuth, workflowId, page, limit]);
|
|
2608
|
-
const getRun =
|
|
3162
|
+
const getRun = useCallback14(
|
|
2609
3163
|
async (runId) => {
|
|
2610
3164
|
if (!runId) throw new Error("Run ID is required");
|
|
2611
3165
|
const res = await fetchWithAuth(`/workflows/runs/${runId}`);
|
|
@@ -2616,7 +3170,7 @@ function useWorkflowRuns(workflowId, options) {
|
|
|
2616
3170
|
},
|
|
2617
3171
|
[fetchWithAuth]
|
|
2618
3172
|
);
|
|
2619
|
-
const cancelRun =
|
|
3173
|
+
const cancelRun = useCallback14(
|
|
2620
3174
|
async (runId) => {
|
|
2621
3175
|
if (!runId) throw new Error("Run ID is required");
|
|
2622
3176
|
const res = await fetchWithAuth(`/workflows/runs/${runId}/cancel`, {
|
|
@@ -2631,7 +3185,7 @@ function useWorkflowRuns(workflowId, options) {
|
|
|
2631
3185
|
},
|
|
2632
3186
|
[fetchWithAuth]
|
|
2633
3187
|
);
|
|
2634
|
-
|
|
3188
|
+
useEffect13(() => {
|
|
2635
3189
|
if (autoFetch && workflowId) {
|
|
2636
3190
|
void fetchRuns();
|
|
2637
3191
|
}
|
|
@@ -2655,8 +3209,9 @@ import {
|
|
|
2655
3209
|
getLogLevel,
|
|
2656
3210
|
isLevelEnabled
|
|
2657
3211
|
} from "@personaai/logger";
|
|
2658
|
-
var VERSION = "0.
|
|
3212
|
+
var VERSION = "0.10.0";
|
|
2659
3213
|
export {
|
|
3214
|
+
ARCHITECT_AGENT_ID,
|
|
2660
3215
|
PersonaProvider,
|
|
2661
3216
|
VERSION,
|
|
2662
3217
|
createLogger2 as createLogger,
|
|
@@ -2667,6 +3222,7 @@ export {
|
|
|
2667
3222
|
setLogLevel,
|
|
2668
3223
|
supportsStreamingFetch,
|
|
2669
3224
|
useAgents,
|
|
3225
|
+
useArchitectChat,
|
|
2670
3226
|
useChat,
|
|
2671
3227
|
useConnection,
|
|
2672
3228
|
useFiles,
|