@agent-native/toolkit 0.201.0 → 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 (109) 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/resources/McpConnectionSuggestion.d.ts.map +1 -1
  58. package/dist/app/resources/McpConnectionSuggestion.js +55 -21
  59. package/dist/app/resources/McpConnectionSuggestion.js.map +1 -1
  60. package/dist/app/settings/BuilderConnectPopover.d.ts +1 -2
  61. package/dist/app/settings/BuilderConnectPopover.d.ts.map +1 -1
  62. package/dist/app/settings/BuilderConnectPopover.js +35 -21
  63. package/dist/app/settings/BuilderConnectPopover.js.map +1 -1
  64. package/dist/app/settings/deferred-builder-connect-popover.d.ts.map +1 -1
  65. package/dist/app/settings/deferred-builder-connect-popover.js +28 -1
  66. package/dist/app/settings/deferred-builder-connect-popover.js.map +1 -1
  67. package/dist/app/settings/useBuilderStatus.d.ts.map +1 -1
  68. package/dist/app/settings/useBuilderStatus.js +8 -1
  69. package/dist/app/settings/useBuilderStatus.js.map +1 -1
  70. package/dist/app/styles/tokens/agent-kit.css +23 -11
  71. package/dist/composer/MessageQueueDrawer.d.ts +5 -0
  72. package/dist/composer/MessageQueueDrawer.d.ts.map +1 -1
  73. package/dist/composer/MessageQueueDrawer.js +11 -5
  74. package/dist/composer/MessageQueueDrawer.js.map +1 -1
  75. package/package.json +4 -3
  76. package/src/app/agentkit/react/components.interaction.spec.tsx +13 -5
  77. package/src/app/agentkit/react/components.tsx +24 -14
  78. package/src/app/agentkit/react/context.tsx +53 -6
  79. package/src/app/agentkit/react/styles.css +11 -7
  80. package/src/app/chat/AgentKitAssistantChat.spec.tsx +35 -16
  81. package/src/app/chat/AgentKitAssistantChat.tsx +13 -4
  82. package/src/app/chat/FileStorageSetupPopover.spec.tsx +129 -0
  83. package/src/app/chat/FileStorageSetupPopover.tsx +10 -3
  84. package/src/app/i18n/catalogs/ar-SA.ts +14 -5
  85. package/src/app/i18n/catalogs/de-DE.ts +15 -5
  86. package/src/app/i18n/catalogs/en-US.ts +14 -5
  87. package/src/app/i18n/catalogs/es-ES.ts +14 -5
  88. package/src/app/i18n/catalogs/fr-FR.ts +15 -5
  89. package/src/app/i18n/catalogs/hi-IN.ts +14 -5
  90. package/src/app/i18n/catalogs/ja-JP.ts +14 -5
  91. package/src/app/i18n/catalogs/ko-KR.ts +14 -5
  92. package/src/app/i18n/catalogs/pt-BR.ts +15 -5
  93. package/src/app/i18n/catalogs/zh-CN.ts +13 -5
  94. package/src/app/i18n/catalogs/zh-TW.ts +13 -5
  95. package/src/app/onboarding/BuilderIncludedServices.tsx +263 -0
  96. package/src/app/onboarding/FirstRunOnboarding.spec.tsx +23 -8
  97. package/src/app/onboarding/FirstRunOnboarding.tsx +22 -112
  98. package/src/app/resources/McpConnectionSuggestion.render.spec.tsx +54 -2
  99. package/src/app/resources/McpConnectionSuggestion.tsx +73 -25
  100. package/src/app/settings/BuilderConnectPopover.spec.tsx +175 -6
  101. package/src/app/settings/BuilderConnectPopover.tsx +87 -67
  102. package/src/app/settings/deferred-builder-connect-popover.tsx +46 -0
  103. package/src/app/settings/model/ModelSettingsPage.spec.tsx +8 -24
  104. package/src/app/settings/useBuilderStatus.spec.tsx +5 -0
  105. package/src/app/settings/useBuilderStatus.ts +8 -0
  106. package/src/app/styles/agent-native.spec.ts +5 -8
  107. package/src/app/styles/tokens/agent-kit.css +23 -11
  108. package/src/composer/MessageQueueDrawer.spec.tsx +20 -7
  109. package/src/composer/MessageQueueDrawer.tsx +47 -21
@@ -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
 
@@ -43,13 +43,17 @@
43
43
  --agent-kit-composer-block-padding-end: 1rem;
44
44
  --agent-kit-composer-min-height: 4.75rem;
45
45
  --agent-kit-composer-radius: 1.25rem;
46
- --agent-kit-composer-border-opacity: 0.82;
47
- --agent-kit-composer-focus-border-opacity: 1;
48
- --agent-kit-composer-border-color: hsl(
49
- var(--border) / var(--agent-kit-composer-border-opacity)
46
+ --agent-kit-composer-border-mix: 82%;
47
+ --agent-kit-composer-focus-border-mix: 100%;
48
+ --agent-kit-composer-border-color: color-mix(
49
+ in srgb,
50
+ hsl(var(--border)) var(--agent-kit-composer-border-mix),
51
+ transparent
50
52
  );
51
- --agent-kit-composer-focus-border-color: hsl(
52
- var(--border) / var(--agent-kit-composer-focus-border-opacity)
53
+ --agent-kit-composer-focus-border-color: color-mix(
54
+ in srgb,
55
+ hsl(var(--border)) var(--agent-kit-composer-focus-border-mix),
56
+ transparent
53
57
  );
54
58
  --agent-kit-composer-editor-padding-inline: 1rem;
55
59
  --agent-kit-composer-editor-padding-block-start: 0.875rem;
@@ -96,11 +100,19 @@
96
100
  }
97
101
  }
98
102
 
99
- .dark {
100
- --agent-kit-composer-border-opacity: 0;
101
- --agent-kit-composer-focus-border-opacity: 0;
102
- --agent-kit-composer-border-color: transparent;
103
- --agent-kit-composer-focus-border-color: transparent;
103
+ :is(.dark, :root[data-theme="dark"]:not(.light)) {
104
+ --agent-kit-composer-border-mix: 40%;
105
+ --agent-kit-composer-focus-border-mix: 65%;
106
+ --agent-kit-composer-border-color: color-mix(
107
+ in srgb,
108
+ hsl(var(--border)) var(--agent-kit-composer-border-mix),
109
+ transparent
110
+ );
111
+ --agent-kit-composer-focus-border-color: color-mix(
112
+ in srgb,
113
+ hsl(var(--border)) var(--agent-kit-composer-focus-border-mix),
114
+ transparent
115
+ );
104
116
  --agent-kit-shadow-color: var(--background);
105
117
  --agent-kit-control-elevation: 0 0.0625rem 0.125rem
106
118
  hsl(var(--agent-kit-shadow-color) / 0.18);
@@ -12,8 +12,8 @@ import {
12
12
 
13
13
  const labels: MessageQueueDrawerLabels = {
14
14
  region: "2 queued",
15
- steer: "Steer",
16
- steerHint: "Send this message next",
15
+ steer: "Send now",
16
+ steerHint: "Stops the current response, then sends this message.",
17
17
  remove: "Remove from queue",
18
18
  moreActions: "More actions",
19
19
  };
@@ -58,8 +58,9 @@ describe("MessageQueueDrawer", () => {
58
58
  onRemove={onRemove}
59
59
  getItemActions={(item) => [
60
60
  {
61
- id: "move-to-top",
62
- label: "Move to top",
61
+ id: "send-next",
62
+ label: "Send next",
63
+ hint: "Send after the current response finishes",
63
64
  onSelect: () => onMoveToTop(item),
64
65
  },
65
66
  ]}
@@ -87,7 +88,7 @@ describe("MessageQueueDrawer", () => {
87
88
  expect(container.querySelectorAll("li")).toHaveLength(2);
88
89
 
89
90
  const steerButton = Array.from(container.querySelectorAll("button")).find(
90
- (button) => button.textContent?.includes("Steer"),
91
+ (button) => button.textContent?.includes("Send now"),
91
92
  );
92
93
  const removeButton = container.querySelector<HTMLButtonElement>(
93
94
  'button[aria-label="Remove from queue"]',
@@ -116,7 +117,19 @@ describe("MessageQueueDrawer", () => {
116
117
  await Promise.resolve();
117
118
  });
118
119
  const menuItem = document.querySelector<HTMLElement>('[role="menuitem"]');
119
- expect(menuItem?.textContent).toContain("Move to top");
120
+ expect(menuItem?.textContent).toContain("Send next");
121
+ await act(async () => {
122
+ menuItem?.dispatchEvent(
123
+ new PointerEvent("pointermove", {
124
+ bubbles: true,
125
+ pointerType: "mouse",
126
+ }),
127
+ );
128
+ await new Promise((resolve) => setTimeout(resolve, 500));
129
+ });
130
+ expect(document.body.textContent).toContain(
131
+ "Send after the current response finishes",
132
+ );
120
133
  act(() => menuItem?.click());
121
134
  expect(onMoveToTop).toHaveBeenCalledWith(items[0]);
122
135
  });
@@ -238,7 +251,7 @@ describe("MessageQueueDrawer", () => {
238
251
  variant="recessed"
239
252
  onRemove={() => undefined}
240
253
  getItemActions={() => [
241
- { id: "move-to-top", label: "Move to top", onSelect: () => {} },
254
+ { id: "send-next", label: "Send next", onSelect: () => {} },
242
255
  ]}
243
256
  />,
244
257
  );
@@ -25,6 +25,7 @@ export interface MessageQueueItem {
25
25
  export interface MessageQueueItemAction {
26
26
  id: string;
27
27
  label: ReactNode;
28
+ hint?: string;
28
29
  icon?: ReactNode;
29
30
  destructive?: boolean;
30
31
  disabled?: boolean;
@@ -33,8 +34,12 @@ export interface MessageQueueItemAction {
33
34
 
34
35
  export interface MessageQueueDrawerLabels {
35
36
  region: string;
37
+ /** @deprecated Use sendNow. */
36
38
  steer: string;
39
+ /** @deprecated Use sendNowHint. */
37
40
  steerHint: string;
41
+ sendNow?: string;
42
+ sendNowHint?: string;
38
43
  remove: string;
39
44
  moreActions: string;
40
45
  }
@@ -55,7 +60,6 @@ export interface MessageQueueDrawerProps {
55
60
  className?: string;
56
61
  }
57
62
 
58
- const RECESSED_QUEUE_MAX_HEIGHT_PX = 160;
59
63
  const RECESSED_QUEUE_VERTICAL_CHROME_PX = 10;
60
64
 
61
65
  function recessedQueueHeight(items: readonly MessageQueueItem[]): number {
@@ -66,7 +70,7 @@ function recessedQueueHeight(items: readonly MessageQueueItem[]): number {
66
70
  height + (item.images && item.images.length > 0 ? 56 : 36),
67
71
  RECESSED_QUEUE_VERTICAL_CHROME_PX,
68
72
  );
69
- return Math.min(contentHeight, RECESSED_QUEUE_MAX_HEIGHT_PX);
73
+ return contentHeight;
70
74
  }
71
75
 
72
76
  export function MessageQueueDrawer({
@@ -82,6 +86,8 @@ export function MessageQueueDrawer({
82
86
  }: MessageQueueDrawerProps) {
83
87
  const recessed = variant === "recessed";
84
88
  const empty = items.length === 0;
89
+ const sendNow = labels.sendNow ?? labels.steer;
90
+ const sendNowHint = labels.sendNowHint ?? labels.steerHint;
85
91
  const [openActionsItemId, setOpenActionsItemId] = useState<string | null>(
86
92
  null,
87
93
  );
@@ -121,8 +127,10 @@ export function MessageQueueDrawer({
121
127
  >
122
128
  <ul
123
129
  className={cn(
124
- "flex max-h-40 flex-col gap-0.5 overflow-y-auto py-1",
125
- recessed && "h-full",
130
+ "flex flex-col",
131
+ recessed
132
+ ? "h-full gap-0 overflow-y-hidden py-0"
133
+ : "max-h-40 gap-0.5 overflow-y-auto py-1",
126
134
  )}
127
135
  >
128
136
  {items.map((item) => {
@@ -131,7 +139,10 @@ export function MessageQueueDrawer({
131
139
  return (
132
140
  <li
133
141
  key={item.id}
134
- className="group flex min-h-9 min-w-0 items-center gap-2 px-4 py-1.5 text-[13px] leading-4 animate-in fade-in-0 slide-in-from-bottom-1 duration-200 ease-[var(--ease-collapse)] motion-reduce:animate-none"
142
+ className={cn(
143
+ "group flex min-h-9 min-w-0 items-center gap-2 px-4 text-[13px] leading-4 animate-in fade-in-0 slide-in-from-bottom-1 duration-200 ease-[var(--ease-collapse)] motion-reduce:animate-none",
144
+ recessed ? "py-0.5" : "py-1.5",
145
+ )}
135
146
  >
136
147
  <IconCornerDownRight
137
148
  aria-hidden="true"
@@ -172,10 +183,10 @@ export function MessageQueueDrawer({
172
183
  className="size-3.5"
173
184
  strokeWidth={1.7}
174
185
  />
175
- <span>{labels.steer}</span>
186
+ <span>{sendNow}</span>
176
187
  </Button>
177
188
  </TooltipTrigger>
178
- <TooltipContent>{labels.steerHint}</TooltipContent>
189
+ <TooltipContent>{sendNowHint}</TooltipContent>
179
190
  </Tooltip>
180
191
  ) : null}
181
192
  <Tooltip>
@@ -233,20 +244,35 @@ export function MessageQueueDrawer({
233
244
  side="top"
234
245
  sideOffset={4}
235
246
  >
236
- {actions.map((action) => (
237
- <DropdownMenuItem
238
- key={action.id}
239
- disabled={disabled || action.disabled}
240
- onSelect={() => action.onSelect(item)}
241
- className={cn(
242
- action.destructive &&
243
- "text-destructive focus:text-destructive",
244
- )}
245
- >
246
- {action.icon}
247
- {action.label}
248
- </DropdownMenuItem>
249
- ))}
247
+ {actions.map((action) => {
248
+ const menuItem = (
249
+ <DropdownMenuItem
250
+ key={action.id}
251
+ disabled={disabled || action.disabled}
252
+ onSelect={() => action.onSelect(item)}
253
+ className={cn(
254
+ action.destructive &&
255
+ "text-destructive focus:text-destructive",
256
+ )}
257
+ >
258
+ {action.icon}
259
+ {action.label}
260
+ </DropdownMenuItem>
261
+ );
262
+
263
+ return action.hint ? (
264
+ <Tooltip key={action.id}>
265
+ <TooltipTrigger asChild>
266
+ {menuItem}
267
+ </TooltipTrigger>
268
+ <TooltipContent side="right">
269
+ {action.hint}
270
+ </TooltipContent>
271
+ </Tooltip>
272
+ ) : (
273
+ menuItem
274
+ );
275
+ })}
250
276
  </DropdownMenuContent>
251
277
  </DropdownMenu>
252
278
  ) : null}