@agent-native/toolkit 0.207.0-nightly-20261011153008 → 0.207.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.
Files changed (118) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/app/auth/AuthPage.d.ts.map +1 -1
  3. package/dist/app/auth/AuthPage.js +24 -55
  4. package/dist/app/auth/AuthPage.js.map +1 -1
  5. package/dist/app/chat/chat/run-recovery.d.ts.map +1 -1
  6. package/dist/app/chat/chat/run-recovery.js +21 -5
  7. package/dist/app/chat/chat/run-recovery.js.map +1 -1
  8. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.d.ts.map +1 -1
  9. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js +3 -2
  10. package/dist/app/chat/chat-first/ChatFirstAgentActivityPanel.js.map +1 -1
  11. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  12. package/dist/app/i18n/catalogs/ar-SA.js +10 -2
  13. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  14. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  15. package/dist/app/i18n/catalogs/de-DE.js +10 -2
  16. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  17. package/dist/app/i18n/catalogs/en-US.d.ts +10 -2
  18. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  19. package/dist/app/i18n/catalogs/en-US.js +10 -2
  20. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  21. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  22. package/dist/app/i18n/catalogs/es-ES.js +10 -2
  23. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  24. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  25. package/dist/app/i18n/catalogs/fr-FR.js +10 -2
  26. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  27. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  28. package/dist/app/i18n/catalogs/hi-IN.js +10 -2
  29. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  30. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  31. package/dist/app/i18n/catalogs/ja-JP.js +10 -2
  32. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  33. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  34. package/dist/app/i18n/catalogs/ko-KR.js +10 -2
  35. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  36. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  37. package/dist/app/i18n/catalogs/pt-BR.js +10 -2
  38. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  39. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  40. package/dist/app/i18n/catalogs/zh-CN.js +10 -2
  41. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  42. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  43. package/dist/app/i18n/catalogs/zh-TW.js +10 -2
  44. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  45. package/dist/app/onboarding/OnboardingBanner.d.ts.map +1 -1
  46. package/dist/app/onboarding/OnboardingBanner.js +37 -3
  47. package/dist/app/onboarding/OnboardingBanner.js.map +1 -1
  48. package/dist/app/onboarding/OnboardingPanel.d.ts.map +1 -1
  49. package/dist/app/onboarding/OnboardingPanel.js +49 -11
  50. package/dist/app/onboarding/OnboardingPanel.js.map +1 -1
  51. package/dist/app/progress/RunsTray.d.ts.map +1 -1
  52. package/dist/app/progress/RunsTray.js +136 -17
  53. package/dist/app/progress/RunsTray.js.map +1 -1
  54. package/dist/app/settings/BuilderConnectPopover.d.ts +1 -0
  55. package/dist/app/settings/BuilderConnectPopover.d.ts.map +1 -1
  56. package/dist/app/settings/BuilderConnectPopover.js +21 -5
  57. package/dist/app/settings/BuilderConnectPopover.js.map +1 -1
  58. package/dist/app/settings/shell/pages/audit.d.ts.map +1 -1
  59. package/dist/app/settings/shell/pages/audit.js +3 -3
  60. package/dist/app/settings/shell/pages/audit.js.map +1 -1
  61. package/dist/app/settings/useBuilderStatus.d.ts +1 -0
  62. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  63. package/dist/app/settings/useBuilderStatus.js +1 -0
  64. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  65. package/dist/app/shared/AgentMark.d.ts +6 -0
  66. package/dist/app/shared/AgentMark.d.ts.map +1 -0
  67. package/dist/app/shared/AgentMark.js +5 -0
  68. package/dist/app/shared/AgentMark.js.map +1 -0
  69. package/dist/app/shared/index.d.ts +1 -0
  70. package/dist/app/shared/index.d.ts.map +1 -1
  71. package/dist/app/shared/index.js +1 -0
  72. package/dist/app/shared/index.js.map +1 -1
  73. package/dist/app/sharing/ShareButton.d.ts +1 -0
  74. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  75. package/dist/app/sharing/ShareButton.js +2 -2
  76. package/dist/app/sharing/ShareButton.js.map +1 -1
  77. package/dist/app/styles/agent-native.css +1 -0
  78. package/dist/app-shell/sidebar.d.ts.map +1 -1
  79. package/dist/app-shell/sidebar.js +3 -3
  80. package/dist/app-shell/sidebar.js.map +1 -1
  81. package/dist/sharing/ShareControls.d.ts +1 -0
  82. package/dist/sharing/ShareControls.d.ts.map +1 -1
  83. package/dist/sharing/ShareControls.js +5 -2
  84. package/dist/sharing/ShareControls.js.map +1 -1
  85. package/package.json +4 -4
  86. package/src/app/auth/AuthPage.attribution.spec.tsx +268 -0
  87. package/src/app/auth/AuthPage.tsx +27 -54
  88. package/src/app/chat/chat/run-recovery.spec.tsx +76 -0
  89. package/src/app/chat/chat/run-recovery.tsx +101 -23
  90. package/src/app/chat/chat-first/ChatFirstAgentActivityPanel.tsx +11 -5
  91. package/src/app/i18n/catalogs/ar-SA.ts +17 -2
  92. package/src/app/i18n/catalogs/de-DE.ts +18 -2
  93. package/src/app/i18n/catalogs/en-US.ts +17 -2
  94. package/src/app/i18n/catalogs/es-ES.ts +17 -2
  95. package/src/app/i18n/catalogs/fr-FR.ts +18 -2
  96. package/src/app/i18n/catalogs/hi-IN.ts +17 -2
  97. package/src/app/i18n/catalogs/ja-JP.ts +17 -2
  98. package/src/app/i18n/catalogs/ko-KR.ts +17 -2
  99. package/src/app/i18n/catalogs/pt-BR.ts +17 -2
  100. package/src/app/i18n/catalogs/zh-CN.ts +16 -2
  101. package/src/app/i18n/catalogs/zh-TW.ts +16 -2
  102. package/src/app/onboarding/OnboardingBanner.tsx +70 -5
  103. package/src/app/onboarding/OnboardingPanel.save-scope.spec.tsx +143 -2
  104. package/src/app/onboarding/OnboardingPanel.tsx +72 -5
  105. package/src/app/progress/RunsTray.spec.tsx +577 -0
  106. package/src/app/progress/RunsTray.tsx +253 -51
  107. package/src/app/settings/BuilderConnectPopover.spec.tsx +39 -1
  108. package/src/app/settings/BuilderConnectPopover.tsx +27 -4
  109. package/src/app/settings/shell/pages/audit.tsx +7 -3
  110. package/src/app/settings/useBuilderStatus.spec.tsx +21 -1
  111. package/src/app/settings/useBuilderStatus.ts +2 -0
  112. package/src/app/shared/AgentMark.tsx +34 -0
  113. package/src/app/shared/index.ts +1 -0
  114. package/src/app/sharing/ShareButton.tsx +3 -0
  115. package/src/app/styles/agent-native.css +1 -0
  116. package/src/app-shell/sidebar.tsx +29 -21
  117. package/src/sharing/ShareControls.spec.tsx +36 -0
  118. package/src/sharing/ShareControls.tsx +17 -2
@@ -1,3 +1,7 @@
1
+ import {
2
+ requestAgentChatThreadOpen,
3
+ requestAgentTaskOpen,
4
+ } from "@agent-native/core/client/agent-chat";
1
5
  import { agentNativePath } from "@agent-native/core/client/api-path";
2
6
  import { useFormatters, useT } from "@agent-native/core/client/i18n";
3
7
  import { useChangeVersion } from "@agent-native/core/client/use-change-version";
@@ -12,6 +16,7 @@ import {
12
16
  Tooltip as DesignSystemTooltip,
13
17
  } from "@agent-native/toolkit/design-system";
14
18
  import {
19
+ DropdownMenuItem,
15
20
  DropdownMenuSub,
16
21
  DropdownMenuSubContent,
17
22
  DropdownMenuSubTrigger,
@@ -27,7 +32,7 @@ import {
27
32
  IconSubtask,
28
33
  IconX,
29
34
  } from "@tabler/icons-react";
30
- import { useCallback, useEffect, useMemo, useState } from "react";
35
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
31
36
 
32
37
  type AgentRunDto = AgentRun;
33
38
  type BackgroundAgentRunDto = {
@@ -288,6 +293,8 @@ export function RunsTray({
288
293
  showRecent,
289
294
  });
290
295
 
296
+ const openRunThread = onOpenThread ?? openAgentRunThread;
297
+
291
298
  if (!hasRuns && hideWhenIdle) return null;
292
299
 
293
300
  const trigger = (
@@ -372,7 +379,8 @@ export function RunsTray({
372
379
  terminalCount={terminalCount}
373
380
  onDismiss={dismissRun}
374
381
  onStop={stopRun}
375
- onOpenThread={onOpenThread}
382
+ onOpenThread={openRunThread}
383
+ insideDropdownMenu={false}
376
384
  />
377
385
  </DesignSystemPopover>
378
386
  );
@@ -390,6 +398,9 @@ export function RunsTrayMenuItem({
390
398
  >) {
391
399
  const t = useT();
392
400
  const [submenuOpen, setSubmenuOpen] = useState(false);
401
+ const triggerRef = useRef<HTMLDivElement>(null);
402
+ const keepOpenRef = useRef(false);
403
+ const keepOpenTimerRef = useRef<number | null>(null);
393
404
  const {
394
405
  runs,
395
406
  hasRuns,
@@ -408,23 +419,41 @@ export function RunsTrayMenuItem({
408
419
  showRecent,
409
420
  });
410
421
 
422
+ const openRunThread = onOpenThread ?? openAgentRunThread;
423
+ const keepMenuOpen = () => {
424
+ keepOpenRef.current = true;
425
+ window.clearTimeout(keepOpenTimerRef.current ?? undefined);
426
+ keepOpenTimerRef.current = window.setTimeout(() => {
427
+ keepOpenRef.current = false;
428
+ }, 250);
429
+ };
430
+ // Radix restores focus to the parent menu while these actions update rows.
431
+ const onFocusOutside = (event: Event) => {
432
+ const target = event.target;
433
+ if (
434
+ keepOpenRef.current &&
435
+ target instanceof HTMLElement &&
436
+ target.matches('[role="menu"][data-radix-menu-content]')
437
+ ) {
438
+ event.preventDefault();
439
+ keepOpenRef.current = false;
440
+ window.clearTimeout(keepOpenTimerRef.current ?? undefined);
441
+ }
442
+ };
443
+
444
+ useEffect(
445
+ () => () => window.clearTimeout(keepOpenTimerRef.current ?? undefined),
446
+ [],
447
+ );
448
+
411
449
  if (!hasRuns && hideWhenIdle) return null;
412
450
 
413
451
  return (
414
452
  <DropdownMenuSub open={submenuOpen} onOpenChange={setSubmenuOpen}>
415
453
  <DropdownMenuSubTrigger
454
+ ref={triggerRef}
416
455
  aria-label={t("runsTray.ariaAgentRuns", { label: triggerLabel })}
417
456
  className="cursor-pointer gap-2"
418
- onClick={(event) => {
419
- event.preventDefault();
420
- setSubmenuOpen(true);
421
- }}
422
- onKeyDown={(event) => {
423
- if (event.key === "Enter" || event.key === " ") {
424
- event.preventDefault();
425
- setSubmenuOpen(true);
426
- }
427
- }}
428
457
  >
429
458
  <TriggerIcon
430
459
  size={14}
@@ -453,6 +482,7 @@ export function RunsTrayMenuItem({
453
482
  <DropdownMenuSubContent
454
483
  sideOffset={6}
455
484
  className="an-runs-tray__menu w-80 max-w-[calc(100vw-24px)] p-0"
485
+ onFocusOutside={onFocusOutside}
456
486
  >
457
487
  <RunsTrayContent
458
488
  runs={runs}
@@ -461,7 +491,10 @@ export function RunsTrayMenuItem({
461
491
  terminalCount={terminalCount}
462
492
  onDismiss={dismissRun}
463
493
  onStop={stopRun}
464
- onOpenThread={onOpenThread}
494
+ onOpenThread={openRunThread}
495
+ onKeepMenuOpen={() => triggerRef.current?.focus()}
496
+ onActionStart={keepMenuOpen}
497
+ insideDropdownMenu
465
498
  />
466
499
  </DropdownMenuSubContent>
467
500
  </DropdownMenuSub>
@@ -476,6 +509,9 @@ function RunsTrayContent({
476
509
  onDismiss,
477
510
  onStop,
478
511
  onOpenThread,
512
+ onKeepMenuOpen,
513
+ onActionStart,
514
+ insideDropdownMenu,
479
515
  }: {
480
516
  runs: AgentRunDto[];
481
517
  hasRuns: boolean;
@@ -484,6 +520,9 @@ function RunsTrayContent({
484
520
  onDismiss: (runId: string) => void;
485
521
  onStop: (runId: string) => void;
486
522
  onOpenThread?: (threadId: string, run: AgentRunDto) => void;
523
+ onKeepMenuOpen?: () => void;
524
+ onActionStart?: () => void;
525
+ insideDropdownMenu: boolean;
487
526
  }) {
488
527
  const t = useT();
489
528
  return (
@@ -514,6 +553,9 @@ function RunsTrayContent({
514
553
  onDismiss={onDismiss}
515
554
  onStop={onStop}
516
555
  onOpenThread={onOpenThread}
556
+ onKeepMenuOpen={onKeepMenuOpen}
557
+ onActionStart={onActionStart}
558
+ insideDropdownMenu={insideDropdownMenu}
517
559
  />
518
560
  ))}
519
561
  </div>
@@ -619,6 +661,29 @@ function getRunThreadId(run: AgentRunDto): string | undefined {
619
661
  return match?.[1] ? decodeURIComponent(match[1]) : undefined;
620
662
  }
621
663
 
664
+ function openAgentRunThread(threadId: string, run: AgentRunDto): void {
665
+ const metadata = run.metadata ?? {};
666
+ const parentThreadId =
667
+ typeof metadata.parentThreadId === "string"
668
+ ? metadata.parentThreadId.trim()
669
+ : "";
670
+ const isAgentTeam =
671
+ metadata.kind === "agent-team" || metadata.source === "agent-teams";
672
+ if (isAgentTeam && parentThreadId && parentThreadId !== threadId) {
673
+ requestAgentTaskOpen({
674
+ threadId,
675
+ parentThreadId,
676
+ description:
677
+ typeof metadata.description === "string"
678
+ ? metadata.description
679
+ : run.title || "",
680
+ name: typeof metadata.name === "string" ? metadata.name : "",
681
+ });
682
+ return;
683
+ }
684
+ requestAgentChatThreadOpen({ threadId });
685
+ }
686
+
622
687
  function formatRunTime(
623
688
  run: AgentRunDto,
624
689
  t: ReturnType<typeof useT>,
@@ -676,11 +741,17 @@ function RunRow({
676
741
  onDismiss,
677
742
  onStop,
678
743
  onOpenThread,
744
+ onKeepMenuOpen,
745
+ onActionStart,
746
+ insideDropdownMenu,
679
747
  }: {
680
748
  run: AgentRunDto;
681
749
  onDismiss: (runId: string) => void;
682
750
  onStop: (runId: string) => void;
683
751
  onOpenThread?: (threadId: string, run: AgentRunDto) => void;
752
+ onKeepMenuOpen?: () => void;
753
+ onActionStart?: () => void;
754
+ insideDropdownMenu: boolean;
684
755
  }) {
685
756
  const t = useT();
686
757
  const formatters = useFormatters();
@@ -689,20 +760,166 @@ function RunRow({
689
760
  const isRunning = run.status === "running";
690
761
  const canStop = isRunning && (isAgentTeamRun(run) || isBackgroundRun(run));
691
762
  const backgroundStatus = getBackgroundStatus(run);
763
+ const actionButtonRef = useRef<HTMLButtonElement>(null);
764
+ const titleButtonRef = useRef<HTMLButtonElement>(null);
765
+ const pointerStartedOnRunRef = useRef<string | null>(null);
766
+ const pointerStartedOnActionRef = useRef(false);
767
+ const pointerStartedOnAnyActionRef = useRef(false);
768
+ const pointerReleasedOnActionRef = useRef(false);
769
+ const pointerReleasedOnSuppressedTitleRef = useRef(false);
770
+
771
+ useEffect(() => {
772
+ if (!insideDropdownMenu) return;
773
+ const isActionTarget = (target: EventTarget | null) =>
774
+ target instanceof Node &&
775
+ actionButtonRef.current?.contains(target) === true;
776
+ const getRunTarget = (target: EventTarget | null) =>
777
+ target instanceof Element
778
+ ? target.closest<HTMLElement>(
779
+ "[data-runs-tray-title], [data-runs-tray-action]",
780
+ )
781
+ : null;
782
+ const isTitleTarget = (target: EventTarget | null) =>
783
+ target instanceof Node &&
784
+ titleButtonRef.current?.contains(target) === true;
785
+ const recordPointerDown = (event: PointerEvent) => {
786
+ const runTarget = getRunTarget(event.target);
787
+ pointerStartedOnRunRef.current =
788
+ runTarget?.dataset.runsTrayTitle ??
789
+ runTarget?.dataset.runsTrayAction ??
790
+ null;
791
+ pointerStartedOnActionRef.current = isActionTarget(event.target);
792
+ pointerStartedOnAnyActionRef.current =
793
+ runTarget?.dataset.runsTrayAction !== undefined;
794
+ pointerReleasedOnActionRef.current = false;
795
+ pointerReleasedOnSuppressedTitleRef.current = false;
796
+ };
797
+ const recordPointerUp = (event: PointerEvent) => {
798
+ const pointerStartedOutsideThisRun =
799
+ pointerStartedOnRunRef.current !== run.id;
800
+ pointerReleasedOnActionRef.current = isActionTarget(event.target);
801
+ pointerReleasedOnSuppressedTitleRef.current =
802
+ isTitleTarget(event.target) &&
803
+ (pointerStartedOutsideThisRun || pointerStartedOnAnyActionRef.current);
804
+ window.setTimeout(() => {
805
+ pointerStartedOnRunRef.current = null;
806
+ pointerStartedOnActionRef.current = false;
807
+ pointerStartedOnAnyActionRef.current = false;
808
+ pointerReleasedOnActionRef.current = false;
809
+ pointerReleasedOnSuppressedTitleRef.current = false;
810
+ }, 0);
811
+ };
812
+ document.addEventListener("pointerdown", recordPointerDown, true);
813
+ document.addEventListener("pointerup", recordPointerUp, true);
814
+ return () => {
815
+ document.removeEventListener("pointerdown", recordPointerDown, true);
816
+ document.removeEventListener("pointerup", recordPointerUp, true);
817
+ };
818
+ }, [insideDropdownMenu]);
819
+
820
+ const titleButton =
821
+ threadId && onOpenThread ? (
822
+ <button
823
+ type="button"
824
+ className="group flex min-w-0 flex-1 flex-col text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
825
+ ref={insideDropdownMenu ? titleButtonRef : undefined}
826
+ data-runs-tray-title={insideDropdownMenu ? run.id : undefined}
827
+ onClick={() => {
828
+ if (pointerReleasedOnSuppressedTitleRef.current) return;
829
+ onOpenThread(threadId, run);
830
+ }}
831
+ >
832
+ <span className="inline-flex min-w-0 max-w-full items-center gap-1.5">
833
+ <span className="truncate font-medium text-foreground group-hover:underline">
834
+ {run.title}
835
+ </span>
836
+ <IconExternalLink size={12} className="shrink-0" aria-hidden />
837
+ <span className="sr-only">{t("runsTray.open")}</span>
838
+ </span>
839
+ {run.step ? (
840
+ <span className="mt-0.5 max-w-full truncate text-xs text-muted-foreground">
841
+ {run.step}
842
+ </span>
843
+ ) : null}
844
+ </button>
845
+ ) : null;
846
+ const actionButton =
847
+ canStop || !isRunning ? (
848
+ <button
849
+ type="button"
850
+ ref={insideDropdownMenu ? actionButtonRef : undefined}
851
+ data-runs-tray-action={insideDropdownMenu ? run.id : undefined}
852
+ aria-label={
853
+ canStop
854
+ ? t("runsTray.stopRun", { title: run.title })
855
+ : t("runsTray.hideRun", { title: run.title })
856
+ }
857
+ className={cn(
858
+ "inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded text-muted-foreground hover:bg-accent/60",
859
+ canStop ? "hover:text-destructive" : "hover:text-foreground",
860
+ )}
861
+ onPointerMove={onActionStart}
862
+ onPointerDown={onActionStart}
863
+ onClick={(event) => {
864
+ const isPointerActivation =
865
+ event.detail > 0 || pointerReleasedOnActionRef.current;
866
+ if (
867
+ insideDropdownMenu &&
868
+ isPointerActivation &&
869
+ !pointerStartedOnActionRef.current
870
+ ) {
871
+ return;
872
+ }
873
+ pointerStartedOnActionRef.current = false;
874
+ pointerReleasedOnActionRef.current = false;
875
+ onActionStart?.();
876
+ if (canStop) {
877
+ onStop(run.id);
878
+ } else {
879
+ onKeepMenuOpen?.();
880
+ onDismiss(run.id);
881
+ }
882
+ }}
883
+ >
884
+ {canStop ? (
885
+ <IconPlayerStop size={13} aria-hidden />
886
+ ) : (
887
+ <IconX size={13} aria-hidden />
888
+ )}
889
+ </button>
890
+ ) : null;
692
891
 
693
892
  return (
694
893
  <div className="flex flex-col gap-1.5 px-3 py-2.5 text-sm">
695
894
  <div className="flex items-start justify-between gap-2">
696
- <div className="min-w-0 flex-1">
697
- <div className="truncate font-medium text-foreground">
698
- {run.title}
699
- </div>
700
- {run.step ? (
701
- <div className="mt-0.5 truncate text-xs text-muted-foreground">
702
- {run.step}
895
+ {titleButton ? (
896
+ insideDropdownMenu ? (
897
+ <DropdownMenuItem
898
+ asChild
899
+ className="min-w-0 flex-1 justify-start p-0"
900
+ onSelect={(event) => {
901
+ if (pointerReleasedOnSuppressedTitleRef.current) {
902
+ event.preventDefault();
903
+ }
904
+ }}
905
+ >
906
+ {titleButton}
907
+ </DropdownMenuItem>
908
+ ) : (
909
+ titleButton
910
+ )
911
+ ) : (
912
+ <div className="min-w-0 flex-1">
913
+ <div className="truncate font-medium text-foreground">
914
+ {run.title}
703
915
  </div>
704
- ) : null}
705
- </div>
916
+ {run.step ? (
917
+ <div className="mt-0.5 truncate text-xs text-muted-foreground">
918
+ {run.step}
919
+ </div>
920
+ ) : null}
921
+ </div>
922
+ )}
706
923
  <StatusPill status={run.status} backgroundStatus={backgroundStatus} />
707
924
  </div>
708
925
  {run.percent != null || isRunning ? (
@@ -732,36 +949,21 @@ function RunRow({
732
949
  {formatRunTime(run, t, formatDate)}
733
950
  </span>
734
951
  <div className="flex shrink-0 items-center gap-1">
735
- {threadId && onOpenThread ? (
736
- <button
737
- type="button"
738
- className="inline-flex h-6 items-center gap-1 rounded px-1.5 text-[11px] font-medium text-muted-foreground hover:bg-accent/60 hover:text-foreground"
739
- onClick={() => onOpenThread(threadId, run)}
740
- >
741
- {t("runsTray.open")}
742
- <IconExternalLink size={12} aria-hidden />
743
- </button>
744
- ) : null}
745
- {canStop ? (
746
- <button
747
- type="button"
748
- aria-label={t("runsTray.stopRun", { title: run.title })}
749
- className="inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded text-muted-foreground hover:bg-accent/60 hover:text-destructive"
750
- onClick={() => onStop(run.id)}
751
- >
752
- <IconPlayerStop size={13} aria-hidden />
753
- </button>
754
- ) : null}
755
- {!isRunning ? (
756
- <button
757
- type="button"
758
- aria-label={t("runsTray.hideRun", { title: run.title })}
759
- className="inline-flex h-6 w-6 cursor-pointer items-center justify-center rounded text-muted-foreground hover:bg-accent/60 hover:text-foreground"
760
- onClick={() => onDismiss(run.id)}
952
+ {actionButton && insideDropdownMenu ? (
953
+ <DropdownMenuItem
954
+ asChild
955
+ className="h-6 w-6 justify-center p-0"
956
+ onSelect={(event) => {
957
+ event.preventDefault();
958
+ onActionStart?.();
959
+ onKeepMenuOpen?.();
960
+ }}
761
961
  >
762
- <IconX size={13} aria-hidden />
763
- </button>
764
- ) : null}
962
+ {actionButton}
963
+ </DropdownMenuItem>
964
+ ) : (
965
+ actionButton
966
+ )}
765
967
  </div>
766
968
  </div>
767
969
  </div>
@@ -143,6 +143,9 @@ describe("BuilderConnectPopover", () => {
143
143
  consent?.querySelector<HTMLButtonElement>("[data-testid='create']")
144
144
  ?.disabled,
145
145
  ).toBe(true);
146
+ expect(consent?.textContent).toContain(
147
+ "New account creation isn't enabled for this app.",
148
+ );
146
149
 
147
150
  click(consent?.querySelector("[data-testid='create']") as HTMLElement);
148
151
  expect(onConnect).not.toHaveBeenCalled();
@@ -187,6 +190,41 @@ describe("BuilderConnectPopover", () => {
187
190
  expect(flow.start).not.toHaveBeenCalled();
188
191
  });
189
192
 
193
+ it("shows a resolved state when status checks are disabled", async () => {
194
+ const flow = {
195
+ connecting: false,
196
+ statusCheckEnabled: false,
197
+ statusResolved: false,
198
+ agentNativeProvisioningEnabled: false,
199
+ start: vi.fn(),
200
+ };
201
+
202
+ render(
203
+ React.createElement(
204
+ DeferredBuilderConnectPopover,
205
+ {
206
+ flow,
207
+ contentTestId: "consent",
208
+ primaryTestId: "create",
209
+ secondaryTestId: "sign-in",
210
+ },
211
+ trigger(),
212
+ ),
213
+ );
214
+ click(connectButton());
215
+ await finishLazyLoad();
216
+
217
+ const consent = document.querySelector("[data-testid='consent']");
218
+ expect(consent?.textContent).toContain(
219
+ "New account activation isn't available in this mode.",
220
+ );
221
+ expect(consent?.textContent).not.toContain("Checking whether");
222
+ expect(
223
+ consent?.querySelector<HTMLButtonElement>("[data-testid='create']")
224
+ ?.disabled,
225
+ ).toBe(true);
226
+ });
227
+
190
228
  it("offers a retry after a Builder status read fails", () => {
191
229
  const retry = vi.fn(() => true);
192
230
  const flow = {
@@ -371,7 +409,7 @@ describe("BuilderConnectPopover", () => {
371
409
 
372
410
  const consent = document.querySelector("[data-testid='consent']");
373
411
  expect(consent?.textContent).toContain(
374
- "Create or connect a Builder.io account in one click to get free credits.",
412
+ "Create a new account to activate free credits, or continue with an existing account.",
375
413
  );
376
414
  expect(consent?.textContent).toContain(
377
415
  "You already have a Builder.io account",
@@ -32,6 +32,7 @@ type BuilderConnectChoiceFlow = Pick<BuilderConnectFlow, "connecting"> & {
32
32
  error?: string | null;
33
33
  errorKind?: BuilderConnectErrorKind | null;
34
34
  retry?: () => boolean | void;
35
+ statusCheckEnabled?: boolean;
35
36
  statusReadSettledCount?: number;
36
37
  statusResolved?: boolean;
37
38
  };
@@ -92,6 +93,7 @@ export function BuilderConnectChoicePanel({
92
93
  })}
93
94
  </h2>
94
95
  <p
96
+ id="builder-connect-popover-description"
95
97
  className="text-xs leading-5 text-muted-foreground"
96
98
  role={flow.connecting ? "status" : undefined}
97
99
  aria-live={flow.connecting ? "polite" : undefined}
@@ -101,10 +103,30 @@ export function BuilderConnectChoicePanel({
101
103
  defaultValue:
102
104
  "Creating your Builder.io account and activating free credits.",
103
105
  })
104
- : t("agentChat.onboarding.builderActivationDescription", {
105
- defaultValue:
106
- "Create or connect a Builder.io account in one click to get free credits.",
107
- })}
106
+ : canProvisionAccount
107
+ ? t("agentChat.onboarding.builderActivationDescription", {
108
+ defaultValue:
109
+ "Create a new account to activate free credits, or continue with an existing account.",
110
+ })
111
+ : statusReadFailed
112
+ ? t("agentChat.onboarding.builderActivationCheckFailed", {
113
+ defaultValue:
114
+ "Couldn't check whether this app can create new accounts.",
115
+ })
116
+ : flow.statusCheckEnabled === false
117
+ ? t("agentChat.onboarding.builderActivationUnavailableInMode", {
118
+ defaultValue:
119
+ "New account activation isn't available in this mode. You can still connect an existing account.",
120
+ })
121
+ : flow.statusResolved === false
122
+ ? t("agentChat.onboarding.builderActivationChecking", {
123
+ defaultValue:
124
+ "Checking whether new account activation is available.",
125
+ })
126
+ : t("agentChat.onboarding.builderActivationUnavailable", {
127
+ defaultValue:
128
+ "New account creation isn't enabled for this app. You can still connect an existing account.",
129
+ })}
108
130
  </p>
109
131
  {flow.accountExists ? (
110
132
  <div
@@ -160,6 +182,7 @@ export function BuilderConnectChoicePanel({
160
182
  type="button"
161
183
  data-testid={primaryTestId}
162
184
  className="w-full"
185
+ aria-describedby="builder-connect-popover-description"
163
186
  onClick={onCreateAndActivate}
164
187
  disabled={flow.connecting || !canProvisionAccount}
165
188
  >
@@ -31,10 +31,11 @@ import {
31
31
  SelectValue,
32
32
  } from "@agent-native/toolkit/ui/select";
33
33
  import { cn } from "@agent-native/toolkit/utils";
34
- import { IconHistory, IconSettings, IconSparkles } from "@tabler/icons-react";
34
+ import { IconHistory, IconSettings } from "@tabler/icons-react";
35
35
  import { useInfiniteQuery } from "@tanstack/react-query";
36
36
  import { useEffect, useState, type ReactNode } from "react";
37
37
 
38
+ import { AgentMark } from "../../../shared/AgentMark.js";
38
39
  import { SettingsGroup } from "../../SettingsRow.js";
39
40
 
40
41
  export const AUDIT_PAGE_SIZE = 25;
@@ -99,10 +100,13 @@ function initials(email: string): string {
99
100
 
100
101
  function ActorMark({ event }: { event: AuditEvent }) {
101
102
  if (event.actorKind === "agent" || event.actorKind === "system") {
102
- const Icon = event.actorKind === "agent" ? IconSparkles : IconSettings;
103
103
  return (
104
104
  <span className="flex size-8 shrink-0 items-center justify-center rounded-full border border-border bg-background text-muted-foreground">
105
- <Icon className="size-4" aria-hidden="true" />
105
+ {event.actorKind === "agent" ? (
106
+ <AgentMark className="size-4" />
107
+ ) : (
108
+ <IconSettings className="size-4" aria-hidden="true" />
109
+ )}
106
110
  </span>
107
111
  );
108
112
  }
@@ -106,6 +106,9 @@ function BuilderConnectProbeContent({
106
106
  {flow.statusResolved ? "resolved" : "unresolved"}{" "}
107
107
  {flow.accountExists ? "account-exists" : "no-account-exists"}
108
108
  </output>
109
+ <output data-testid="status-check-enabled">
110
+ {flow.statusCheckEnabled ? "enabled" : "disabled"}
111
+ </output>
109
112
  <output data-testid="credential-source">
110
113
  {flow.credentialSource ?? "none"}
111
114
  </output>
@@ -324,6 +327,21 @@ describe("useBuilderStatus", () => {
324
327
  );
325
328
  });
326
329
 
330
+ it("distinguishes a disabled status check from one still loading", async () => {
331
+ const fetchMock = vi.fn();
332
+ vi.stubGlobal("fetch", fetchMock);
333
+
334
+ await act(async () => {
335
+ root.render(<BuilderConnectProbe enabled={false} />);
336
+ });
337
+
338
+ expect(
339
+ container.querySelector('[data-testid="status-check-enabled"]')
340
+ ?.textContent,
341
+ ).toBe("disabled");
342
+ expect(fetchMock).not.toHaveBeenCalled();
343
+ });
344
+
327
345
  it("keeps the last good Builder status when a refresh fails", async () => {
328
346
  vi.stubGlobal(
329
347
  "fetch",
@@ -2580,7 +2598,9 @@ describe("useBuilderConnectFlow", () => {
2580
2598
  document.querySelectorAll<HTMLButtonElement>(
2581
2599
  "[data-radix-popper-content-wrapper] button",
2582
2600
  ),
2583
- ).find((button) => button.textContent?.includes("Use Builder.io"));
2601
+ ).find((button) =>
2602
+ button.textContent?.includes("I have a Builder.io account"),
2603
+ );
2584
2604
  expect(existingAccountAction).toBeDefined();
2585
2605
 
2586
2606
  await act(async () => existingAccountAction?.click());
@@ -249,6 +249,7 @@ export type BuilderConnectErrorKind = "status-read" | "connection" | "launch";
249
249
  export interface BuilderConnectFlow {
250
250
  configured: boolean;
251
251
  provisionAccount?: boolean;
252
+ statusCheckEnabled: boolean;
252
253
  statusResolved: boolean;
253
254
  statusReadSettledCount: number;
254
255
  envManaged: boolean;
@@ -2092,6 +2093,7 @@ export function useBuilderConnectFlow(
2092
2093
  configured,
2093
2094
  provisionAccount,
2094
2095
  codeChangeConfigured,
2096
+ statusCheckEnabled: enabled,
2095
2097
  statusResolved,
2096
2098
  statusReadSettledCount,
2097
2099
  envManaged,
@@ -0,0 +1,34 @@
1
+ import type { SVGProps } from "react";
2
+
3
+ export interface AgentMarkProps extends SVGProps<SVGSVGElement> {
4
+ size?: number | string;
5
+ }
6
+
7
+ export function AgentMark({ size = 24, className, ...props }: AgentMarkProps) {
8
+ return (
9
+ <svg
10
+ data-agent-mark
11
+ xmlns="http://www.w3.org/2000/svg"
12
+ width={size}
13
+ height={size}
14
+ viewBox="0 0 24 24"
15
+ fill="none"
16
+ className={className}
17
+ aria-hidden="true"
18
+ focusable="false"
19
+ {...props}
20
+ >
21
+ <path
22
+ d="M18.4 5.2a9.15 9.15 0 1 0 1.8 2.4"
23
+ stroke="currentColor"
24
+ strokeWidth="1.7"
25
+ strokeLinecap="round"
26
+ />
27
+ <circle cx="18.55" cy="4.65" r="1.45" fill="currentColor" />
28
+ <path
29
+ d="m12 6.8 1.7 3.5 3.5 1.7-3.5 1.7-1.7 3.5-1.7-3.5-3.5-1.7 3.5-1.7L12 6.8Z"
30
+ fill="currentColor"
31
+ />
32
+ </svg>
33
+ );
34
+ }
@@ -1,6 +1,7 @@
1
1
  export * from "./AboutAgentNativeDialog.js";
2
2
  export * from "./AppShellSkeleton.js";
3
3
  export * from "./AgentNativeIcon.js";
4
+ export * from "./AgentMark.js";
4
5
  export * from "./AppSidebar.js";
5
6
  export * from "./AppearancePicker.js";
6
7
  export * from "./ClientOnly.js";