@agent-native/toolkit 0.227.0 → 0.228.0-nightly-20261111004142

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 (144) hide show
  1. package/agent-native.eject.json +8 -0
  2. package/dist/app/agent-page/AgentTabFrame.d.ts +2 -1
  3. package/dist/app/agent-page/AgentTabFrame.d.ts.map +1 -1
  4. package/dist/app/agent-page/AgentTabFrame.js +3 -2
  5. package/dist/app/agent-page/AgentTabFrame.js.map +1 -1
  6. package/dist/app/chat/AgentPanel.d.ts +15 -15
  7. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  8. package/dist/app/chat/AgentPanel.js.map +1 -1
  9. package/dist/app/chat/AgentSidebar.d.ts +7 -1
  10. package/dist/app/chat/AgentSidebar.d.ts.map +1 -1
  11. package/dist/app/chat/AgentSidebar.js +47 -16
  12. package/dist/app/chat/AgentSidebar.js.map +1 -1
  13. package/dist/app/chat/chat/surface-types.d.ts +26 -26
  14. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  15. package/dist/app/extensions/EmbeddedExtension.d.ts.map +1 -1
  16. package/dist/app/extensions/EmbeddedExtension.js +6 -4
  17. package/dist/app/extensions/EmbeddedExtension.js.map +1 -1
  18. package/dist/app/extensions/ExtensionViewer.d.ts.map +1 -1
  19. package/dist/app/extensions/ExtensionViewer.js +6 -3
  20. package/dist/app/extensions/ExtensionViewer.js.map +1 -1
  21. package/dist/app/extensions/InlineExtensionFrame.d.ts.map +1 -1
  22. package/dist/app/extensions/InlineExtensionFrame.js +32 -28
  23. package/dist/app/extensions/InlineExtensionFrame.js.map +1 -1
  24. package/dist/app/extensions/use-extension-chat-submission.d.ts +14 -0
  25. package/dist/app/extensions/use-extension-chat-submission.d.ts.map +1 -0
  26. package/dist/app/extensions/use-extension-chat-submission.js +36 -0
  27. package/dist/app/extensions/use-extension-chat-submission.js.map +1 -0
  28. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  29. package/dist/app/i18n/catalogs/ar-SA.js +16 -0
  30. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  31. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  32. package/dist/app/i18n/catalogs/de-DE.js +12 -0
  33. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  34. package/dist/app/i18n/catalogs/en-US.d.ts +12 -0
  35. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  36. package/dist/app/i18n/catalogs/en-US.js +12 -0
  37. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  38. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  39. package/dist/app/i18n/catalogs/es-ES.js +13 -0
  40. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  41. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  42. package/dist/app/i18n/catalogs/fr-FR.js +13 -0
  43. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  44. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  45. package/dist/app/i18n/catalogs/hi-IN.js +12 -0
  46. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  47. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  48. package/dist/app/i18n/catalogs/ja-JP.js +12 -0
  49. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  50. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  51. package/dist/app/i18n/catalogs/ko-KR.js +12 -0
  52. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  53. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  54. package/dist/app/i18n/catalogs/pt-BR.js +13 -0
  55. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  56. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  57. package/dist/app/i18n/catalogs/zh-CN.js +12 -0
  58. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  59. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  60. package/dist/app/i18n/catalogs/zh-TW.js +12 -0
  61. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  62. package/dist/app/org/OrgSwitcher.d.ts.map +1 -1
  63. package/dist/app/org/OrgSwitcher.js +3 -2
  64. package/dist/app/org/OrgSwitcher.js.map +1 -1
  65. package/dist/app/resources/McpAccessSettings.d.ts +9 -2
  66. package/dist/app/resources/McpAccessSettings.d.ts.map +1 -1
  67. package/dist/app/resources/McpAccessSettings.js +264 -86
  68. package/dist/app/resources/McpAccessSettings.js.map +1 -1
  69. package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts +6 -0
  70. package/dist/app/settings/api-keys/ApiKeyDialogs.d.ts.map +1 -1
  71. package/dist/app/settings/api-keys/ApiKeyDialogs.js +74 -2
  72. package/dist/app/settings/api-keys/ApiKeyDialogs.js.map +1 -1
  73. package/dist/app/settings/api-keys/ApiKeysSettingsPage.d.ts.map +1 -1
  74. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js +23 -9
  75. package/dist/app/settings/api-keys/ApiKeysSettingsPage.js.map +1 -1
  76. package/dist/app/settings/api-keys/api-keys-client.d.ts +10 -1
  77. package/dist/app/settings/api-keys/api-keys-client.d.ts.map +1 -1
  78. package/dist/app/settings/api-keys/api-keys-client.js +19 -1
  79. package/dist/app/settings/api-keys/api-keys-client.js.map +1 -1
  80. package/dist/app/settings/shell/pages/mcp.d.ts +1 -1
  81. package/dist/app/settings/shell/pages/mcp.d.ts.map +1 -1
  82. package/dist/app/settings/shell/pages/mcp.js +26 -20
  83. package/dist/app/settings/shell/pages/mcp.js.map +1 -1
  84. package/dist/app/sharing/ShareButton.d.ts +2 -0
  85. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  86. package/dist/app/sharing/ShareButton.js +4 -3
  87. package/dist/app/sharing/ShareButton.js.map +1 -1
  88. package/dist/composer/TiptapComposer.js +3 -0
  89. package/dist/composer/TiptapComposer.js.map +1 -1
  90. package/dist/sharing/ShareControls.d.ts +2 -0
  91. package/dist/sharing/ShareControls.d.ts.map +1 -1
  92. package/dist/sharing/ShareControls.js +7 -2
  93. package/dist/sharing/ShareControls.js.map +1 -1
  94. package/dist/ui/index.d.ts +1 -0
  95. package/dist/ui/index.d.ts.map +1 -1
  96. package/dist/ui/index.js +1 -0
  97. package/dist/ui/index.js.map +1 -1
  98. package/dist/ui/numbered-steps.d.ts +14 -0
  99. package/dist/ui/numbered-steps.d.ts.map +1 -0
  100. package/dist/ui/numbered-steps.js +19 -0
  101. package/dist/ui/numbered-steps.js.map +1 -0
  102. package/package.json +4 -4
  103. package/src/app/agent-page/AgentTabFrame.tsx +21 -10
  104. package/src/app/chat/AgentPanel.header.spec.ts +4 -1
  105. package/src/app/chat/AgentPanel.tsx +19 -14
  106. package/src/app/chat/AgentSidebar.spec.tsx +167 -0
  107. package/src/app/chat/AgentSidebar.tsx +170 -101
  108. package/src/app/chat/chat/surface-types.ts +35 -31
  109. package/src/app/extensions/EmbeddedExtension.height.spec.tsx +45 -0
  110. package/src/app/extensions/EmbeddedExtension.tsx +6 -3
  111. package/src/app/extensions/ExtensionViewer.spec.tsx +28 -0
  112. package/src/app/extensions/ExtensionViewer.tsx +6 -2
  113. package/src/app/extensions/InlineExtensionFrame.spec.tsx +23 -0
  114. package/src/app/extensions/InlineExtensionFrame.tsx +23 -15
  115. package/src/app/extensions/use-extension-chat-submission.tsx +101 -0
  116. package/src/app/i18n/catalogs/ar-SA.ts +17 -0
  117. package/src/app/i18n/catalogs/de-DE.ts +15 -0
  118. package/src/app/i18n/catalogs/en-US.ts +13 -0
  119. package/src/app/i18n/catalogs/es-ES.ts +16 -0
  120. package/src/app/i18n/catalogs/fr-FR.ts +16 -0
  121. package/src/app/i18n/catalogs/hi-IN.ts +13 -0
  122. package/src/app/i18n/catalogs/ja-JP.ts +15 -0
  123. package/src/app/i18n/catalogs/ko-KR.ts +14 -0
  124. package/src/app/i18n/catalogs/pt-BR.ts +15 -0
  125. package/src/app/i18n/catalogs/zh-CN.ts +13 -0
  126. package/src/app/i18n/catalogs/zh-TW.ts +13 -0
  127. package/src/app/integrations/IntegrationsPanel.spec.tsx +1 -1
  128. package/src/app/org/OrgSwitcher.spec.tsx +43 -0
  129. package/src/app/org/OrgSwitcher.tsx +29 -24
  130. package/src/app/resources/McpAccessSettings.spec.tsx +354 -278
  131. package/src/app/resources/McpAccessSettings.tsx +1012 -323
  132. package/src/app/settings/api-keys/ApiKeyDialogs.tsx +148 -1
  133. package/src/app/settings/api-keys/ApiKeysSettingsPage.spec.tsx +190 -0
  134. package/src/app/settings/api-keys/ApiKeysSettingsPage.tsx +40 -1
  135. package/src/app/settings/api-keys/api-keys-client.spec.ts +49 -0
  136. package/src/app/settings/api-keys/api-keys-client.ts +32 -0
  137. package/src/app/settings/shell/pages/app-group-pages.spec.tsx +31 -21
  138. package/src/app/settings/shell/pages/mcp.tsx +50 -42
  139. package/src/app/sharing/ShareButton.tsx +10 -3
  140. package/src/composer/TiptapComposer.tsx +2 -0
  141. package/src/composer/TiptapComposer.voice-toolbar.spec.tsx +16 -0
  142. package/src/sharing/ShareControls.tsx +44 -24
  143. package/src/ui/index.ts +1 -0
  144. package/src/ui/numbered-steps.tsx +104 -0
@@ -73,7 +73,7 @@ import React, {
73
73
  lazy,
74
74
  Suspense,
75
75
  } from "react";
76
- import { flushSync } from "react-dom";
76
+ import { createPortal, flushSync } from "react-dom";
77
77
 
78
78
  import { setComposerReferenceEventTarget } from "../../composer/runtime-adapters.js";
79
79
  import { Skeleton } from "../../ui/skeleton.js";
@@ -296,6 +296,12 @@ export interface AgentSidebarProps {
296
296
  forceOverlay?: boolean;
297
297
  defaultOpen?: boolean;
298
298
  disableChatShortcut?: boolean;
299
+ /**
300
+ * Draws the panel inside this element instead of beside the page, for an app
301
+ * whose own sidebar holds the agent. The app then owns the panel's width,
302
+ * motion and close button, and the key that toggles the sidebar.
303
+ */
304
+ panelHost?: HTMLElement | null;
299
305
  showCollapseButton?: boolean;
300
306
  animateMobile?: boolean;
301
307
  animateDesktop?: boolean;
@@ -376,6 +382,7 @@ export function AgentSidebar({
376
382
  forceOverlay = false,
377
383
  defaultOpen,
378
384
  disableChatShortcut = false,
385
+ panelHost,
379
386
  showCollapseButton = true,
380
387
  animateMobile = true,
381
388
  animateDesktop = true,
@@ -644,9 +651,18 @@ export function AgentSidebar({
644
651
  !isPerAppChatHosted &&
645
652
  !presentationMode &&
646
653
  (!frameCodeMode || !shouldParentFrameOwnAgentPanel());
654
+ const hostedPanel = ownsPanel && panelHost ? panelHost : null;
655
+ // A host switches between the agent and its other sections, so the chat
656
+ // stays mounted behind them once it has opened.
657
+ const [hostedPanelOpened, setHostedPanelOpened] = useState(false);
658
+ useEffect(() => {
659
+ if (!hostedPanel) setHostedPanelOpened(false);
660
+ else if (open) setHostedPanelOpened(true);
661
+ }, [hostedPanel, open]);
647
662
  const shouldMountPanel =
648
663
  ownsPanel &&
649
664
  (open ||
665
+ hostedPanelOpened ||
650
666
  backgroundPanelActive ||
651
667
  runningTabIds.size > 0 ||
652
668
  hasPendingPanelEvents);
@@ -714,7 +730,7 @@ export function AgentSidebar({
714
730
  return (
715
731
  pending.order < navigation.order &&
716
732
  !isPanelNavigationEvent(pending.event) &&
717
- !submit?.targetTabId
733
+ !sendsToOwnTab(submit)
718
734
  );
719
735
  }),
720
736
  );
@@ -844,7 +860,11 @@ export function AgentSidebar({
844
860
  pending.targetId == null &&
845
861
  !pending.reference &&
846
862
  !isComposerReferenceEvent(pending.event) &&
847
- !isPanelNavigationEvent(pending.event)
863
+ !isPanelNavigationEvent(pending.event) &&
864
+ !(
865
+ pending.event instanceof MessageEvent &&
866
+ sendsToOwnTab(parseSubmitChatMessage(pending.event))
867
+ )
848
868
  )
849
869
  pending.targetId = targetId;
850
870
  }
@@ -855,7 +875,7 @@ export function AgentSidebar({
855
875
  pending.event instanceof MessageEvent
856
876
  ? parseSubmitChatMessage(pending.event)
857
877
  : null;
858
- if (submit?.targetTabId) return false;
878
+ if (sendsToOwnTab(submit)) return false;
859
879
  const recipient =
860
880
  pending.referenceTargetId ??
861
881
  pending.reference?.referenceTargetId ??
@@ -978,10 +998,12 @@ export function AgentSidebar({
978
998
  : !presentationMode && (frameOwned ? frameSidebarOpen : open)),
979
999
  source: frameOwned ? "frame" : "app",
980
1000
  mode: frameOwned ? "code" : "app",
1001
+ hosted: Boolean(hostedPanel),
981
1002
  });
982
1003
  }, [
983
1004
  frameCodeMode,
984
1005
  frameSidebarOpen,
1006
+ hostedPanel,
985
1007
  open,
986
1008
  presentationMode,
987
1009
  hasFrameSidebarState,
@@ -1125,12 +1147,13 @@ export function AgentSidebar({
1125
1147
  : recipient
1126
1148
  : undefined,
1127
1149
  navigation:
1128
- submit?.targetTabId || isPanelNavigationEvent(event)
1150
+ sendsToOwnTab(submit) || isPanelNavigationEvent(event)
1129
1151
  ? undefined
1130
1152
  : navigation,
1131
1153
  targetId:
1132
1154
  submit?.targetTabId ??
1133
1155
  (navigation ||
1156
+ sendsToOwnTab(submit) ||
1134
1157
  isPanelNavigationEvent(event) ||
1135
1158
  isComposerReferenceEvent(event)
1136
1159
  ? undefined
@@ -1139,7 +1162,7 @@ export function AgentSidebar({
1139
1162
  : recipient),
1140
1163
  reference:
1141
1164
  !isPanelNavigationEvent(event) &&
1142
- !submit?.targetTabId &&
1165
+ !sendsToOwnTab(submit) &&
1143
1166
  reference &&
1144
1167
  isComposerReferenceEvent(reference.event)
1145
1168
  ? reference
@@ -1468,14 +1491,22 @@ export function AgentSidebar({
1468
1491
  useEffect(() => {
1469
1492
  if (!enabled) return;
1470
1493
  const handleKeyDown = (e: KeyboardEvent) => {
1471
- if (isAgentSidebarToggleShortcut(e)) {
1494
+ // A host's element that isn't on the page yet, such as one a loading
1495
+ // page's skeleton holds, has no panel to answer ⌘\, so the shell does.
1496
+ if (!panelHost?.isConnected && isAgentSidebarToggleShortcut(e)) {
1472
1497
  e.preventDefault();
1473
1498
  window.dispatchEvent(
1474
1499
  new CustomEvent("agent-panel:toggle", { detail: { focus: true } }),
1475
1500
  );
1476
1501
  return;
1477
1502
  }
1478
- if (!disableChatShortcut && isAgentPanelChatShortcut(e)) {
1503
+ // A Mac reports ⇧⌘I with a lowercase key, and a hosting page's panel
1504
+ // owns that key.
1505
+ if (
1506
+ !disableChatShortcut &&
1507
+ isAgentPanelChatShortcut(e) &&
1508
+ !(panelHost && e.shiftKey)
1509
+ ) {
1479
1510
  e.preventDefault();
1480
1511
  let selectionText = "";
1481
1512
  try {
@@ -1500,7 +1531,7 @@ export function AgentSidebar({
1500
1531
  };
1501
1532
  document.addEventListener("keydown", handleKeyDown);
1502
1533
  return () => document.removeEventListener("keydown", handleKeyDown);
1503
- }, [disableChatShortcut, enabled]);
1534
+ }, [disableChatShortcut, enabled, panelHost]);
1504
1535
 
1505
1536
  useEffect(() => {
1506
1537
  const handler = (event: MessageEvent) => {
@@ -1602,12 +1633,12 @@ export function AgentSidebar({
1602
1633
  }, [persistWidth]);
1603
1634
 
1604
1635
  const isLeft = effectivePosition === "left";
1605
- const isOverlay = isMobile || forceOverlay;
1606
- const wideDrawerEnabled = isWideDrawer && !isOverlay;
1636
+ const isOverlay = !hostedPanel && (isMobile || forceOverlay);
1637
+ const wideDrawerEnabled = isWideDrawer && !isOverlay && !hostedPanel;
1607
1638
  const mobileAnimationEnabled =
1608
1639
  !presentationMode && isOverlay && animateMobile;
1609
1640
  const desktopAnimationEnabled =
1610
- !presentationMode && !isOverlay && effectiveAnimateDesktop;
1641
+ !presentationMode && !isOverlay && !hostedPanel && effectiveAnimateDesktop;
1611
1642
  const sidebarAnimationEnabled =
1612
1643
  mobileAnimationEnabled || desktopAnimationEnabled;
1613
1644
  const [renderAnimatedPanel, setRenderAnimatedPanel] =
@@ -1713,7 +1744,119 @@ export function AgentSidebar({
1713
1744
  };
1714
1745
  }
1715
1746
 
1716
- const sidebar = shouldRenderPanel ? (
1747
+ const panelBody = (
1748
+ <LazyChunkErrorBoundary
1749
+ fallback={<LazyChunkRetryFallback />}
1750
+ shouldHandleError={isPanelImportError}
1751
+ onError={onPanelLoadFailed}
1752
+ >
1753
+ <Suspense
1754
+ fallback={
1755
+ <div
1756
+ aria-hidden="true"
1757
+ className="flex min-h-0 flex-1 flex-col gap-3 p-3"
1758
+ >
1759
+ <Skeleton className="h-8 w-full" />
1760
+ <div className="flex-1" />
1761
+ <Skeleton className="h-28 w-full rounded-xl" />
1762
+ </div>
1763
+ }
1764
+ >
1765
+ <AgentSidebarPanel
1766
+ onReadyChange={onPanelReadyChange}
1767
+ onReferenceTargetChange={onReferenceTargetChange}
1768
+ onNavigationChange={onNavigationChange}
1769
+ onTabsClosing={onTabsClosing}
1770
+ emptyStateText={emptyStateText}
1771
+ mcpSuggestionExcludedIntegrationIds={
1772
+ mcpSuggestionExcludedIntegrationIds
1773
+ }
1774
+ suggestions={suggestions}
1775
+ dynamicSuggestions={dynamicSuggestions}
1776
+ suggestionPlacement="context-chips"
1777
+ composerToolbarSlot={composerToolbarSlot}
1778
+ composerSlot={composerSlot}
1779
+ // A host's tab opens the chat without taking focus from the tab.
1780
+ {...(hostedPanel ? { composerAutoFocus: false } : {})}
1781
+ composerContextProvider={composerContextProvider}
1782
+ onComposerTextChange={onComposerTextChange}
1783
+ imageModelMenu={imageModelMenu}
1784
+ availableAgents={effectiveAvailableAgents}
1785
+ availableModels={availableModels}
1786
+ modelListLoading={modelListLoading}
1787
+ selectedAgent={effectiveSelectedAgent}
1788
+ onAgentChange={effectiveOnAgentChange}
1789
+ hostedHarness={hostedHarnessEnabled}
1790
+ onConnectProvider={onConnectProvider}
1791
+ builderConnectTransport={builderConnectTransport}
1792
+ onConnectLocalRuntime={onConnectLocalRuntime}
1793
+ runtime={runtime}
1794
+ adapterReloadKey={adapterReloadKey}
1795
+ threadFooterSlot={threadFooterSlot}
1796
+ apiUrl={apiUrl}
1797
+ agentChatSurface={agentChatSurface}
1798
+ desktopIdentityUnauthenticated={desktopIdentityUnauthenticated}
1799
+ desktopIdentityAuthenticated={desktopIdentityAuthenticated}
1800
+ showTabBar={effectiveShowTabBar}
1801
+ suppressInlineOpenApp={suppressInlineOpenApp}
1802
+ composerPlaceholder={composerPlaceholder}
1803
+ showMissingApiKeySetup={showMissingApiKeySetup}
1804
+ setupCardOwner={setupCardOwner}
1805
+ showGuidedQuestions={showGuidedQuestions}
1806
+ missingApiKeySetupLayout="sidebar"
1807
+ defaultMode={defaultMode}
1808
+ onCollapse={() => setOpenPersisted(false)}
1809
+ showCollapseButton={!hostedPanel && showCollapseButton}
1810
+ onSnapTo75Percent={
1811
+ isOverlay || hostedPanel ? undefined : snapTo75Percent
1812
+ }
1813
+ isWideDrawer={wideDrawerEnabled}
1814
+ onExitWideDrawer={
1815
+ isOverlay || hostedPanel ? undefined : exitWideDrawer
1816
+ }
1817
+ onFullViewRequest={onFullscreenRequest}
1818
+ onOpenSettings={onOpenSettings}
1819
+ onNewCliTab={onNewCliTab}
1820
+ onNewUiTab={onNewUiTab}
1821
+ renderCliTab={renderCliTab}
1822
+ newTabMode={newTabMode}
1823
+ newCliTabLabel={newCliTabLabel}
1824
+ newUiTabLabel={newUiTabLabel}
1825
+ storageKey={storageKey}
1826
+ restoreActiveThread={restoreActiveThread}
1827
+ scope={scope}
1828
+ {...(chatHistory ? { chatHistory } : {})}
1829
+ isolateHistoryByScope={isolateHistoryByScope}
1830
+ showScopeBadge={showScopeBadge}
1831
+ browserTabId={resolvedBrowserTabId}
1832
+ threadUrlSync={threadUrlSync}
1833
+ agentPageHref={agentPageHref}
1834
+ thinkingDisplay={thinkingDisplay}
1835
+ showModelSelector={showModelSelector}
1836
+ chatOnly={chatOnly}
1837
+ />
1838
+ </Suspense>
1839
+ </LazyChunkErrorBoundary>
1840
+ );
1841
+
1842
+ const sidebar = !shouldRenderPanel ? null : hostedPanel ? (
1843
+ createPortal(
1844
+ <div
1845
+ className="agent-sidebar-panel agent-kit-density flex min-h-0 flex-1 flex-col overflow-hidden"
1846
+ data-agent-sidebar-layout="hosted"
1847
+ data-agent-sidebar-state={panelOpen ? "open" : "closed"}
1848
+ inert={!panelOpen || undefined}
1849
+ >
1850
+ <div
1851
+ ref={panelElementRef}
1852
+ className="agent-sidebar-panel-inner relative flex min-h-0 flex-1 flex-col"
1853
+ >
1854
+ {panelBody}
1855
+ </div>
1856
+ </div>,
1857
+ hostedPanel,
1858
+ )
1859
+ ) : (
1717
1860
  <>
1718
1861
  {showResizeHandle && !isLeft && (
1719
1862
  <ResizeHandle
@@ -1766,92 +1909,7 @@ export function AgentSidebar({
1766
1909
  ref={panelElementRef}
1767
1910
  className="agent-sidebar-panel-inner relative flex min-h-0 flex-1 flex-col"
1768
1911
  >
1769
- <LazyChunkErrorBoundary
1770
- fallback={<LazyChunkRetryFallback />}
1771
- shouldHandleError={isPanelImportError}
1772
- onError={onPanelLoadFailed}
1773
- >
1774
- <Suspense
1775
- fallback={
1776
- <div
1777
- aria-hidden="true"
1778
- className="flex min-h-0 flex-1 flex-col gap-3 p-3"
1779
- >
1780
- <Skeleton className="h-8 w-full" />
1781
- <div className="flex-1" />
1782
- <Skeleton className="h-28 w-full rounded-xl" />
1783
- </div>
1784
- }
1785
- >
1786
- <AgentSidebarPanel
1787
- onReadyChange={onPanelReadyChange}
1788
- onReferenceTargetChange={onReferenceTargetChange}
1789
- onNavigationChange={onNavigationChange}
1790
- onTabsClosing={onTabsClosing}
1791
- emptyStateText={emptyStateText}
1792
- mcpSuggestionExcludedIntegrationIds={
1793
- mcpSuggestionExcludedIntegrationIds
1794
- }
1795
- suggestions={suggestions}
1796
- dynamicSuggestions={dynamicSuggestions}
1797
- suggestionPlacement="context-chips"
1798
- composerToolbarSlot={composerToolbarSlot}
1799
- composerSlot={composerSlot}
1800
- composerContextProvider={composerContextProvider}
1801
- onComposerTextChange={onComposerTextChange}
1802
- imageModelMenu={imageModelMenu}
1803
- availableAgents={effectiveAvailableAgents}
1804
- availableModels={availableModels}
1805
- modelListLoading={modelListLoading}
1806
- selectedAgent={effectiveSelectedAgent}
1807
- onAgentChange={effectiveOnAgentChange}
1808
- hostedHarness={hostedHarnessEnabled}
1809
- onConnectProvider={onConnectProvider}
1810
- builderConnectTransport={builderConnectTransport}
1811
- onConnectLocalRuntime={onConnectLocalRuntime}
1812
- runtime={runtime}
1813
- adapterReloadKey={adapterReloadKey}
1814
- threadFooterSlot={threadFooterSlot}
1815
- apiUrl={apiUrl}
1816
- agentChatSurface={agentChatSurface}
1817
- desktopIdentityUnauthenticated={desktopIdentityUnauthenticated}
1818
- desktopIdentityAuthenticated={desktopIdentityAuthenticated}
1819
- showTabBar={effectiveShowTabBar}
1820
- suppressInlineOpenApp={suppressInlineOpenApp}
1821
- composerPlaceholder={composerPlaceholder}
1822
- showMissingApiKeySetup={showMissingApiKeySetup}
1823
- setupCardOwner={setupCardOwner}
1824
- showGuidedQuestions={showGuidedQuestions}
1825
- missingApiKeySetupLayout="sidebar"
1826
- defaultMode={defaultMode}
1827
- onCollapse={() => setOpenPersisted(false)}
1828
- showCollapseButton={showCollapseButton}
1829
- onSnapTo75Percent={isOverlay ? undefined : snapTo75Percent}
1830
- isWideDrawer={isOverlay ? false : isWideDrawer}
1831
- onExitWideDrawer={isOverlay ? undefined : exitWideDrawer}
1832
- onFullViewRequest={onFullscreenRequest}
1833
- onOpenSettings={onOpenSettings}
1834
- onNewCliTab={onNewCliTab}
1835
- onNewUiTab={onNewUiTab}
1836
- renderCliTab={renderCliTab}
1837
- newTabMode={newTabMode}
1838
- newCliTabLabel={newCliTabLabel}
1839
- newUiTabLabel={newUiTabLabel}
1840
- storageKey={storageKey}
1841
- restoreActiveThread={restoreActiveThread}
1842
- scope={scope}
1843
- chatHistory={chatHistory}
1844
- isolateHistoryByScope={isolateHistoryByScope}
1845
- showScopeBadge={showScopeBadge}
1846
- browserTabId={resolvedBrowserTabId}
1847
- threadUrlSync={threadUrlSync}
1848
- agentPageHref={agentPageHref}
1849
- thinkingDisplay={thinkingDisplay}
1850
- showModelSelector={showModelSelector}
1851
- chatOnly={chatOnly}
1852
- />
1853
- </Suspense>
1854
- </LazyChunkErrorBoundary>
1912
+ {panelBody}
1855
1913
  </div>
1856
1914
  </div>
1857
1915
  {showResizeHandle && isLeft && (
@@ -1866,7 +1924,7 @@ export function AgentSidebar({
1866
1924
  />
1867
1925
  )}
1868
1926
  </>
1869
- ) : null;
1927
+ );
1870
1928
 
1871
1929
  const drawerPlaceholder =
1872
1930
  wideDrawerEnabled && !presentationMode && panelOpen ? (
@@ -1928,7 +1986,9 @@ export function AgentSidebar({
1928
1986
  className="agent-sidebar-main-surface flex flex-1 flex-col overflow-auto min-w-0"
1929
1987
  data-agent-sidebar-main-position={effectivePosition}
1930
1988
  data-agent-sidebar-main-state={
1931
- !isOverlay && !presentationMode && panelOpen ? "open" : "closed"
1989
+ !isOverlay && !hostedPanel && !presentationMode && panelOpen
1990
+ ? "open"
1991
+ : "closed"
1932
1992
  }
1933
1993
  data-agent-sidebar-resizing={isResizing ? "true" : undefined}
1934
1994
  >
@@ -1972,6 +2032,15 @@ function isComposerReferenceEvent(event: Event): boolean {
1972
2032
  );
1973
2033
  }
1974
2034
 
2035
+ // A send that names its tab, or opens a new one, already knows where it lands.
2036
+ // The chat the panel happens to show meanwhile is not its recipient, so a
2037
+ // switch away from that chat must not cancel it.
2038
+ function sendsToOwnTab(
2039
+ submit: ReturnType<typeof parseSubmitChatMessage>,
2040
+ ): boolean {
2041
+ return Boolean(submit?.targetTabId || submit?.newTab);
2042
+ }
2043
+
1975
2044
  function isPanelNavigationEvent(event: Event): boolean {
1976
2045
  return (
1977
2046
  event.type === "agent-chat:open-thread" || event.type === "agent-task-open"
@@ -195,7 +195,7 @@ export interface AssistantChatAdapterContext {
195
195
 
196
196
  export interface AssistantChatProps {
197
197
  /** API endpoint URL. Default: "/_agent-native/agent-chat" */
198
- apiUrl?: string;
198
+ apiUrl?: string | undefined;
199
199
  /** Optional Nitro response-streaming endpoint, usually supplied by VITE_AGENT_NATIVE_AGENT_CHAT_STREAM_URL. */
200
200
  streamingUrl?: string;
201
201
  /** Stable tab identifier passed to the adapter for event correlation */
@@ -218,21 +218,21 @@ export interface AssistantChatProps {
218
218
  * Identifies which surface hosts this chat. Defaults to "app", which keeps
219
219
  * dev filesystem/bash code-editing tools out of in-product sidebars.
220
220
  */
221
- agentChatSurface?: AgentChatSurfaceKind;
221
+ agentChatSurface?: AgentChatSurfaceKind | undefined;
222
222
  /** Whether the desktop host is currently showing its unauthenticated identity gate. */
223
- desktopIdentityUnauthenticated?: boolean;
223
+ desktopIdentityUnauthenticated?: boolean | undefined;
224
224
  /** Whether the desktop host has just established its authenticated identity session. */
225
- desktopIdentityAuthenticated?: boolean;
225
+ desktopIdentityAuthenticated?: boolean | undefined;
226
226
  /** Route completed first-party open_app calls through the host app pane. */
227
- suppressInlineOpenApp?: boolean;
227
+ suppressInlineOpenApp?: boolean | undefined;
228
228
  /** Placeholder text for empty state */
229
- emptyStateText?: string;
229
+ emptyStateText?: string | undefined;
230
230
  /** Initial empty-thread starters. Completed turns use the runtime's model-authored suggestions. */
231
- suggestions?: AgentSuggestionInput[];
231
+ suggestions?: AgentSuggestionInput[] | undefined;
232
232
  /** Initial empty-thread suggestion behavior; never disables or replaces runtime follow-ups. */
233
- dynamicSuggestions?: AgentDynamicSuggestionsOption;
233
+ dynamicSuggestions?: AgentDynamicSuggestionsOption | undefined;
234
234
  /** Exclude providers whose connection flow is handled by the host app. */
235
- mcpSuggestionExcludedIntegrationIds?: readonly string[];
235
+ mcpSuggestionExcludedIntegrationIds?: readonly string[] | undefined;
236
236
  /** Where suggestions appear. Context chips use a next-action bar at the thread base. */
237
237
  suggestionPlacement?:
238
238
  | "empty-state"
@@ -298,7 +298,9 @@ export interface AssistantChatProps {
298
298
  /** Optional ordering policy for assistant message text and attached widgets. */
299
299
  resolveMessageContentOrder?: AgentMessageContentOrderResolver;
300
300
  /** App-owned context controller, mounted separately for each chat thread. */
301
- composerContextProvider?: React.ComponentType<AssistantChatComposerContextProviderProps>;
301
+ composerContextProvider?:
302
+ | React.ComponentType<AssistantChatComposerContextProviderProps>
303
+ | undefined;
302
304
  /** Optional home content rendered above the composer on an empty chat. */
303
305
  homeIntroSlot?: React.ReactNode;
304
306
  /** Optional content rendered below the composer on an empty chat. */
@@ -309,17 +311,17 @@ export interface AssistantChatProps {
309
311
  * Host apps can use this to render contextual, non-destructive affordances
310
312
  * beside the shared composer without replacing the composer stack.
311
313
  */
312
- onComposerTextChange?: (text: string) => void;
314
+ onComposerTextChange?: ((text: string) => void) | undefined;
313
315
  /** Class applied to the shared composer area for host-specific sizing/skin. */
314
316
  composerAreaClassName?: string;
315
317
  /** Placeholder for the shared composer in its normal idle state. */
316
- composerPlaceholder?: string;
318
+ composerPlaceholder?: string | undefined;
317
319
  /** Controls the compactness of the provider setup panel attached above the composer. */
318
320
  missingApiKeySetupLayout?: BuilderSetupCardLayout;
319
321
  /** Hide the provider setup panel when another host surface owns that prompt. */
320
- showMissingApiKeySetup?: boolean;
322
+ showMissingApiKeySetup?: boolean | undefined;
321
323
  /** Keep chat-owned recovery cards hidden when the host renders the setup card. */
322
- setupCardOwner?: "chat" | "host";
324
+ setupCardOwner?: "chat" | "host" | undefined;
323
325
  /** Visual density for the shared composer shell. */
324
326
  composerLayoutVariant?: AgentComposerLayoutVariant;
325
327
  /** Center the composer on a fresh empty chat instead of pinning it low. */
@@ -331,7 +333,7 @@ export interface AssistantChatProps {
331
333
  /** Optional action rendered beside the voice/send controls. */
332
334
  composerExtraActionButton?: React.ReactNode;
333
335
  /** Show the framework model picker in the shared composer. Defaults to true. */
334
- showModelSelector?: boolean;
336
+ showModelSelector?: boolean | undefined;
335
337
  /** Disable the composer for capability-gated surfaces while still showing history. */
336
338
  composerDisabled?: boolean;
337
339
  /** Block sending while keeping the composer editable. */
@@ -357,14 +359,16 @@ export interface AssistantChatProps {
357
359
  /** Selected effort override for this conversation */
358
360
  selectedEffort?: ReasoningEffort;
359
361
  /** Available engine/model list for the model picker */
360
- availableModels?: Array<{
361
- engine: string;
362
- label: string;
363
- models: string[];
364
- configured: boolean;
365
- }>;
362
+ availableModels?:
363
+ | Array<{
364
+ engine: string;
365
+ label: string;
366
+ models: string[];
367
+ configured: boolean;
368
+ }>
369
+ | undefined;
366
370
  /** Whether the model list is still being resolved. */
367
- modelListLoading?: boolean;
371
+ modelListLoading?: boolean | undefined;
368
372
  /** Whether the model list failed to load. */
369
373
  modelListError?: boolean;
370
374
  /** Retry loading a host-managed model list. */
@@ -374,24 +378,24 @@ export interface AssistantChatProps {
374
378
  /** Callback when user picks an effort from the picker */
375
379
  onEffortChange?: (effort: ReasoningEffort) => void;
376
380
  /** Local or hosted agent runtimes shown above the model list. */
377
- availableAgents?: ComposerAgentOption[];
381
+ availableAgents?: ComposerAgentOption[] | undefined;
378
382
  /** Selected agent runtime identifier. */
379
- selectedAgent?: string;
383
+ selectedAgent?: string | undefined;
380
384
  /** Mark the selected runtime as the hosted tools-only harness mode. */
381
385
  hostedHarness?: boolean;
382
386
  /** Callback when the user picks an agent runtime. */
383
- onAgentChange?: (agent: string) => void;
387
+ onAgentChange?: ((agent: string) => void) | undefined;
384
388
  /**
385
389
  * Optional secondary model menu (e.g. an image-generation model) shown inside
386
390
  * the composer's model picker. Opt-in; chat-only apps omit it.
387
391
  */
388
- imageModelMenu?: ComposerImageModelMenu;
392
+ imageModelMenu?: ComposerImageModelMenu | undefined;
389
393
  /** Callback when user clicks "Fork Chat" in the message actions menu */
390
394
  onForkChat?: () => void | boolean | Promise<void | boolean>;
391
395
  /** Override Builder/provider connect routing for embedded hosts. */
392
- onConnectProvider?: () => void;
396
+ onConnectProvider?: (() => void) | undefined;
393
397
  /** Route local runtime setup through the host's native bridge. */
394
- onConnectLocalRuntime?: (engine: string) => void;
398
+ onConnectLocalRuntime?: ((engine: string) => void) | undefined;
395
399
  /**
396
400
  * Controls the shared composer + menu. Sidebar keeps the full menu by default;
397
401
  * hosts without the sidebar provider stack can use upload-only.
@@ -400,13 +404,13 @@ export interface AssistantChatProps {
400
404
  /** Replace the built-in transport with an AgentKit-native BYO transport. */
401
405
  createTransport?: (context: AssistantChatAdapterContext) => AgentTransport;
402
406
  /** Hide the default guided-question card when another host owns its flow. */
403
- showGuidedQuestions?: boolean;
407
+ showGuidedQuestions?: boolean | undefined;
404
408
  /**
405
409
  * Bring-your-own agent runtime. When supplied, AssistantChat keeps the
406
410
  * standard composer/transcript/tool rendering shell but sends turns through
407
411
  * this runtime instead of the built-in Agent-Native SSE endpoint.
408
412
  */
409
- runtime?: AgentChatRuntime;
413
+ runtime?: AgentChatRuntime | undefined;
410
414
  /**
411
415
  * Explicitly recreate an injected adapter or runtime when its identity
412
416
  * changes. Omit for the production sidebar so parent rerenders do not reset
@@ -446,5 +450,5 @@ export interface AssistantChatProps {
446
450
  * let the reader's own preference apply, which is what surfaces the in-chat
447
451
  * control — a pinned mode hides it rather than leaving a dead menu item.
448
452
  */
449
- thinkingDisplay?: ThinkingDisplay;
453
+ thinkingDisplay?: ThinkingDisplay | undefined;
450
454
  }
@@ -1,4 +1,5 @@
1
1
  // @vitest-environment happy-dom
2
+ import { sendToAgentChat } from "@agent-native/core/client/agent-chat";
2
3
  import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
3
4
  import { act } from "react";
4
5
  import { createRoot, type Root } from "react-dom/client";
@@ -6,6 +7,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
6
7
 
7
8
  import { EmbeddedExtension } from "./EmbeddedExtension.js";
8
9
 
10
+ vi.mock("@agent-native/core/client/agent-chat", () => ({
11
+ sendToAgentChat: vi.fn(),
12
+ }));
13
+
9
14
  vi.mock("./extension-links.js", () => ({
10
15
  extensionFrameNameWithLinkNonce: (nonce: string) => `frame-${nonce}`,
11
16
  // No nonce leaves the frame without a src, so nothing loads.
@@ -17,6 +22,7 @@ describe("EmbeddedExtension height reports", () => {
17
22
  let root: Root;
18
23
 
19
24
  beforeEach(() => {
25
+ vi.mocked(sendToAgentChat).mockClear();
20
26
  vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
21
27
  vi.stubGlobal(
22
28
  "fetch",
@@ -35,6 +41,45 @@ describe("EmbeddedExtension height reports", () => {
35
41
  root = createRoot(container);
36
42
  });
37
43
 
44
+ it("holds a frame-submitted chat turn for host confirmation", async () => {
45
+ await act(async () => {
46
+ root.render(
47
+ <QueryClientProvider client={new QueryClient()}>
48
+ <EmbeddedExtension
49
+ extensionId="extension-1"
50
+ slotId="dashboard-panel-status"
51
+ />
52
+ </QueryClientProvider>,
53
+ );
54
+ });
55
+ await vi.waitFor(() =>
56
+ expect(container.querySelector("iframe")).not.toBeNull(),
57
+ );
58
+ const iframe = container.querySelector("iframe");
59
+ await act(async () => {
60
+ window.dispatchEvent(
61
+ new MessageEvent("message", {
62
+ source: iframe?.contentWindow,
63
+ data: {
64
+ type: "agent-native-send-to-chat",
65
+ message: "Forged on load",
66
+ submit: true,
67
+ },
68
+ }),
69
+ );
70
+ });
71
+ expect(sendToAgentChat).not.toHaveBeenCalled();
72
+ const dialog = document.querySelector('[role="alertdialog"]');
73
+ expect(dialog?.textContent).toContain("Forged on load");
74
+ const send = Array.from(dialog?.querySelectorAll("button") ?? []).find(
75
+ (button) => button.textContent === "Send to agent",
76
+ );
77
+ await act(async () => send?.click());
78
+ expect(sendToAgentChat).toHaveBeenCalledWith(
79
+ expect.objectContaining({ message: "Forged on load", submit: true }),
80
+ );
81
+ });
82
+
38
83
  afterEach(() => {
39
84
  act(() => root.unmount());
40
85
  container.remove();