@agent-native/dispatch 0.13.2 → 0.13.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/dispatch",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "Dispatch — workspace control plane for agent-native apps. Vault, integrations, destinations, scheduled jobs, and cross-app delegation, shipped as a single drop-in package.",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -94,7 +94,7 @@
94
94
  "typescript": "^6.0.3",
95
95
  "vite": "8.1.0",
96
96
  "vitest": "^4.1.5",
97
- "@agent-native/core": "0.79.18"
97
+ "@agent-native/core": "0.80.1"
98
98
  },
99
99
  "peerDependencies": {
100
100
  "@agent-native/core": ">=0.8.0",
@@ -33,6 +33,8 @@ import {
33
33
  IconBroadcast,
34
34
  IconFingerprint,
35
35
  IconHistory,
36
+ IconLayoutSidebarLeftCollapse,
37
+ IconLayoutSidebarLeftExpand,
36
38
  IconPuzzle,
37
39
  IconSettings,
38
40
  IconShieldCheck,
@@ -219,6 +221,7 @@ const OPERATIONS_NAV_ITEMS = [
219
221
  const EMPTY_NAV_ITEMS: readonly DispatchNavItem[] = [];
220
222
 
221
223
  const CHROMELESS_PATHS = ["/approval"];
224
+ const SIDEBAR_COLLAPSE_KEY = "dispatch.sidebar.collapsed";
222
225
 
223
226
  // Routes whose page renders its own toolbar (with NotificationsBell + AgentToggleButton).
224
227
  // Layout still mounts the sidebar + AgentSidebar, but skips its own Header so
@@ -573,9 +576,15 @@ function DispatchChatsSection({ onNavigate }: { onNavigate?: () => void }) {
573
576
  export function NavContent({
574
577
  onNavigate,
575
578
  extensions,
579
+ collapsed = false,
580
+ collapsible = false,
581
+ onCollapsedChange,
576
582
  }: {
577
583
  onNavigate?: () => void;
578
584
  extensions?: DispatchExtensionConfig;
585
+ collapsed?: boolean;
586
+ collapsible?: boolean;
587
+ onCollapsedChange?: (collapsed: boolean) => void;
579
588
  }) {
580
589
  const t = useT();
581
590
  const location = useLocation();
@@ -616,45 +625,91 @@ export function NavContent({
616
625
  const label = navLabel(item);
617
626
  return (
618
627
  <li key={item.id}>
619
- <NavLink
620
- to={dispatchNavLinkTarget(item.to)}
621
- onClick={(event) => {
622
- if (
623
- item.id === "chat" &&
624
- localPathname !== "/chat" &&
625
- !event.metaKey &&
626
- !event.ctrlKey &&
627
- !event.shiftKey &&
628
- !event.altKey
629
- ) {
630
- event.preventDefault();
631
- navigateWithAgentChatViewTransition(
632
- navigate,
633
- dispatchNavLinkTarget("/chat"),
634
- );
628
+ {collapsed ? (
629
+ <Tooltip>
630
+ <TooltipTrigger asChild>
631
+ <NavLink
632
+ to={dispatchNavLinkTarget(item.to)}
633
+ onClick={(event) => {
634
+ if (
635
+ item.id === "chat" &&
636
+ localPathname !== "/chat" &&
637
+ !event.metaKey &&
638
+ !event.ctrlKey &&
639
+ !event.shiftKey &&
640
+ !event.altKey
641
+ ) {
642
+ event.preventDefault();
643
+ navigateWithAgentChatViewTransition(
644
+ navigate,
645
+ dispatchNavLinkTarget("/chat"),
646
+ );
647
+ onNavigate?.();
648
+ return;
649
+ }
650
+ onNavigate?.();
651
+ }}
652
+ aria-label={label}
653
+ className={({ isActive }) => {
654
+ const active = isActive || itemMatchesLocalPath;
655
+ return cn(
656
+ "flex h-10 w-10 items-center justify-center rounded-md text-sm",
657
+ active
658
+ ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
659
+ : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
660
+ );
661
+ }}
662
+ >
663
+ {Icon ? (
664
+ <Icon size={16} className="shrink-0" />
665
+ ) : (
666
+ <span className="h-4 w-4 shrink-0" aria-hidden="true" />
667
+ )}
668
+ </NavLink>
669
+ </TooltipTrigger>
670
+ <TooltipContent side="right">{label}</TooltipContent>
671
+ </Tooltip>
672
+ ) : (
673
+ <NavLink
674
+ to={dispatchNavLinkTarget(item.to)}
675
+ onClick={(event) => {
676
+ if (
677
+ item.id === "chat" &&
678
+ localPathname !== "/chat" &&
679
+ !event.metaKey &&
680
+ !event.ctrlKey &&
681
+ !event.shiftKey &&
682
+ !event.altKey
683
+ ) {
684
+ event.preventDefault();
685
+ navigateWithAgentChatViewTransition(
686
+ navigate,
687
+ dispatchNavLinkTarget("/chat"),
688
+ );
689
+ onNavigate?.();
690
+ return;
691
+ }
635
692
  onNavigate?.();
636
- return;
637
- }
638
- onNavigate?.();
639
- }}
640
- className={({ isActive }) => {
641
- const active = isActive || itemMatchesLocalPath;
642
- return cn(
643
- "flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
644
- active
645
- ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
646
- : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
647
- );
648
- }}
649
- >
650
- {Icon ? (
651
- <Icon size={16} className="shrink-0" />
652
- ) : (
653
- <span className="h-4 w-4 shrink-0" aria-hidden="true" />
654
- )}
655
- <span className="truncate">{label}</span>
656
- </NavLink>
657
- {item.id === "chat" && itemMatchesLocalPath ? (
693
+ }}
694
+ className={({ isActive }) => {
695
+ const active = isActive || itemMatchesLocalPath;
696
+ return cn(
697
+ "flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm",
698
+ active
699
+ ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground"
700
+ : "text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
701
+ );
702
+ }}
703
+ >
704
+ {Icon ? (
705
+ <Icon size={16} className="shrink-0" />
706
+ ) : (
707
+ <span className="h-4 w-4 shrink-0" aria-hidden="true" />
708
+ )}
709
+ <span className="truncate">{label}</span>
710
+ </NavLink>
711
+ )}
712
+ {!collapsed && item.id === "chat" && itemMatchesLocalPath ? (
658
713
  <DispatchChatsSection onNavigate={onNavigate} />
659
714
  ) : null}
660
715
  </li>
@@ -663,70 +718,118 @@ export function NavContent({
663
718
 
664
719
  return (
665
720
  <>
666
- <div className="border-b px-4 py-3">
667
- <div className="flex items-center gap-3">
668
- <div className="flex h-9 w-9 items-center justify-center rounded-xl border bg-card text-foreground">
669
- <img
670
- src={appPath("/agent-native-icon-light.svg")}
671
- alt=""
672
- aria-hidden="true"
673
- className="block h-4 w-auto shrink-0 dark:hidden"
674
- />
675
- <img
676
- src={appPath("/agent-native-icon-dark.svg")}
677
- alt=""
678
- aria-hidden="true"
679
- className="hidden h-4 w-auto shrink-0 dark:block"
680
- />
681
- </div>
682
- <div className="min-w-0">
683
- <div className="truncate text-sm font-semibold text-foreground">
684
- {workspaceLabel ?? "Dispatch"}
685
- </div>
686
- <div className="truncate text-xs text-muted-foreground">
687
- {workspaceLabel
688
- ? t("dispatch.sidebar.workspaceSubtitle", {
689
- count: ws?.appCount ?? 0,
690
- })
691
- : t("dispatch.sidebar.workspaceControlPlane")}
692
- </div>
693
- </div>
721
+ <div className={cn("border-b py-3", collapsed ? "px-1" : "px-4")}>
722
+ <div
723
+ className={cn(
724
+ "flex items-center",
725
+ collapsed ? "justify-center" : "gap-3",
726
+ )}
727
+ >
728
+ {!collapsed && (
729
+ <>
730
+ <div className="flex h-9 w-9 items-center justify-center rounded-xl border bg-card text-foreground">
731
+ <img
732
+ src={appPath("/agent-native-icon-light.svg")}
733
+ alt=""
734
+ aria-hidden="true"
735
+ className="block h-4 w-auto shrink-0 dark:hidden"
736
+ />
737
+ <img
738
+ src={appPath("/agent-native-icon-dark.svg")}
739
+ alt=""
740
+ aria-hidden="true"
741
+ className="hidden h-4 w-auto shrink-0 dark:block"
742
+ />
743
+ </div>
744
+ <div className="min-w-0 flex-1">
745
+ <div className="truncate text-sm font-semibold text-foreground">
746
+ {workspaceLabel ?? "Dispatch"}
747
+ </div>
748
+ <div className="truncate text-xs text-muted-foreground">
749
+ {workspaceLabel
750
+ ? t("dispatch.sidebar.workspaceSubtitle", {
751
+ count: ws?.appCount ?? 0,
752
+ })
753
+ : t("dispatch.sidebar.workspaceControlPlane")}
754
+ </div>
755
+ </div>
756
+ </>
757
+ )}
758
+ {collapsible ? (
759
+ <Tooltip>
760
+ <TooltipTrigger asChild>
761
+ <button
762
+ type="button"
763
+ onClick={() => onCollapsedChange?.(!collapsed)}
764
+ aria-label={
765
+ collapsed
766
+ ? t("sidebar.expandSidebar")
767
+ : t("sidebar.collapseSidebar")
768
+ }
769
+ className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
770
+ >
771
+ {collapsed ? (
772
+ <IconLayoutSidebarLeftExpand className="h-4 w-4 rtl:-scale-x-100" />
773
+ ) : (
774
+ <IconLayoutSidebarLeftCollapse className="h-4 w-4 rtl:-scale-x-100" />
775
+ )}
776
+ </button>
777
+ </TooltipTrigger>
778
+ <TooltipContent side="right">
779
+ {collapsed
780
+ ? t("sidebar.expandSidebar")
781
+ : t("sidebar.collapseSidebar")}
782
+ </TooltipContent>
783
+ </Tooltip>
784
+ ) : null}
694
785
  </div>
695
786
  </div>
696
787
 
697
788
  <div className="flex min-h-0 flex-1 flex-col overflow-y-auto">
698
- <nav className="px-2 py-3">
699
- <ul className="space-y-0.5">{primaryNavItems.map(renderNavItem)}</ul>
789
+ <nav className={cn("py-3", collapsed ? "px-1" : "px-2")}>
790
+ <ul
791
+ className={cn(
792
+ "space-y-0.5",
793
+ collapsed && "flex flex-col items-center",
794
+ )}
795
+ >
796
+ {(collapsed
797
+ ? [...primaryNavItems, ...operationsNavItems]
798
+ : primaryNavItems
799
+ ).map(renderNavItem)}
800
+ </ul>
700
801
  </nav>
701
802
 
702
- <div className="mt-auto shrink-0">
703
- <div className="border-t px-2 py-2">
704
- <details className="group" open={operationsOpen}>
705
- <summary className="flex h-8 cursor-pointer list-none items-center justify-between rounded-md px-2 text-xs font-medium uppercase text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [&::-webkit-details-marker]:hidden">
706
- <span>{t("dispatch.nav.operations")}</span>
707
- <IconChevronDown
708
- size={14}
709
- className="transition-transform group-open:rotate-180"
710
- />
711
- </summary>
712
- <ul className="mt-1 space-y-0.5">
713
- {operationsNavItems.map(renderNavItem)}
714
- </ul>
715
- </details>
716
- </div>
803
+ {!collapsed ? (
804
+ <div className="mt-auto shrink-0">
805
+ <div className="px-2 py-2">
806
+ <details className="group" open={operationsOpen}>
807
+ <summary className="flex h-8 cursor-pointer list-none items-center justify-between rounded-md px-2 text-xs font-medium uppercase text-sidebar-foreground/50 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground [&::-webkit-details-marker]:hidden">
808
+ <span>{t("dispatch.nav.operations")}</span>
809
+ <IconChevronDown
810
+ size={14}
811
+ className="transition-transform group-open:rotate-180"
812
+ />
813
+ </summary>
814
+ <ul className="mt-1 space-y-0.5">
815
+ {operationsNavItems.map(renderNavItem)}
816
+ </ul>
817
+ </details>
818
+ </div>
717
819
 
718
- <div className="border-t px-2 py-1">
719
- <ExtensionsSidebarSection />
720
- </div>
820
+ <div className="px-2 py-1">
821
+ <ExtensionsSidebarSection />
822
+ </div>
721
823
 
722
- <div className="border-t px-3 py-2">
723
- <OrgSwitcher />
724
- </div>
824
+ <div className="px-3 py-2">
825
+ <OrgSwitcher />
826
+ </div>
725
827
 
726
- <div className="border-t px-3 py-2">
727
- <FeedbackButton />
828
+ <div className="px-3 py-2">
829
+ <FeedbackButton />
830
+ </div>
728
831
  </div>
729
- </div>
832
+ ) : null}
730
833
  </div>
731
834
  </>
732
835
  );
@@ -743,6 +846,14 @@ export function Layout({
743
846
  const location = useLocation();
744
847
  const navigate = useNavigate();
745
848
  const [mobileOpen, setMobileOpen] = useState(false);
849
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
850
+ if (typeof window === "undefined") return false;
851
+ try {
852
+ return window.localStorage.getItem(SIDEBAR_COLLAPSE_KEY) === "1";
853
+ } catch {
854
+ return false;
855
+ }
856
+ });
746
857
  const localPathname = localDispatchPath(location.pathname);
747
858
  const isChatRoute =
748
859
  localPathname === "/chat" || localPathname.startsWith("/chat/");
@@ -758,6 +869,18 @@ export function Layout({
758
869
  };
759
870
  useAgentChatHomeHandoffLinks(chatHandoffLinkOptions);
760
871
 
872
+ useEffect(() => {
873
+ if (typeof window === "undefined") return;
874
+ try {
875
+ window.localStorage.setItem(
876
+ SIDEBAR_COLLAPSE_KEY,
877
+ sidebarCollapsed ? "1" : "0",
878
+ );
879
+ } catch {
880
+ // Ignore storage failures; the in-memory preference still works.
881
+ }
882
+ }, [sidebarCollapsed]);
883
+
761
884
  if (CHROMELESS_PATHS.some((path) => localPathname === path)) {
762
885
  return <>{children}</>;
763
886
  }
@@ -817,8 +940,19 @@ export function Layout({
817
940
  return (
818
941
  <HeaderActionsProvider>
819
942
  <div className="agent-layout-shell flex h-screen w-full overflow-hidden bg-background">
820
- <aside className="agent-layout-left-drawer hidden lg:flex w-64 shrink-0 flex-col border-e bg-sidebar text-sidebar-foreground">
821
- <NavContent extensions={extensions} />
943
+ <aside
944
+ data-collapsed={sidebarCollapsed ? "true" : "false"}
945
+ className={cn(
946
+ "agent-layout-left-drawer hidden shrink-0 flex-col border-e bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-out lg:flex",
947
+ sidebarCollapsed ? "w-12" : "w-64",
948
+ )}
949
+ >
950
+ <NavContent
951
+ extensions={extensions}
952
+ collapsed={sidebarCollapsed}
953
+ collapsible
954
+ onCollapsedChange={setSidebarCollapsed}
955
+ />
822
956
  </aside>
823
957
 
824
958
  <Sheet open={mobileOpen} onOpenChange={setMobileOpen}>
@@ -835,6 +969,7 @@ export function Layout({
835
969
  <div className="flex h-full w-full flex-col">
836
970
  <NavContent
837
971
  extensions={extensions}
972
+ collapsed={false}
838
973
  onNavigate={() => setMobileOpen(false)}
839
974
  />
840
975
  </div>
@@ -3061,7 +3061,7 @@ export async function rejectDreamProposal(
3061
3061
  function cronLooksValid(schedule: string): boolean {
3062
3062
  const parts = schedule.trim().split(/\s+/);
3063
3063
  if (parts.length !== 5) return false;
3064
- return parts.every((part) => /^[\d*/,\-]+$/.test(part));
3064
+ return parts.every((part) => /^[\d*/,-]+$/.test(part));
3065
3065
  }
3066
3066
 
3067
3067
  function dreamJobBody(settings: Omit<DreamSettings, "scope" | "scopeId">) {