@agent-native/dispatch 0.40.37 → 0.40.38
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/CHANGELOG.md +13 -8
- package/dist/components/layout/Layout.d.ts +0 -1
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +57 -101
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +9 -5
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/community-apps.js +1 -1
- package/dist/server/lib/community-apps.js.map +1 -1
- package/package.json +3 -3
- package/src/components/layout/Layout.spec.tsx +348 -26
- package/src/components/layout/Layout.tsx +119 -193
- package/src/server/lib/app-creation-store.spec.ts +26 -5
- package/src/server/lib/app-creation-store.ts +10 -6
- package/src/server/lib/community-apps.ts +1 -1
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
useAgentChatHomeHandoff,
|
|
28
28
|
useAgentChatHomeHandoffLinks,
|
|
29
29
|
useChatThreads,
|
|
30
|
-
type ChatThreadSummary,
|
|
31
30
|
} from "@agent-native/core/client/agent-chat";
|
|
32
31
|
import { appBasePath, appPath } from "@agent-native/core/client/api-path";
|
|
33
32
|
import {
|
|
@@ -55,6 +54,11 @@ import type {
|
|
|
55
54
|
ChatFirstCopy,
|
|
56
55
|
ChatFirstEmbedTarget,
|
|
57
56
|
} from "@agent-native/toolkit/app/chat/chat-first/types";
|
|
57
|
+
import {
|
|
58
|
+
OwnershipChatHistoryRail,
|
|
59
|
+
ownDraftRows,
|
|
60
|
+
useOwnershipChatHistory,
|
|
61
|
+
} from "@agent-native/toolkit/app/chat/ownership-history";
|
|
58
62
|
import { FeedbackButton } from "@agent-native/toolkit/app/feedback";
|
|
59
63
|
import { InvitationBanner, OrgSwitcher } from "@agent-native/toolkit/app/org";
|
|
60
64
|
import { RunsTray } from "@agent-native/toolkit/app/progress";
|
|
@@ -64,10 +68,6 @@ import {
|
|
|
64
68
|
AppSidebarHeader,
|
|
65
69
|
openCommandMenu,
|
|
66
70
|
} from "@agent-native/toolkit/app/shared";
|
|
67
|
-
import {
|
|
68
|
-
ChatHistoryRail,
|
|
69
|
-
type ChatHistoryItem,
|
|
70
|
-
} from "@agent-native/toolkit/chat-history";
|
|
71
71
|
import { writeClipboardText } from "@agent-native/toolkit/clipboard";
|
|
72
72
|
import {
|
|
73
73
|
IconApps,
|
|
@@ -143,7 +143,6 @@ import {
|
|
|
143
143
|
} from "../deferred-chat-components.js";
|
|
144
144
|
import { Button } from "../ui/button";
|
|
145
145
|
import { Sheet, SheetContent, SheetDescription, SheetTitle } from "../ui/sheet";
|
|
146
|
-
import { Skeleton } from "../ui/skeleton";
|
|
147
146
|
import {
|
|
148
147
|
Tooltip,
|
|
149
148
|
TooltipContent,
|
|
@@ -588,23 +587,6 @@ function threadIdFromPath(pathname: string): string | null {
|
|
|
588
587
|
}
|
|
589
588
|
}
|
|
590
589
|
|
|
591
|
-
export function formatThreadAge(updatedAt: number, now = Date.now()) {
|
|
592
|
-
const diffMs = Math.max(0, now - updatedAt);
|
|
593
|
-
const minutes = Math.floor(diffMs / 60_000);
|
|
594
|
-
if (minutes < 1) return "now";
|
|
595
|
-
if (minutes < 60) return `${minutes}m`;
|
|
596
|
-
const hours = Math.floor(minutes / 60);
|
|
597
|
-
if (hours < 24) return `${hours}h`;
|
|
598
|
-
const days = Math.floor(hours / 24);
|
|
599
|
-
if (days < 14) return `${days}d`;
|
|
600
|
-
if (days < 365) return `${Math.floor(days / 7)}w`;
|
|
601
|
-
return `${Math.floor(days / 365)}y`;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function threadTitle(thread: ChatThreadSummary, fallback: string) {
|
|
605
|
-
return thread.title || thread.preview || fallback;
|
|
606
|
-
}
|
|
607
|
-
|
|
608
590
|
function threadSourceIcon(platform: string | undefined): ReactNode {
|
|
609
591
|
const normalized = platform?.trim().toLowerCase();
|
|
610
592
|
if (!normalized) return null;
|
|
@@ -627,14 +609,6 @@ function readChatHistoryIncludesExternal(): boolean {
|
|
|
627
609
|
}
|
|
628
610
|
}
|
|
629
611
|
|
|
630
|
-
function threadUpdatedAt(thread: ChatThreadSummary) {
|
|
631
|
-
return Number.isFinite(thread.updatedAt)
|
|
632
|
-
? thread.updatedAt
|
|
633
|
-
: Number.isFinite(thread.createdAt)
|
|
634
|
-
? thread.createdAt
|
|
635
|
-
: 0;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
612
|
function DispatchChatsSection({
|
|
639
613
|
onNavigate,
|
|
640
614
|
showNewChat = false,
|
|
@@ -663,78 +637,74 @@ function DispatchChatsSection({
|
|
|
663
637
|
const location = useLocation();
|
|
664
638
|
const [includeExternal, setIncludeExternal] = useState(false);
|
|
665
639
|
const [historyPreferenceReady, setHistoryPreferenceReady] = useState(false);
|
|
666
|
-
const historyModeRef = useRef(includeExternal);
|
|
667
640
|
const {
|
|
668
641
|
threads,
|
|
669
642
|
activeThreadId,
|
|
670
|
-
isLoading: chatsLoading,
|
|
671
|
-
threadsLoadError,
|
|
672
643
|
createThread,
|
|
673
644
|
switchThread,
|
|
674
645
|
renameThread,
|
|
675
|
-
|
|
646
|
+
pinThread,
|
|
647
|
+
archiveThread,
|
|
648
|
+
isNewThread,
|
|
676
649
|
} = useChatThreads(undefined, "dispatch", undefined, {
|
|
677
650
|
autoCreate: false,
|
|
678
651
|
includeExternal,
|
|
679
652
|
});
|
|
680
653
|
|
|
681
|
-
const visibleThreads = useMemo(
|
|
682
|
-
() =>
|
|
683
|
-
threads
|
|
684
|
-
.filter(
|
|
685
|
-
(thread) => thread.messageCount > 0 || thread.id === activeThreadId,
|
|
686
|
-
)
|
|
687
|
-
.sort((a, b) => threadUpdatedAt(b) - threadUpdatedAt(a))
|
|
688
|
-
.slice(0, 8),
|
|
689
|
-
[activeThreadId, threads],
|
|
690
|
-
);
|
|
691
654
|
const localPathname = localDispatchPath(location.pathname);
|
|
692
655
|
const displayedActiveThreadId =
|
|
693
656
|
threadIdFromPath(localPathname) ??
|
|
694
657
|
(localPathname === "/chat" ? null : activeThreadId);
|
|
695
|
-
const
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
658
|
+
const history = useOwnershipChatHistory({
|
|
659
|
+
getThreadUrl: (threadId) => appPath(chatThreadPath(threadId)),
|
|
660
|
+
includeExternal,
|
|
661
|
+
activeId: displayedActiveThreadId,
|
|
662
|
+
localMineRows: ownDraftRows({
|
|
663
|
+
threads,
|
|
664
|
+
isNewThread,
|
|
665
|
+
activeId: activeThreadId,
|
|
666
|
+
}),
|
|
667
|
+
mutations: { pin: pinThread, rename: renameThread, archive: archiveThread },
|
|
668
|
+
onMutationFailed: () =>
|
|
669
|
+
toast.error(t("dispatch.integrations.failedToSave")),
|
|
670
|
+
decorateItem: (item, row) => {
|
|
671
|
+
const source = "source" in row ? row.source : undefined;
|
|
672
|
+
const sourceLabel = source?.platform
|
|
673
|
+
? source.platform[0].toUpperCase() + source.platform.slice(1)
|
|
674
|
+
: undefined;
|
|
675
|
+
const sourceIcon = threadSourceIcon(source?.platform);
|
|
676
|
+
return {
|
|
677
|
+
...item,
|
|
678
|
+
title: (
|
|
679
|
+
<span
|
|
680
|
+
className="flex min-w-0 items-center gap-1"
|
|
681
|
+
title={
|
|
682
|
+
sourceLabel ? `${sourceLabel}: ${item.titleText}` : item.titleText
|
|
683
|
+
}
|
|
684
|
+
>
|
|
685
|
+
{sourceIcon ? (
|
|
686
|
+
<span
|
|
687
|
+
className="shrink-0 text-sidebar-foreground/55"
|
|
688
|
+
aria-label={sourceLabel}
|
|
689
|
+
>
|
|
690
|
+
{sourceIcon}
|
|
691
|
+
</span>
|
|
692
|
+
) : null}
|
|
693
|
+
<span className="truncate">{item.title}</span>
|
|
694
|
+
</span>
|
|
695
|
+
),
|
|
696
|
+
};
|
|
697
|
+
},
|
|
726
698
|
});
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
) : undefined;
|
|
737
|
-
|
|
699
|
+
const listProps = {
|
|
700
|
+
...history.listProps,
|
|
701
|
+
onDelete: async (threadId: string) => {
|
|
702
|
+
const archived = await history.listProps.onDelete?.(threadId);
|
|
703
|
+
if (archived && threadId === displayedActiveThreadId)
|
|
704
|
+
await handleNewChat();
|
|
705
|
+
return archived === true;
|
|
706
|
+
},
|
|
707
|
+
};
|
|
738
708
|
useEffect(() => {
|
|
739
709
|
setIncludeExternal(readChatHistoryIncludesExternal());
|
|
740
710
|
setHistoryPreferenceReady(true);
|
|
@@ -748,30 +718,7 @@ function DispatchChatsSection({
|
|
|
748
718
|
includeExternal ? "all" : "local",
|
|
749
719
|
);
|
|
750
720
|
} catch {} // coercion-ok: localStorage is optional browser persistence.
|
|
751
|
-
|
|
752
|
-
historyModeRef.current = includeExternal;
|
|
753
|
-
refreshThreads();
|
|
754
|
-
}
|
|
755
|
-
}, [historyPreferenceReady, includeExternal, refreshThreads]);
|
|
756
|
-
|
|
757
|
-
useEffect(() => {
|
|
758
|
-
const refresh = () => refreshThreads();
|
|
759
|
-
const handleRunning = (event: Event) => {
|
|
760
|
-
const detail = (event as CustomEvent).detail as
|
|
761
|
-
| { isRunning?: unknown }
|
|
762
|
-
| undefined;
|
|
763
|
-
if (detail?.isRunning === false) refreshThreads();
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
window.addEventListener("agent-chat:threads-updated", refresh);
|
|
767
|
-
window.addEventListener("agentNative.chatRunning", handleRunning);
|
|
768
|
-
window.addEventListener("focus", refresh);
|
|
769
|
-
return () => {
|
|
770
|
-
window.removeEventListener("agent-chat:threads-updated", refresh);
|
|
771
|
-
window.removeEventListener("agentNative.chatRunning", handleRunning);
|
|
772
|
-
window.removeEventListener("focus", refresh);
|
|
773
|
-
};
|
|
774
|
-
}, [refreshThreads]);
|
|
721
|
+
}, [historyPreferenceReady, includeExternal]);
|
|
775
722
|
|
|
776
723
|
function openThread(threadId: string, options?: { isNew?: boolean }) {
|
|
777
724
|
switchThread(threadId);
|
|
@@ -796,6 +743,36 @@ function DispatchChatsSection({
|
|
|
796
743
|
if (threadId) openThread(threadId, { isNew: true });
|
|
797
744
|
}
|
|
798
745
|
|
|
746
|
+
const sourceToggle = (
|
|
747
|
+
<Tooltip>
|
|
748
|
+
<TooltipTrigger asChild>
|
|
749
|
+
<button
|
|
750
|
+
type="button"
|
|
751
|
+
data-dispatch-chat-source-toggle
|
|
752
|
+
aria-pressed={includeExternal}
|
|
753
|
+
aria-label={
|
|
754
|
+
includeExternal
|
|
755
|
+
? t("dispatch.sidebar.showLocalChats")
|
|
756
|
+
: t("dispatch.sidebar.showAllChats")
|
|
757
|
+
}
|
|
758
|
+
onClick={() => setIncludeExternal((current) => !current)}
|
|
759
|
+
className="flex size-6 items-center justify-center rounded-md text-sidebar-foreground/55 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
760
|
+
>
|
|
761
|
+
{includeExternal ? (
|
|
762
|
+
<IconWorld size={14} aria-hidden="true" />
|
|
763
|
+
) : (
|
|
764
|
+
<IconDeviceDesktop size={14} aria-hidden="true" />
|
|
765
|
+
)}
|
|
766
|
+
</button>
|
|
767
|
+
</TooltipTrigger>
|
|
768
|
+
<TooltipContent side="right">
|
|
769
|
+
{includeExternal
|
|
770
|
+
? t("dispatch.sidebar.showLocalChats")
|
|
771
|
+
: t("dispatch.sidebar.showAllChats")}
|
|
772
|
+
</TooltipContent>
|
|
773
|
+
</Tooltip>
|
|
774
|
+
);
|
|
775
|
+
|
|
799
776
|
const collapsedChatFirst = collapsed && chatFirstMode;
|
|
800
777
|
|
|
801
778
|
return (
|
|
@@ -822,80 +799,26 @@ function DispatchChatsSection({
|
|
|
822
799
|
</nav>
|
|
823
800
|
) : null}
|
|
824
801
|
{prelude}
|
|
825
|
-
{!collapsedChatFirst
|
|
826
|
-
<div className="flex justify-end px-2 pt-0.5">
|
|
827
|
-
<Tooltip>
|
|
828
|
-
<TooltipTrigger asChild>
|
|
829
|
-
<button
|
|
830
|
-
type="button"
|
|
831
|
-
data-dispatch-chat-source-toggle
|
|
832
|
-
aria-pressed={includeExternal}
|
|
833
|
-
aria-label={
|
|
834
|
-
includeExternal
|
|
835
|
-
? t("dispatch.sidebar.showLocalChats", {
|
|
836
|
-
defaultValue: "Show local chats",
|
|
837
|
-
})
|
|
838
|
-
: t("dispatch.sidebar.showAllChats", {
|
|
839
|
-
defaultValue: "Show all chats",
|
|
840
|
-
})
|
|
841
|
-
}
|
|
842
|
-
onClick={() => setIncludeExternal((current) => !current)}
|
|
843
|
-
className="flex size-6 items-center justify-center rounded-md text-sidebar-foreground/55 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
844
|
-
>
|
|
845
|
-
{includeExternal ? (
|
|
846
|
-
<IconWorld size={14} aria-hidden="true" />
|
|
847
|
-
) : (
|
|
848
|
-
<IconDeviceDesktop size={14} aria-hidden="true" />
|
|
849
|
-
)}
|
|
850
|
-
</button>
|
|
851
|
-
</TooltipTrigger>
|
|
852
|
-
<TooltipContent side="right">
|
|
853
|
-
{includeExternal
|
|
854
|
-
? t("dispatch.sidebar.showLocalChats", {
|
|
855
|
-
defaultValue: "Show local chats",
|
|
856
|
-
})
|
|
857
|
-
: t("dispatch.sidebar.showAllChats", {
|
|
858
|
-
defaultValue: "Show all chats",
|
|
859
|
-
})}
|
|
860
|
-
</TooltipContent>
|
|
861
|
-
</Tooltip>
|
|
862
|
-
</div>
|
|
802
|
+
{!collapsedChatFirst ? (
|
|
803
|
+
<div className="flex justify-end px-2 pt-0.5">{sourceToggle}</div>
|
|
863
804
|
) : null}
|
|
864
|
-
{!collapsedChatFirst &&
|
|
865
|
-
!chatFirstMode &&
|
|
866
|
-
chatsLoading &&
|
|
867
|
-
visibleThreads.length === 0 &&
|
|
868
|
-
Array.from({ length: 3 }).map((_, index) => (
|
|
869
|
-
<div
|
|
870
|
-
key={`chat-skeleton-${index}`}
|
|
871
|
-
className="flex items-center gap-2 px-3 py-1"
|
|
872
|
-
>
|
|
873
|
-
<Skeleton className="size-3.5 shrink-0 rounded-sm" />
|
|
874
|
-
<Skeleton className="h-3 w-3/4 rounded" />
|
|
875
|
-
</div>
|
|
876
|
-
))}
|
|
877
805
|
{!collapsedChatFirst &&
|
|
878
806
|
chatFirstMode &&
|
|
879
|
-
(
|
|
807
|
+
(history.loading ||
|
|
808
|
+
history.sections.some(
|
|
809
|
+
(section) => section.items.length > 0 || section.error,
|
|
810
|
+
)) ? (
|
|
880
811
|
<ChatFirstChatHistory
|
|
881
|
-
|
|
812
|
+
sections={history.sections}
|
|
813
|
+
{...history.listProps}
|
|
814
|
+
onDelete={listProps.onDelete}
|
|
815
|
+
onNewChat={() => void handleNewChat()}
|
|
816
|
+
showNewChat={false}
|
|
817
|
+
railLabels={history.railLabels}
|
|
882
818
|
activeId={displayedActiveThreadId}
|
|
883
|
-
|
|
884
|
-
loadingLabel={
|
|
885
|
-
<div className="space-y-1 px-2 py-1">
|
|
886
|
-
{[0, 1, 2].map((index) => (
|
|
887
|
-
<div key={index} className="flex items-center gap-2 px-1 py-1">
|
|
888
|
-
<Skeleton className="size-3.5 shrink-0 rounded-sm" />
|
|
889
|
-
<Skeleton className="h-3 w-3/4 rounded" />
|
|
890
|
-
</div>
|
|
891
|
-
))}
|
|
892
|
-
</div>
|
|
893
|
-
}
|
|
894
|
-
label={t("dispatch.sidebar.chats", { defaultValue: "Chats" })}
|
|
895
|
-
error={chatHistoryError}
|
|
819
|
+
label={t("dispatch.sidebar.chats")}
|
|
896
820
|
onSelect={(threadId) => openThread(threadId)}
|
|
897
821
|
renameMaxLength={160}
|
|
898
|
-
onRename={(threadId, title) => void renameThread(threadId, title)}
|
|
899
822
|
labels={{
|
|
900
823
|
options: (item) =>
|
|
901
824
|
t("dispatch.sidebar.chatOptions", {
|
|
@@ -906,6 +829,9 @@ function DispatchChatsSection({
|
|
|
906
829
|
title: item.titleText ?? "",
|
|
907
830
|
}),
|
|
908
831
|
rename: t("dispatch.sidebar.renameChat"),
|
|
832
|
+
pin: t("agentChat.contextXray.pin"),
|
|
833
|
+
unpin: t("agentChat.contextXray.unpin"),
|
|
834
|
+
delete: t("extensions.delete"),
|
|
909
835
|
}}
|
|
910
836
|
renderAdditionalRowActions={(item, closeMenu) => (
|
|
911
837
|
<>
|
|
@@ -918,14 +844,14 @@ function DispatchChatsSection({
|
|
|
918
844
|
void writeClipboardText(item.id).then((copied) => {
|
|
919
845
|
toast[copied ? "success" : "error"](
|
|
920
846
|
copied
|
|
921
|
-
? "
|
|
922
|
-
: "
|
|
847
|
+
? chatFirstCopy("sessionIdCopied")
|
|
848
|
+
: t("agentChat.recovery.copyFailed"),
|
|
923
849
|
);
|
|
924
850
|
});
|
|
925
851
|
}}
|
|
926
852
|
>
|
|
927
853
|
<IconCopy size={13} strokeWidth={1.8} />
|
|
928
|
-
<span>
|
|
854
|
+
<span>{chatFirstCopy("copySessionId")}</span>
|
|
929
855
|
</button>
|
|
930
856
|
{!chatFirstEmbedded ? (
|
|
931
857
|
<button
|
|
@@ -943,27 +869,24 @@ function DispatchChatsSection({
|
|
|
943
869
|
}}
|
|
944
870
|
>
|
|
945
871
|
<IconEye size={13} strokeWidth={1.8} />
|
|
946
|
-
<span>
|
|
872
|
+
<span>{chatFirstCopy("watchSession")}</span>
|
|
947
873
|
</button>
|
|
948
874
|
) : null}
|
|
949
875
|
</>
|
|
950
876
|
)}
|
|
951
877
|
className="min-w-0 px-2"
|
|
952
878
|
/>
|
|
953
|
-
) : !collapsedChatFirst ? (
|
|
954
|
-
<
|
|
955
|
-
|
|
879
|
+
) : !collapsedChatFirst && !chatFirstMode ? (
|
|
880
|
+
<OwnershipChatHistoryRail
|
|
881
|
+
sections={history.sections}
|
|
882
|
+
listProps={listProps}
|
|
956
883
|
activeId={displayedActiveThreadId}
|
|
957
884
|
onSelect={(threadId) => openThread(threadId)}
|
|
958
885
|
onNewChat={() => void handleNewChat()}
|
|
959
886
|
railLabels={{
|
|
960
887
|
newChat: t("dispatch.sidebar.newChat"),
|
|
961
|
-
showMore: t("dispatch.sidebar.chats"),
|
|
962
|
-
showLess: t("dispatch.sidebar.chats"),
|
|
963
888
|
}}
|
|
964
|
-
error={chatHistoryError}
|
|
965
889
|
renameMaxLength={160}
|
|
966
|
-
onRename={(threadId, title) => void renameThread(threadId, title)}
|
|
967
890
|
labels={{
|
|
968
891
|
options: (item) =>
|
|
969
892
|
t("dispatch.sidebar.chatOptions", {
|
|
@@ -974,6 +897,9 @@ function DispatchChatsSection({
|
|
|
974
897
|
title: item.titleText ?? "",
|
|
975
898
|
}),
|
|
976
899
|
rename: t("dispatch.sidebar.renameChat"),
|
|
900
|
+
pin: t("agentChat.contextXray.pin"),
|
|
901
|
+
unpin: t("agentChat.contextXray.unpin"),
|
|
902
|
+
delete: t("extensions.delete"),
|
|
977
903
|
}}
|
|
978
904
|
renderAdditionalRowActions={(item, closeMenu) => (
|
|
979
905
|
<>
|
|
@@ -986,14 +912,14 @@ function DispatchChatsSection({
|
|
|
986
912
|
void writeClipboardText(item.id).then((copied) => {
|
|
987
913
|
toast[copied ? "success" : "error"](
|
|
988
914
|
copied
|
|
989
|
-
? "
|
|
990
|
-
: "
|
|
915
|
+
? chatFirstCopy("sessionIdCopied")
|
|
916
|
+
: t("agentChat.recovery.copyFailed"),
|
|
991
917
|
);
|
|
992
918
|
});
|
|
993
919
|
}}
|
|
994
920
|
>
|
|
995
921
|
<IconCopy size={13} strokeWidth={1.8} />
|
|
996
|
-
<span>
|
|
922
|
+
<span>{chatFirstCopy("copySessionId")}</span>
|
|
997
923
|
</button>
|
|
998
924
|
{chatFirstMode && !chatFirstEmbedded ? (
|
|
999
925
|
<button
|
|
@@ -1011,7 +937,7 @@ function DispatchChatsSection({
|
|
|
1011
937
|
}}
|
|
1012
938
|
>
|
|
1013
939
|
<IconEye size={13} strokeWidth={1.8} />
|
|
1014
|
-
<span>
|
|
940
|
+
<span>{chatFirstCopy("watchSession")}</span>
|
|
1015
941
|
</button>
|
|
1016
942
|
) : null}
|
|
1017
943
|
</>
|
|
@@ -636,7 +636,7 @@ describe("listWorkspaceApps", () => {
|
|
|
636
636
|
expect(apps[0]?.url).toBe("https://agent-workspace.builder.io/atlas");
|
|
637
637
|
});
|
|
638
638
|
|
|
639
|
-
it("signs the hosted registry request with the
|
|
639
|
+
it("signs the hosted registry request as the member with the deployment secret", async () => {
|
|
640
640
|
const fetchMock = vi.fn(
|
|
641
641
|
async () =>
|
|
642
642
|
new Response(JSON.stringify([]), {
|
|
@@ -656,15 +656,36 @@ describe("listWorkspaceApps", () => {
|
|
|
656
656
|
const authorization = fetchMock.mock.calls[0]?.[1]?.headers
|
|
657
657
|
?.Authorization as string;
|
|
658
658
|
const token = authorization.slice("Bearer ".length);
|
|
659
|
-
const payload = verifyJwtWithSecret(token, "
|
|
659
|
+
const payload = verifyJwtWithSecret(token, "dispatch-deployment-secret");
|
|
660
660
|
expect(payload).toMatchObject({
|
|
661
661
|
sub: "dev@example.test",
|
|
662
662
|
org_domain: "example.test",
|
|
663
663
|
aud: "https://agent-workspace.builder.io",
|
|
664
664
|
});
|
|
665
|
-
expect(() =>
|
|
666
|
-
|
|
667
|
-
|
|
665
|
+
expect(() => verifyJwtWithSecret(token, "workspace-org-secret")).toThrow();
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it("reports a missing deployment secret instead of signing a member's request with the organization secret", async () => {
|
|
669
|
+
const fetchMock = vi.fn();
|
|
670
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
671
|
+
vi.stubEnv("A2A_SECRET", "");
|
|
672
|
+
vi.stubEnv("WORKSPACE_GATEWAY_URL", "https://agent-workspace.builder.io");
|
|
673
|
+
mocks.getOrgA2ASecret.mockResolvedValueOnce("workspace-org-secret");
|
|
674
|
+
stubManifest([
|
|
675
|
+
{ id: "dispatch", name: "Dispatch", path: "/dispatch" },
|
|
676
|
+
{ id: "clips", name: "Clips", path: "/clips" },
|
|
677
|
+
]);
|
|
678
|
+
|
|
679
|
+
await expect(
|
|
680
|
+
runWithRequestContext(
|
|
681
|
+
{ userEmail: "dev@example.test", orgId: "org-1" },
|
|
682
|
+
() => listWorkspaceApps({ includeAgentCards: false }),
|
|
683
|
+
),
|
|
684
|
+
).rejects.toMatchObject({
|
|
685
|
+
statusCode: 503,
|
|
686
|
+
errorCode: "workspace_gateway_a2a_secret_missing",
|
|
687
|
+
});
|
|
688
|
+
expect(fetchMock).not.toHaveBeenCalled();
|
|
668
689
|
});
|
|
669
690
|
|
|
670
691
|
it("reports missing hosted gateway signing credentials instead of sending anonymously", async () => {
|
|
@@ -1935,10 +1935,14 @@ async function readWorkspaceAppsFromGateway(): Promise<WorkspaceAppDiscovery | n
|
|
|
1935
1935
|
);
|
|
1936
1936
|
}
|
|
1937
1937
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1938
|
+
// The receiver drops the user from any user token that verifies only with an
|
|
1939
|
+
// organization secret, so a member's own request is signed with the
|
|
1940
|
+
// deployment secret to stay named.
|
|
1941
|
+
const delegatedMember =
|
|
1942
|
+
requestContext.a2aIdentityAssurance === "org-member";
|
|
1943
|
+
const signingSecret = delegatedMember ? orgSecret : getGlobalA2ASecret();
|
|
1944
|
+
const allowLocalUnauthenticatedRequest = isLocalGateway && !signingSecret;
|
|
1945
|
+
if (!signingSecret && !allowLocalUnauthenticatedRequest) {
|
|
1942
1946
|
throw new WorkspaceAppsGatewayAuthorizationError(
|
|
1943
1947
|
503,
|
|
1944
1948
|
"workspace_gateway_a2a_secret_missing",
|
|
@@ -1950,10 +1954,10 @@ async function readWorkspaceAppsFromGateway(): Promise<WorkspaceAppDiscovery | n
|
|
|
1950
1954
|
const token = await signA2AToken(
|
|
1951
1955
|
requestContext.userEmail,
|
|
1952
1956
|
orgDomain,
|
|
1953
|
-
orgSecret,
|
|
1957
|
+
delegatedMember ? orgSecret : undefined,
|
|
1954
1958
|
{
|
|
1955
1959
|
expiresIn: "1m",
|
|
1956
|
-
preferGlobalSecret:
|
|
1960
|
+
preferGlobalSecret: !delegatedMember,
|
|
1957
1961
|
audience: canonicalA2AAudience(
|
|
1958
1962
|
baseUrl.toString(),
|
|
1959
1963
|
getConfiguredAppBasePath() || undefined,
|
|
@@ -95,7 +95,7 @@ export const CURATED_COMMUNITY_APP_SOURCES: readonly CuratedCommunityAppSource[]
|
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
path: "package.json",
|
|
98
|
-
blobSha: "
|
|
98
|
+
blobSha: "b37b5659f115dd887f97611ff01e9861c004e0f8",
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
path: "server/db/index.ts",
|