@agent-native/toolkit 0.201.1-nightly-20261005213104 → 0.201.1

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 (104) hide show
  1. package/agent-native.eject.json +1 -0
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +21 -13
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/context.d.ts +15 -1
  6. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/context.js +30 -4
  8. package/dist/app/agentkit/react/context.js.map +1 -1
  9. package/dist/app/agentkit/react/styles.css +11 -7
  10. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  11. package/dist/app/chat/AgentKitAssistantChat.js +9 -4
  12. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  13. package/dist/app/chat/FileStorageSetupPopover.d.ts.map +1 -1
  14. package/dist/app/chat/FileStorageSetupPopover.js +3 -2
  15. package/dist/app/chat/FileStorageSetupPopover.js.map +1 -1
  16. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  17. package/dist/app/i18n/catalogs/ar-SA.js +11 -5
  18. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  19. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  20. package/dist/app/i18n/catalogs/de-DE.js +11 -5
  21. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  22. package/dist/app/i18n/catalogs/en-US.d.ts +11 -5
  23. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  24. package/dist/app/i18n/catalogs/en-US.js +11 -5
  25. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  26. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  27. package/dist/app/i18n/catalogs/es-ES.js +11 -5
  28. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  29. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  30. package/dist/app/i18n/catalogs/fr-FR.js +11 -5
  31. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  32. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  33. package/dist/app/i18n/catalogs/hi-IN.js +11 -5
  34. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  35. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  36. package/dist/app/i18n/catalogs/ja-JP.js +11 -5
  37. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  38. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  39. package/dist/app/i18n/catalogs/ko-KR.js +11 -5
  40. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  41. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  42. package/dist/app/i18n/catalogs/pt-BR.js +11 -5
  43. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  44. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  45. package/dist/app/i18n/catalogs/zh-CN.js +11 -5
  46. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  47. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  48. package/dist/app/i18n/catalogs/zh-TW.js +11 -5
  49. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  50. package/dist/app/onboarding/BuilderIncludedServices.d.ts +23 -0
  51. package/dist/app/onboarding/BuilderIncludedServices.d.ts.map +1 -0
  52. package/dist/app/onboarding/BuilderIncludedServices.js +67 -0
  53. package/dist/app/onboarding/BuilderIncludedServices.js.map +1 -0
  54. package/dist/app/onboarding/FirstRunOnboarding.d.ts.map +1 -1
  55. package/dist/app/onboarding/FirstRunOnboarding.js +10 -29
  56. package/dist/app/onboarding/FirstRunOnboarding.js.map +1 -1
  57. package/dist/app/settings/BuilderConnectPopover.d.ts +1 -2
  58. package/dist/app/settings/BuilderConnectPopover.d.ts.map +1 -1
  59. package/dist/app/settings/BuilderConnectPopover.js +35 -21
  60. package/dist/app/settings/BuilderConnectPopover.js.map +1 -1
  61. package/dist/app/settings/deferred-builder-connect-popover.d.ts.map +1 -1
  62. package/dist/app/settings/deferred-builder-connect-popover.js +28 -1
  63. package/dist/app/settings/deferred-builder-connect-popover.js.map +1 -1
  64. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  65. package/dist/app/settings/useBuilderStatus.js +8 -1
  66. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  67. package/dist/app/styles/tokens/agent-kit.css +23 -11
  68. package/dist/composer/MessageQueueDrawer.d.ts +5 -0
  69. package/dist/composer/MessageQueueDrawer.d.ts.map +1 -1
  70. package/dist/composer/MessageQueueDrawer.js +11 -5
  71. package/dist/composer/MessageQueueDrawer.js.map +1 -1
  72. package/package.json +5 -4
  73. package/src/app/agentkit/react/components.interaction.spec.tsx +13 -5
  74. package/src/app/agentkit/react/components.tsx +24 -14
  75. package/src/app/agentkit/react/context.tsx +53 -6
  76. package/src/app/agentkit/react/styles.css +11 -7
  77. package/src/app/chat/AgentKitAssistantChat.spec.tsx +35 -16
  78. package/src/app/chat/AgentKitAssistantChat.tsx +13 -4
  79. package/src/app/chat/FileStorageSetupPopover.spec.tsx +129 -0
  80. package/src/app/chat/FileStorageSetupPopover.tsx +10 -3
  81. package/src/app/i18n/catalogs/ar-SA.ts +14 -5
  82. package/src/app/i18n/catalogs/de-DE.ts +15 -5
  83. package/src/app/i18n/catalogs/en-US.ts +14 -5
  84. package/src/app/i18n/catalogs/es-ES.ts +14 -5
  85. package/src/app/i18n/catalogs/fr-FR.ts +15 -5
  86. package/src/app/i18n/catalogs/hi-IN.ts +14 -5
  87. package/src/app/i18n/catalogs/ja-JP.ts +14 -5
  88. package/src/app/i18n/catalogs/ko-KR.ts +14 -5
  89. package/src/app/i18n/catalogs/pt-BR.ts +15 -5
  90. package/src/app/i18n/catalogs/zh-CN.ts +13 -5
  91. package/src/app/i18n/catalogs/zh-TW.ts +13 -5
  92. package/src/app/onboarding/BuilderIncludedServices.tsx +263 -0
  93. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +23 -8
  94. package/src/app/onboarding/FirstRunOnboarding.tsx +22 -112
  95. package/src/app/settings/BuilderConnectPopover.spec.tsx +175 -6
  96. package/src/app/settings/BuilderConnectPopover.tsx +87 -67
  97. package/src/app/settings/deferred-builder-connect-popover.tsx +46 -0
  98. package/src/app/settings/model/ModelSettingsPage.spec.tsx +8 -24
  99. package/src/app/settings/useBuilderStatus.spec.tsx +5 -0
  100. package/src/app/settings/useBuilderStatus.ts +8 -0
  101. package/src/app/styles/agent-native.spec.ts +5 -8
  102. package/src/app/styles/tokens/agent-kit.css +23 -11
  103. package/src/composer/MessageQueueDrawer.spec.tsx +20 -7
  104. package/src/composer/MessageQueueDrawer.tsx +47 -21
@@ -1,15 +1,55 @@
1
1
  // @vitest-environment happy-dom
2
2
 
3
+ import { TooltipProvider } from "@agent-native/toolkit/ui/tooltip";
3
4
  import React, { act } from "react";
4
5
  import { createRoot, type Root } from "react-dom/client";
5
6
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
7
 
7
8
  vi.mock("@agent-native/core/client/i18n", () => ({
8
- useT: () => (_key: string, options?: { defaultValue?: string }) =>
9
- options?.defaultValue ?? _key,
9
+ useT:
10
+ () => (_key: string, options?: { defaultValue?: string; count?: number }) =>
11
+ (options?.defaultValue ?? _key).replace(
12
+ "{{count}}",
13
+ String(options?.count ?? ""),
14
+ ),
15
+ }));
16
+
17
+ vi.mock("@agent-native/core/client/onboarding/use-onboarding", () => ({
18
+ useOnboarding: () => ({
19
+ loading: false,
20
+ error: null,
21
+ profile: {
22
+ capabilities: [
23
+ {
24
+ id: "llm",
25
+ label: "AI model",
26
+ required: true,
27
+ builderIncluded: true,
28
+ service: "model",
29
+ keySummary: "Connect your own AI model",
30
+ why: "The agent uses a language model.",
31
+ },
32
+ {
33
+ id: "design-system-intelligence",
34
+ label: "Design system intelligence",
35
+ required: false,
36
+ builderIncluded: true,
37
+ service: "design-system-intelligence",
38
+ },
39
+ {
40
+ id: "background-agents",
41
+ label: "Background agents",
42
+ required: false,
43
+ builderIncluded: true,
44
+ service: "background-agents",
45
+ },
46
+ ],
47
+ },
48
+ }),
10
49
  }));
11
50
 
12
51
  import { BuilderConnectPopover } from "./BuilderConnectPopover.js";
52
+ import { DeferredBuilderConnectPopover } from "./deferred-builder-connect-popover.js";
13
53
 
14
54
  let container: HTMLDivElement;
15
55
  let root: Root;
@@ -46,7 +86,7 @@ function click(element: HTMLElement) {
46
86
  }
47
87
 
48
88
  function render(node: React.ReactElement) {
49
- act(() => root.render(node));
89
+ act(() => root.render(React.createElement(TooltipProvider, null, node)));
50
90
  }
51
91
 
52
92
  function trigger() {
@@ -57,6 +97,23 @@ function trigger() {
57
97
  }
58
98
 
59
99
  describe("BuilderConnectPopover before the status read resolves", () => {
100
+ it("starts the OAuth path from the original trigger click", () => {
101
+ const flow = {
102
+ connecting: false,
103
+ start: vi.fn(),
104
+ statusResolved: true,
105
+ agentNativeProvisioningEnabled: false,
106
+ accountExists: false,
107
+ };
108
+
109
+ render(
110
+ React.createElement(DeferredBuilderConnectPopover, { flow }, trigger()),
111
+ );
112
+ click(connectButton());
113
+
114
+ expect(flow.start).toHaveBeenCalledWith({ provisionAccount: false });
115
+ });
116
+
60
117
  it("uses existing-account sign-in when provisioning is unavailable", () => {
61
118
  const onConnect = vi.fn();
62
119
  const flow = {
@@ -182,6 +239,120 @@ describe("BuilderConnectPopover before the status read resolves", () => {
182
239
  expect(onConnect).toHaveBeenCalledWith(true);
183
240
  });
184
241
 
242
+ it("shows one sign-in action when Builder reports an existing account", () => {
243
+ const onConnect = vi.fn();
244
+ const flow = {
245
+ connecting: false,
246
+ start: vi.fn(),
247
+ statusResolved: true,
248
+ agentNativeProvisioningEnabled: true,
249
+ accountExists: true,
250
+ };
251
+
252
+ render(
253
+ React.createElement(
254
+ BuilderConnectPopover,
255
+ {
256
+ flow,
257
+ onConnect,
258
+ contentTestId: "consent",
259
+ primaryTestId: "create",
260
+ secondaryTestId: "sign-in",
261
+ },
262
+ trigger(),
263
+ ),
264
+ );
265
+ expect(document.querySelector("[data-testid='consent']")).toBeNull();
266
+ click(connectButton());
267
+
268
+ const consent = document.querySelector("[data-testid='consent']");
269
+ expect(consent?.textContent).toContain("builderActivateTitle");
270
+ expect(consent?.querySelector("p[role='status']")?.textContent).toBe(
271
+ "You already have a Builder.io account",
272
+ );
273
+ expect(consent?.textContent).toContain("Included free");
274
+ expect(consent?.textContent).not.toContain(
275
+ "Included free with a Builder.io account",
276
+ );
277
+ expect(consent?.textContent).toContain("60 monthly Agent Credits");
278
+ expect(consent?.textContent).toContain("+ 2 more services");
279
+ const services = consent?.querySelector<HTMLElement>(
280
+ "[data-testid='builder-included-services']",
281
+ );
282
+ const servicesToggle = services?.querySelector<HTMLButtonElement>(
283
+ "button[aria-expanded]",
284
+ );
285
+ expect(servicesToggle?.getAttribute("aria-expanded")).toBe("false");
286
+ expect(
287
+ Array.from(
288
+ servicesToggle?.querySelectorAll(":scope > span > span") ?? [],
289
+ ).map((line) => line.textContent),
290
+ ).toEqual([
291
+ "Included free",
292
+ "60 monthly Agent Credits",
293
+ "+ 2 more services",
294
+ ]);
295
+ expect(
296
+ consent?.querySelector<HTMLButtonElement>("[data-testid='create']")
297
+ ?.textContent,
298
+ ).toBe("agentChat.onboarding.builderSignInWithAccount");
299
+ expect(
300
+ consent?.querySelector<HTMLButtonElement>("[data-testid='sign-in']"),
301
+ ).toBeNull();
302
+ expect(
303
+ consent?.querySelector<HTMLButtonElement>("[data-testid='create'] svg"),
304
+ ).toBeNull();
305
+ expect(consent?.textContent).not.toContain("agentChat.auth.logIn");
306
+
307
+ click(servicesToggle!);
308
+ expect(servicesToggle?.getAttribute("aria-expanded")).toBe("true");
309
+ click(consent?.querySelector("[data-testid='create']") as HTMLElement);
310
+ expect(onConnect).toHaveBeenCalledWith(false);
311
+ });
312
+
313
+ it("reopens the same account choices when activation finds an existing account", () => {
314
+ const flow = {
315
+ connecting: false,
316
+ start: vi.fn(),
317
+ statusResolved: true,
318
+ agentNativeProvisioningEnabled: true,
319
+ accountExists: false,
320
+ };
321
+ const props = {
322
+ flow,
323
+ contentTestId: "consent",
324
+ primaryTestId: "create",
325
+ secondaryTestId: "sign-in",
326
+ };
327
+
328
+ render(React.createElement(BuilderConnectPopover, props, trigger()));
329
+ click(connectButton());
330
+ click(
331
+ document.querySelector(
332
+ "[data-testid='consent'] [data-testid='create']",
333
+ ) as HTMLElement,
334
+ );
335
+ expect(flow.start).toHaveBeenCalledWith({ provisionAccount: true });
336
+
337
+ render(
338
+ React.createElement(
339
+ BuilderConnectPopover,
340
+ { ...props, flow: { ...flow, accountExists: true } },
341
+ trigger(),
342
+ ),
343
+ );
344
+
345
+ const consent = document.querySelector("[data-testid='consent']");
346
+ expect(consent?.querySelector("p[role='status']")?.textContent).toBe(
347
+ "You already have a Builder.io account",
348
+ );
349
+ expect(consent?.querySelector("[data-testid='create']")).not.toBeNull();
350
+ expect(consent?.querySelector("[data-testid='sign-in']")).toBeNull();
351
+
352
+ click(consent?.querySelector("[data-testid='create']") as HTMLElement);
353
+ expect(flow.start).toHaveBeenLastCalledWith({ provisionAccount: false });
354
+ });
355
+
185
356
  it("stacks the create and sign-in buttons together with the terms below them", () => {
186
357
  const flow = {
187
358
  connecting: false,
@@ -225,9 +396,7 @@ describe("BuilderConnectPopover before the status read resolves", () => {
225
396
  p.querySelector("a"),
226
397
  );
227
398
  expect(create && signIn && terms).toBeTruthy();
228
- expect(consent?.textContent).toContain(
229
- "Included free with a Builder.io account",
230
- );
399
+ expect(consent?.textContent).toContain("Included free");
231
400
  expect(consent?.textContent).toContain("60 monthly Agent Credits");
232
401
  expect(signIn?.parentElement).toBe(create?.parentElement);
233
402
  expect(create!.compareDocumentPosition(signIn!)).toBe(
@@ -1,4 +1,5 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
+ import { useOnboarding } from "@agent-native/core/client/onboarding/use-onboarding";
2
3
  import { Button } from "@agent-native/toolkit/ui/button";
3
4
  import {
4
5
  Popover,
@@ -6,9 +7,12 @@ import {
6
7
  PopoverTrigger,
7
8
  } from "@agent-native/toolkit/ui/popover";
8
9
  import { Spinner } from "@agent-native/toolkit/ui/spinner";
9
- import { IconArrowRight } from "@tabler/icons-react";
10
10
  import React, { useEffect, useRef, useState } from "react";
11
11
 
12
+ import {
13
+ BuilderIncludedBenefitsDisclosure,
14
+ getBuilderIncludedCapabilities,
15
+ } from "../onboarding/BuilderIncludedServices.js";
12
16
  import type { BuilderConnectFlow } from "./useBuilderStatus.js";
13
17
 
14
18
  type BuilderConnectTrigger = React.ReactElement<{
@@ -19,13 +23,12 @@ type BuilderConnectTrigger = React.ReactElement<{
19
23
 
20
24
  export interface BuilderConnectPopoverProps {
21
25
  flow: Pick<BuilderConnectFlow, "connecting" | "start"> & {
26
+ accountExists?: boolean;
22
27
  cancel?: BuilderConnectFlow["cancel"];
23
28
  agentNativeProvisioningEnabled?: boolean;
24
- accountExists?: boolean;
25
29
  retry?: () => boolean | void;
26
30
  statusResolved?: boolean;
27
31
  statusReadSettledCount?: number;
28
- canConnect?: BuilderConnectFlow["canConnect"];
29
32
  provisionAccount?: boolean;
30
33
  };
31
34
  children: BuilderConnectTrigger;
@@ -49,30 +52,31 @@ export function BuilderConnectPopover({
49
52
  }: BuilderConnectPopoverProps) {
50
53
  const t = useT();
51
54
  const [open, setOpen] = useState(false);
55
+ const provisioningAttemptRef = useRef(false);
52
56
  const capabilityResolved = flow.statusResolved === true;
53
57
  const showPopover =
54
58
  (defaultProvisionAccount && !capabilityResolved) ||
55
59
  (capabilityResolved && flow.agentNativeProvisioningEnabled === true);
56
- const accountExists = capabilityResolved && flow.accountExists;
57
- const initiatedByThisTriggerRef = useRef(false);
58
60
  const [queuedClick, setQueuedClick] = useState<{ settledAt: number } | null>(
59
61
  null,
60
62
  );
61
63
  const settledCount = flow.statusReadSettledCount ?? 0;
62
64
 
63
65
  useEffect(() => {
64
- if (accountExists && initiatedByThisTriggerRef.current) {
65
- initiatedByThisTriggerRef.current = false;
66
+ if (flow.accountExists && provisioningAttemptRef.current) {
67
+ provisioningAttemptRef.current = false;
66
68
  setOpen(true);
69
+ } else if (!flow.connecting) {
70
+ provisioningAttemptRef.current = false;
67
71
  }
68
- }, [accountExists]);
72
+ }, [flow.accountExists, flow.connecting]);
69
73
 
70
74
  const start = (provisionAccount?: boolean) => {
71
75
  const shouldProvision =
72
76
  provisionAccount ??
73
77
  (flow.agentNativeProvisioningEnabled === true &&
74
78
  (defaultProvisionAccount || flow.provisionAccount === true));
75
- initiatedByThisTriggerRef.current = true;
79
+ if (shouldProvision) provisioningAttemptRef.current = true;
76
80
  setOpen(false);
77
81
  if (onConnect) {
78
82
  onConnect(shouldProvision);
@@ -139,11 +143,11 @@ export function BuilderConnectPopover({
139
143
  if (!showPopover) {
140
144
  return cancelAction ? (
141
145
  <span className="inline-flex max-w-full items-center gap-2">
142
- {trigger}
146
+ <span className="contents">{trigger}</span>
143
147
  {cancelAction}
144
148
  </span>
145
149
  ) : (
146
- trigger
150
+ <span className="contents">{trigger}</span>
147
151
  );
148
152
  }
149
153
 
@@ -151,60 +155,50 @@ export function BuilderConnectPopover({
151
155
  <Popover open={open} onOpenChange={setOpen}>
152
156
  <PopoverTrigger asChild>{trigger}</PopoverTrigger>
153
157
  <PopoverContent
154
- align="end"
155
- side="bottom"
156
- sideOffset={-40}
158
+ align="center"
159
+ side="right"
160
+ sideOffset={8}
157
161
  aria-labelledby="builder-connect-popover-title"
158
162
  data-testid={contentTestId}
159
- className="z-[330] w-80 max-w-[calc(100vw-2rem)] p-3 text-left"
163
+ className="z-[330] max-h-[min(640px,calc(100dvh-2rem),var(--radix-popover-content-available-height))] w-80 max-w-[calc(100vw-2rem)] overflow-y-auto p-3 text-left"
160
164
  >
161
165
  <div className="space-y-2.5">
162
166
  <h2
163
167
  id="builder-connect-popover-title"
164
168
  className="text-sm font-semibold text-foreground"
165
169
  >
166
- {accountExists
167
- ? t("agentChat.onboarding.builderAccountExistsTitle")
168
- : t("agentChat.onboarding.builderActivateTitle")}
170
+ {t("agentChat.onboarding.builderActivateTitle")}
169
171
  </h2>
170
- <p className="text-xs leading-5 text-muted-foreground">
171
- {accountExists
172
- ? t("agentChat.onboarding.builderAccountExistsDescription")
173
- : flow.canConnect?.org
174
- ? t("agentChat.onboarding.builderOrgActivationDescription")
175
- : t("agentChat.onboarding.builderActivationDescription")}
172
+ <p
173
+ role={flow.accountExists ? "status" : undefined}
174
+ className="text-xs leading-5 text-muted-foreground"
175
+ >
176
+ {flow.accountExists
177
+ ? t("agentChat.onboarding.builderAccountExistsTitle", {
178
+ defaultValue: "You already have a Builder.io account",
179
+ })
180
+ : t("agentChat.onboarding.builderActivationDescription", {
181
+ defaultValue:
182
+ "Create or connect a Builder.io account in one click to get free credits.",
183
+ })}
176
184
  </p>
177
- <div className="flex flex-col gap-1 rounded-[10px] bg-emerald-50 px-4 py-3 dark:bg-emerald-950/30">
178
- <p className="text-[13px] font-semibold text-foreground">
179
- {t("agentChat.onboarding.builderIncludedFreeWithAccount", {
180
- defaultValue: "Included free with a Builder.io account",
181
- })}
182
- </p>
183
- <p className="text-xs font-medium text-emerald-700 dark:text-emerald-400">
184
- {t("agentChat.onboarding.builderMonthlyCredits", {
185
- defaultValue: "60 monthly Agent Credits",
186
- })}
187
- </p>
188
- </div>
185
+ <BuilderConnectIncludedServices />
189
186
  <div className="flex flex-col gap-2">
190
187
  <Button
191
188
  type="button"
192
189
  data-testid={primaryTestId}
193
190
  className="w-full"
194
- onClick={() => start(accountExists ? false : true)}
191
+ onClick={() => start(!flow.accountExists)}
195
192
  disabled={flow.connecting}
196
193
  >
197
194
  {flow.connecting ? <Spinner aria-hidden /> : null}
198
- {accountExists
199
- ? t("agentChat.auth.logIn")
195
+ {flow.accountExists
196
+ ? t("agentChat.onboarding.builderSignInWithAccount")
200
197
  : flow.connecting
201
198
  ? t("agentChat.onboarding.builderActivating")
202
199
  : t("agentChat.onboarding.builderCreateAndActivate")}
203
- {!accountExists && !flow.connecting ? (
204
- <IconArrowRight aria-hidden />
205
- ) : null}
206
200
  </Button>
207
- {!accountExists && (
201
+ {!flow.accountExists ? (
208
202
  <Button
209
203
  type="button"
210
204
  variant="secondary"
@@ -215,31 +209,29 @@ export function BuilderConnectPopover({
215
209
  >
216
210
  {t("agentChat.onboarding.builderExistingAccount")}
217
211
  </Button>
218
- )}
212
+ ) : null}
219
213
  </div>
220
- {!accountExists && (
221
- <p className="text-[11px] leading-4 text-muted-foreground">
222
- {t("agentChat.onboarding.builderConsentPrefix")}{" "}
223
- <a
224
- href="https://www.builder.io/legal/terms"
225
- target="_blank"
226
- rel="noreferrer"
227
- className="underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
228
- >
229
- {t("agentChat.onboarding.builderTerms")}
230
- </a>{" "}
231
- {t("agentChat.onboarding.builderConsentAnd")}{" "}
232
- <a
233
- href="https://www.builder.io/legal/privacy"
234
- target="_blank"
235
- rel="noreferrer"
236
- className="underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
237
- >
238
- {t("agentChat.onboarding.builderPrivacy")}
239
- </a>
240
- .
241
- </p>
242
- )}
214
+ <p className="text-[11px] leading-4 text-muted-foreground">
215
+ {t("agentChat.onboarding.builderConsentPrefix")}{" "}
216
+ <a
217
+ href="https://www.builder.io/legal/terms"
218
+ target="_blank"
219
+ rel="noreferrer"
220
+ className="underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
221
+ >
222
+ {t("agentChat.onboarding.builderTerms")}
223
+ </a>{" "}
224
+ {t("agentChat.onboarding.builderConsentAnd")}{" "}
225
+ <a
226
+ href="https://www.builder.io/legal/privacy"
227
+ target="_blank"
228
+ rel="noreferrer"
229
+ className="underline underline-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
230
+ >
231
+ {t("agentChat.onboarding.builderPrivacy")}
232
+ </a>
233
+ .
234
+ </p>
243
235
  </div>
244
236
  </PopoverContent>
245
237
  </Popover>
@@ -254,3 +246,31 @@ export function BuilderConnectPopover({
254
246
  popover
255
247
  );
256
248
  }
249
+
250
+ function BuilderConnectIncludedServices() {
251
+ const t = useT();
252
+ const { profile, loading, error } = useOnboarding();
253
+ const capabilities = profile
254
+ ? getBuilderIncludedCapabilities(profile.capabilities)
255
+ : [];
256
+
257
+ return (
258
+ <BuilderIncludedBenefitsDisclosure
259
+ capabilities={capabilities}
260
+ includedLabel={t("agentChat.onboarding.builderIncludedFree", {
261
+ defaultValue: "Included free",
262
+ })}
263
+ creditsLabel={t("agentChat.onboarding.builderMonthlyCredits", {
264
+ defaultValue: "60 monthly Agent Credits",
265
+ })}
266
+ loading={loading}
267
+ loadingLabel={t("agentChat.common.loading")}
268
+ error={
269
+ error || (!loading && !profile)
270
+ ? t("agentChat.common.chunkLoadFailed")
271
+ : null
272
+ }
273
+ testId="builder-included-services"
274
+ />
275
+ );
276
+ }
@@ -1,3 +1,5 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import { Button } from "@agent-native/toolkit/ui/button";
1
3
  import { cloneElement, lazy, Suspense } from "react";
2
4
 
3
5
  import { LazyChunkErrorBoundary } from "../shared/LazyChunkErrorBoundary.js";
@@ -15,6 +17,50 @@ export { LazyChunkRetryFallback } from "../shared/LazyChunkRetryFallback.js";
15
17
  export function DeferredBuilderConnectPopover(
16
18
  props: BuilderConnectPopoverProps,
17
19
  ) {
20
+ const t = useT();
21
+ if (
22
+ props.flow.statusResolved === true &&
23
+ props.flow.agentNativeProvisioningEnabled !== true
24
+ ) {
25
+ // Keep OAuth in the trusted click and avoid loading popover UI for sign-in-only flows.
26
+ const trigger = cloneElement(props.children, {
27
+ "aria-busy": undefined,
28
+ onClick: (event) => {
29
+ if (props.flow.connecting) {
30
+ event.preventDefault();
31
+ event.stopPropagation();
32
+ return;
33
+ }
34
+ props.children.props.onClick?.(event);
35
+ props.onTriggerClick?.(event);
36
+ if (event.defaultPrevented) return;
37
+ if (props.onConnect) props.onConnect(false);
38
+ else props.flow.start({ provisionAccount: false });
39
+ },
40
+ });
41
+ const cancelAction =
42
+ props.flow.connecting && props.flow.cancel ? (
43
+ <Button
44
+ type="button"
45
+ variant="ghost"
46
+ size="sm"
47
+ className="shrink-0"
48
+ onClick={props.flow.cancel}
49
+ >
50
+ {t("common.cancel")}
51
+ </Button>
52
+ ) : null;
53
+
54
+ return cancelAction ? (
55
+ <span className="inline-flex max-w-full items-center gap-2">
56
+ <span className="contents">{trigger}</span>
57
+ {cancelAction}
58
+ </span>
59
+ ) : (
60
+ <span className="contents">{trigger}</span>
61
+ );
62
+ }
63
+
18
64
  return (
19
65
  <LazyChunkErrorBoundary fallback={<LazyChunkRetryFallback />}>
20
66
  <Suspense
@@ -85,14 +85,11 @@ vi.mock("@agent-native/core/client/oauth-popup", () => ({
85
85
  }));
86
86
 
87
87
  vi.mock("../deferred-builder-connect-popover.js", () => ({
88
- // The real popover asks whether to create an account, then calls onConnect.
89
88
  DeferredBuilderConnectPopover: ({
90
89
  children,
91
- onConnect,
92
90
  }: {
93
91
  children: React.ReactElement<{ onClick?: () => void }>;
94
- onConnect?: (provisionAccount: boolean) => void;
95
- }) => React.cloneElement(children, { onClick: () => onConnect?.(false) }),
92
+ }) => React.cloneElement(children, { onClick: () => {} }),
96
93
  }));
97
94
 
98
95
  vi.mock("../shell/context.js", () => ({
@@ -388,15 +385,9 @@ describe("ModelSettingsPage", () => {
388
385
  expect(buttons(row("provider-personal-builder"))).toEqual([
389
386
  "Use Builder.io",
390
387
  ]);
391
- act(() => {
392
- (
393
- row("provider-personal-builder").querySelector("button") as HTMLElement
394
- ).click();
395
- });
396
- expect(state.builder.start).toHaveBeenCalledWith({
397
- provisionAccount: false,
398
- scope: "personal",
399
- });
388
+ expect(
389
+ row("provider-personal-builder").querySelector("button")?.disabled,
390
+ ).toBe(false);
400
391
  expect(row("provider-personal-anthropic").textContent).toContain(
401
392
  "••••1234 · 2 models",
402
393
  );
@@ -669,17 +660,10 @@ describe("ModelSettingsPage", () => {
669
660
  expect(
670
661
  document.querySelector('[data-testid="provider-dialog"]'),
671
662
  ).not.toBeNull();
672
- act(() => {
673
- (
674
- [...empty.querySelectorAll("button")].find(
675
- (button) => button.textContent === "Use Builder.io",
676
- ) as HTMLElement
677
- ).click();
678
- });
679
- expect(state.builder.start).toHaveBeenCalledWith({
680
- provisionAccount: false,
681
- scope: "org",
682
- });
663
+ const builderButton = [...empty.querySelectorAll("button")].find(
664
+ (button) => button.textContent === "Use Builder.io",
665
+ );
666
+ expect(builderButton?.disabled).toBe(false);
683
667
  });
684
668
 
685
669
  it("tells a restricted member with no provider to ask an admin", async () => {
@@ -265,6 +265,11 @@ describe("useBuilderStatus", () => {
265
265
 
266
266
  expect(fetchMock).toHaveBeenCalledWith(
267
267
  "/_agent-native/connection-status/builder",
268
+ expect.objectContaining({
269
+ headers: {
270
+ "x-agent-native-preview-origin": "http://localhost:3000",
271
+ },
272
+ }),
268
273
  );
269
274
  });
270
275
 
@@ -127,6 +127,11 @@ export function useBuilderStatus({
127
127
  try {
128
128
  const res = await fetch(
129
129
  agentNativePath("/_agent-native/connection-status/builder"),
130
+ {
131
+ headers: {
132
+ "x-agent-native-preview-origin": window.location.origin,
133
+ },
134
+ },
130
135
  );
131
136
  if (!res.ok) {
132
137
  keepLastGoodStatus(`Builder status unavailable (${res.status})`);
@@ -926,6 +931,9 @@ export function useBuilderConnectFlow(
926
931
  );
927
932
  }
928
933
  const r = await fetch(statusUrl.href, {
934
+ headers: {
935
+ "x-agent-native-preview-origin": window.location.origin,
936
+ },
929
937
  signal: signal ?? ownController?.signal,
930
938
  });
931
939
  if (!r.ok) return null;
@@ -261,34 +261,31 @@ describe("agent-native shell surface tokens", () => {
261
261
  /\.agent-sidebar-panel\[data-agent-sidebar-animation="drawer"\][\s\S]*?box-shadow: var\(--agent-kit-drawer-elevation\);/s,
262
262
  );
263
263
  expect(tokens).toMatch(
264
- /\.dark\s*\{[\s\S]*?--agent-kit-shadow-color: var\(--background\);[\s\S]*?--agent-kit-drawer-elevation:/s,
264
+ /:is\(\.dark, :root\[data-theme="dark"\]:not\(\.light\)\)\s*\{[\s\S]*?--agent-kit-shadow-color: var\(--background\);[\s\S]*?--agent-kit-drawer-elevation:/s,
265
265
  );
266
266
  expect(css).not.toContain("hsl(var(--foreground) / 0.07)");
267
267
  expect(css).not.toContain("hsl(var(--foreground) / 0.12)");
268
268
  expect(css).not.toContain("hsl(var(--agent-shadow)");
269
269
  expect(tokens).toContain("--agent-kit-shadow-color: var(--foreground);");
270
270
  expect(tokens).toMatch(
271
- /\.dark\s*\{[\s\S]*?--agent-kit-shadow-color: var\(--background\);[\s\S]*?--agent-kit-composer-elevation:[\s\S]*?hsl\(var\(--agent-kit-shadow-color\) \/ 0\.22\)/s,
271
+ /:is\(\.dark, :root\[data-theme="dark"\]:not\(\.light\)\)\s*\{[\s\S]*?--agent-kit-shadow-color: var\(--background\);[\s\S]*?--agent-kit-composer-elevation:[\s\S]*?hsl\(var\(--agent-kit-shadow-color\) \/ 0\.22\)/s,
272
272
  );
273
273
  expect(css).toMatch(
274
274
  /::view-transition-old\(agent-native-sidebar-drawer\),\s*::view-transition-new\(agent-native-sidebar-drawer\)[\s\S]*?height: 100%;/s,
275
275
  );
276
276
  });
277
277
 
278
- it("gives the light composer a quiet boundary without a dark-mode highlight", () => {
278
+ it("gives the composer a quiet boundary in both themes", () => {
279
279
  const tokens = readFileSync(
280
280
  new URL("./tokens/agent-kit.css", import.meta.url),
281
281
  { encoding: "utf8" },
282
282
  );
283
283
 
284
284
  expect(tokens).toMatch(
285
- /:root\s*\{[\s\S]*?--agent-kit-composer-border-opacity: 0\.82;[\s\S]*?--agent-kit-composer-focus-border-opacity: 1;/s,
285
+ /:root\s*\{[\s\S]*?--agent-kit-composer-border-mix: 82%;[\s\S]*?--agent-kit-composer-focus-border-mix: 100%;[\s\S]*?--agent-kit-composer-border-color: color-mix\([\s\S]*?--agent-kit-composer-focus-border-color: color-mix\(/s,
286
286
  );
287
287
  expect(tokens).toMatch(
288
- /\.dark\s*\{[\s\S]*?--agent-kit-composer-border-opacity: 0;[\s\S]*?--agent-kit-composer-focus-border-opacity: 0;/s,
289
- );
290
- expect(tokens).toMatch(
291
- /\.dark\s*\{[\s\S]*?--agent-kit-composer-border-color: transparent;[\s\S]*?--agent-kit-composer-focus-border-color: transparent;/s,
288
+ /:is\(\.dark, :root\[data-theme="dark"\]:not\(\.light\)\)\s*\{[\s\S]*?--agent-kit-composer-border-mix: 40%;[\s\S]*?--agent-kit-composer-focus-border-mix: 65%;[\s\S]*?--agent-kit-composer-border-color: color-mix\([\s\S]*?--agent-kit-composer-focus-border-color: color-mix\(/s,
292
289
  );
293
290
  });
294
291