@agent-native/core 0.168.3 → 0.168.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.js +14 -10
- package/dist/client/AssistantChat.d.ts +2 -0
- package/dist/client/AssistantChat.js +29 -2
- package/dist/client/MultiTabAssistantChat.js +5 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/auth.js +7 -1
- package/dist/server/better-auth-instance.js +18 -3
- package/dist/server/core-routes-plugin.js +12 -0
- package/dist/server/google-credential-check.d.ts +38 -0
- package/dist/server/google-credential-check.js +115 -0
- package/dist/server/google-oauth-credentials.d.ts +28 -0
- package/dist/server/google-oauth-credentials.js +45 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
|
@@ -161,6 +161,8 @@ export declare function shouldAllowAgentChatSurfaceSettingsMode(mode: AgentChatS
|
|
|
161
161
|
export declare function AgentChatSurface({ mode, className, defaultMode, showHeader, showTabBar, isFullscreen, style, chatViewTransition, showPageNewChatButton, ...props }: AgentChatSurfaceProps): React.JSX.Element;
|
|
162
162
|
export interface AgentSidebarProps {
|
|
163
163
|
children: React.ReactNode;
|
|
164
|
+
/** Keep the app surface mounted while temporarily disabling the chat panel. */
|
|
165
|
+
enabled?: boolean;
|
|
164
166
|
/** Placeholder text for the empty chat state */
|
|
165
167
|
emptyStateText?: string;
|
|
166
168
|
/** Suggestion prompts shown when no messages */
|
|
@@ -260,7 +262,7 @@ export interface AgentSidebarProps {
|
|
|
260
262
|
* Wraps app content with a toggleable agent sidebar.
|
|
261
263
|
* Use AgentToggleButton in your header to open/close it.
|
|
262
264
|
*/
|
|
263
|
-
export declare function AgentSidebar({ children, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
|
|
265
|
+
export declare function AgentSidebar({ children, enabled, emptyStateText, suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position, defaultOpen, animateMobile, animateDesktop, chatViewTransition, chatViewTransitionHandoff, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning, onFullscreenRequest, scope, isolateHistoryByScope, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding, }: AgentSidebarProps): React.JSX.Element;
|
|
264
266
|
/**
|
|
265
267
|
* Focus the agent chat composer input.
|
|
266
268
|
* Opens the sidebar if closed, then focuses the text input.
|
|
@@ -1418,7 +1418,7 @@ export function AgentChatSurface({ mode = "panel", className, defaultMode = "cha
|
|
|
1418
1418
|
* Wraps app content with a toggleable agent sidebar.
|
|
1419
1419
|
* Use AgentToggleButton in your header to open/close it.
|
|
1420
1420
|
*/
|
|
1421
|
-
export function AgentSidebar({ children, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
|
|
1421
|
+
export function AgentSidebar({ children, enabled = true, emptyStateText = "How can I help you?", suggestions, dynamicSuggestions, composerToolbarSlot, composerSlot, onComposerTextChange, imageModelMenu, availableAgents, availableModels, modelListLoading, selectedAgent, onAgentChange, onConnectLocalRuntime, onConnectProvider, runtime, adapterReloadKey, threadFooterSlot, defaultSidebarWidth, sidebarWidth, position = "right", defaultOpen = false, animateMobile = true, animateDesktop = true, chatViewTransition = false, chatViewTransitionHandoff = false, storageKey, openStorageKey, apiUrl, agentChatSurface, desktopIdentityUnauthenticated, desktopIdentityAuthenticated, showTabBar = true, suppressInlineOpenApp, composerPlaceholder, openOnChatRunning = false, onFullscreenRequest, scope, isolateHistoryByScope = false, showScopeBadge, browserTabId, threadUrlSync, agentPageHref, suppressFirstRunOnboarding = false, }) {
|
|
1422
1422
|
const staticHostedHarnessEnabled = isHostedHarnessConfigured(injectedAgentNativeConfig().harness);
|
|
1423
1423
|
const hostedHarnessQuery = useActionQuery("get-hosted-harness-config", undefined, { enabled: staticHostedHarnessEnabled });
|
|
1424
1424
|
const hostedHarnessStatus = hostedHarnessQuery.data;
|
|
@@ -1596,7 +1596,8 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1596
1596
|
const [hasFrameSidebarState, setHasFrameSidebarState] = useState(false);
|
|
1597
1597
|
const [backgroundPanelActive, setBackgroundPanelActive] = useState(false);
|
|
1598
1598
|
const [runningTabIds, setRunningTabIds] = useState(() => new Set());
|
|
1599
|
-
const shouldMountPanel =
|
|
1599
|
+
const shouldMountPanel = enabled &&
|
|
1600
|
+
!isPerAppChatHosted &&
|
|
1600
1601
|
!presentationMode &&
|
|
1601
1602
|
(!frameCodeMode || !shouldParentFrameOwnAgentPanel()) &&
|
|
1602
1603
|
(open || backgroundPanelActive || runningTabIds.size > 0);
|
|
@@ -1612,9 +1613,10 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1612
1613
|
if (frameOwned && !hasFrameSidebarState && !isPerAppChatHosted)
|
|
1613
1614
|
return;
|
|
1614
1615
|
dispatchAgentSidebarStateChange({
|
|
1615
|
-
open:
|
|
1616
|
-
|
|
1617
|
-
|
|
1616
|
+
open: enabled &&
|
|
1617
|
+
(isPerAppChatHosted
|
|
1618
|
+
? perAppChatState.open
|
|
1619
|
+
: !presentationMode && (frameOwned ? frameSidebarOpen : open)),
|
|
1618
1620
|
source: frameOwned ? "frame" : "app",
|
|
1619
1621
|
mode: frameOwned ? "code" : "app",
|
|
1620
1622
|
});
|
|
@@ -1626,6 +1628,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1626
1628
|
hasFrameSidebarState,
|
|
1627
1629
|
isPerAppChatHosted,
|
|
1628
1630
|
perAppChatState.open,
|
|
1631
|
+
enabled,
|
|
1629
1632
|
]);
|
|
1630
1633
|
useEffect(() => {
|
|
1631
1634
|
if (!isPerAppChatSidebar)
|
|
@@ -1633,7 +1636,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1633
1636
|
const frameOwned = frameCodeMode && shouldParentFrameOwnAgentPanel();
|
|
1634
1637
|
if (frameOwned && !hasFrameSidebarState)
|
|
1635
1638
|
return;
|
|
1636
|
-
const openState = !presentationMode && (frameOwned ? frameSidebarOpen : open);
|
|
1639
|
+
const openState = enabled && !presentationMode && (frameOwned ? frameSidebarOpen : open);
|
|
1637
1640
|
const message = buildAppChatSidebarStateMessage(openState);
|
|
1638
1641
|
window.dispatchEvent(new CustomEvent(APP_CHAT_SIDEBAR_STATE_EVENT, {
|
|
1639
1642
|
detail: message.data,
|
|
@@ -1656,6 +1659,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1656
1659
|
isPerAppChatSidebar,
|
|
1657
1660
|
open,
|
|
1658
1661
|
presentationMode,
|
|
1662
|
+
enabled,
|
|
1659
1663
|
]);
|
|
1660
1664
|
useEffect(() => {
|
|
1661
1665
|
const preparePanel = () => setBackgroundPanelActive(true);
|
|
@@ -1968,10 +1972,9 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
1968
1972
|
}
|
|
1969
1973
|
};
|
|
1970
1974
|
}, [shouldMountPanel, sidebarAnimationEnabled]);
|
|
1971
|
-
const shouldRenderPanel =
|
|
1972
|
-
? renderAnimatedPanel
|
|
1973
|
-
|
|
1974
|
-
const panelOpen = open && shouldMountPanel;
|
|
1975
|
+
const shouldRenderPanel = enabled &&
|
|
1976
|
+
(sidebarAnimationEnabled ? renderAnimatedPanel : shouldMountPanel);
|
|
1977
|
+
const panelOpen = enabled && open && shouldMountPanel;
|
|
1975
1978
|
const panelLayout = isMobile
|
|
1976
1979
|
? "mobile"
|
|
1977
1980
|
: wideDrawerEnabled
|
|
@@ -2064,6 +2067,7 @@ export function AgentSidebar({ children, emptyStateText = "How can I help you?",
|
|
|
2064
2067
|
return (_jsx(AgentSidebarOnboardingContext.Provider, { value: true, children: _jsxs(RealtimeVoiceModeProvider, { browserTabId: browserTabId, children: [showFirstRunOnboarding && (_jsx(Suspense, { fallback: null, children: _jsx(FirstRunOnboarding, {}) })), _jsxs("div", { className: "agent-sidebar-shell flex min-w-0 flex-1 h-screen overflow-hidden", "data-agent-sidebar-position": effectivePosition, "data-agent-native-hosted-harness-ui": hostedHarnessUi ? "desktop" : undefined, "data-agent-native-hosted-chat": isPerAppChatHosted ? "true" : undefined, "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: [isMobile &&
|
|
2065
2068
|
!isPerAppChatHosted &&
|
|
2066
2069
|
!presentationMode &&
|
|
2070
|
+
enabled &&
|
|
2067
2071
|
(mobileAnimationEnabled ? shouldRenderPanel : open) && (_jsx("div", { className: cn("agent-sidebar-backdrop fixed inset-0 bg-foreground/40", mobileAnimationEnabled && !panelOpen && "pointer-events-none"), "data-agent-sidebar-animation": mobileAnimationEnabled ? "mobile" : undefined, "data-agent-sidebar-state": panelOpen ? "open" : "closed", style: { zIndex: SIDEBAR_OVERLAY_Z_INDEX - 1 }, onClick: () => setOpenPersisted(false) })), shouldMountPanel ? _jsx(URLSync, { browserTabId: browserTabId }) : null, isResizing ? (_jsx("div", { "aria-hidden": "true", className: "agent-sidebar-resize-overlay" })) : null, isLeft && !presentationMode ? sidebar : null, isLeft && !presentationMode ? drawerPlaceholder : null, _jsx("div", { className: "agent-sidebar-main-surface flex flex-1 flex-col overflow-auto min-w-0", "data-agent-sidebar-main-position": position, "data-agent-sidebar-main-state": !isMobile && !presentationMode && panelOpen ? "open" : "closed", "data-agent-sidebar-resizing": isResizing ? "true" : undefined, children: _jsx(ScreenRefreshBoundary, { children: children }) }), !isLeft && !presentationMode ? drawerPlaceholder : null, !isLeft && !presentationMode ? sidebar : null] })] }) }));
|
|
2068
2072
|
}
|
|
2069
2073
|
/**
|
|
@@ -345,6 +345,8 @@ export interface AssistantChatProps {
|
|
|
345
345
|
composerDisabledPlaceholder?: string;
|
|
346
346
|
/** When true, skip the restore skeleton (used for freshly created threads with no messages) */
|
|
347
347
|
isNewThread?: boolean;
|
|
348
|
+
/** Replace an active tab when its saved thread no longer exists. */
|
|
349
|
+
onThreadRestoreNotFound?: () => void;
|
|
348
350
|
/** Defer restore until the owning thread list has reconciled the active id. */
|
|
349
351
|
isThreadStateLoading?: boolean;
|
|
350
352
|
/** Called when a slash command (e.g. /clear, /help) is executed */
|
|
@@ -1387,7 +1387,7 @@ function approvalResolutionIdentity(approvalKey, toolCallId,
|
|
|
1387
1387
|
askId) {
|
|
1388
1388
|
return `${toolCallId ?? ""}\u0000${approvalKey}\u0000${askId ?? ""}`;
|
|
1389
1389
|
}
|
|
1390
|
-
const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateText, suggestions, dynamicSuggestions, threadFooterSlot, emptyStateAddon, showHeader = true, onSwitchToCli, className, apiUrl, tabId, browserTabId, threadId, contextScope, isolateHistoryByScope = false, contextNamespace, isActiveComposer = true, onMessageCountChange, onSaveThread, onGenerateTitle, composerSlot, onComposerTextChange, composerAreaClassName, composerPlaceholder, missingApiKeySetupLayout = "default", composerLayoutVariant = "default", centerComposerWhenEmpty = false, emptyStateDisplay = "default", composerToolbarSlot, composerExtraActionButton, showModelSelector = true, composerDisabled = false, composerDisabledPlaceholder, isNewThread, isThreadStateLoading, onSlashCommand, execMode, onExecModeChange, approvalActions, planModeDisabled, planModeDisabledReason, selectedModel, defaultModel, selectedEngine, selectedEffort, availableModels, modelListLoading, onModelChange, onEffortChange, availableAgents, selectedAgent, hostedHarness, onAgentChange, imageModelMenu, onForkChat, onConnectProvider, onConnectLocalRuntime, plusMenuMode = "full", providerStatusChecksEnabled = true, loadHistoryRepository, historyReloadKey, externalStreaming = false, agentChatSurface = "app", desktopIdentityUnauthenticated = false, desktopIdentityAuthenticated = false, suppressInlineOpenApp = false, }, ref) {
|
|
1390
|
+
const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateText, suggestions, dynamicSuggestions, threadFooterSlot, emptyStateAddon, showHeader = true, onSwitchToCli, className, apiUrl, tabId, browserTabId, threadId, contextScope, isolateHistoryByScope = false, contextNamespace, isActiveComposer = true, onMessageCountChange, onSaveThread, onGenerateTitle, composerSlot, onComposerTextChange, composerAreaClassName, composerPlaceholder, missingApiKeySetupLayout = "default", composerLayoutVariant = "default", centerComposerWhenEmpty = false, emptyStateDisplay = "default", composerToolbarSlot, composerExtraActionButton, showModelSelector = true, composerDisabled = false, composerDisabledPlaceholder, isNewThread, isThreadStateLoading, onSlashCommand, execMode, onExecModeChange, approvalActions, planModeDisabled, planModeDisabledReason, selectedModel, defaultModel, selectedEngine, selectedEffort, availableModels, modelListLoading, onModelChange, onEffortChange, availableAgents, selectedAgent, hostedHarness, onAgentChange, imageModelMenu, onForkChat, onConnectProvider, onConnectLocalRuntime, plusMenuMode = "full", providerStatusChecksEnabled = true, loadHistoryRepository, historyReloadKey, externalStreaming = false, agentChatSurface = "app", desktopIdentityUnauthenticated = false, desktopIdentityAuthenticated = false, onThreadRestoreNotFound, suppressInlineOpenApp = false, }, ref) {
|
|
1391
1391
|
const t = useT();
|
|
1392
1392
|
const thread = useThread();
|
|
1393
1393
|
const threadRuntime = useThreadRuntime();
|
|
@@ -1856,6 +1856,9 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1856
1856
|
setIsRestoring(true);
|
|
1857
1857
|
setRestoreAttempt((attempt) => attempt + 1);
|
|
1858
1858
|
}, [isNewThread, threadId]);
|
|
1859
|
+
const missingThreadNotifiedRef = useRef(null);
|
|
1860
|
+
const desktopIdentityAuthenticatedRef = useRef(desktopIdentityAuthenticated);
|
|
1861
|
+
const desktopIdentityRestoreRetryPendingRef = useRef(false);
|
|
1859
1862
|
// The desktop identity gate and chat restore run in sibling surfaces. If the
|
|
1860
1863
|
// gate wins the race after a masked 404 has already rendered, clear the
|
|
1861
1864
|
// transient not-found card and leave the user at a fresh composer.
|
|
@@ -1864,7 +1867,6 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1864
1867
|
return;
|
|
1865
1868
|
setThreadRestoreError((current) => current === "not-found" ? null : current);
|
|
1866
1869
|
}, [desktopIdentityUnauthenticated]);
|
|
1867
|
-
const desktopIdentityAuthenticatedRef = useRef(desktopIdentityAuthenticated);
|
|
1868
1870
|
useEffect(() => {
|
|
1869
1871
|
const becameAuthenticated = desktopIdentityAuthenticated && !desktopIdentityAuthenticatedRef.current;
|
|
1870
1872
|
desktopIdentityAuthenticatedRef.current = desktopIdentityAuthenticated;
|
|
@@ -1877,6 +1879,7 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1877
1879
|
// A saved-thread request can race the identity handoff and be masked as a
|
|
1878
1880
|
// 404/401/403. Retry once the host confirms the authenticated session so
|
|
1879
1881
|
// the thread is restored without requiring a remount or manual retry.
|
|
1882
|
+
desktopIdentityRestoreRetryPendingRef.current = true;
|
|
1880
1883
|
retryThreadRestore();
|
|
1881
1884
|
}, [
|
|
1882
1885
|
agentChatSurface,
|
|
@@ -1885,6 +1888,30 @@ const AssistantChatInner = forwardRef(function AssistantChatInner({ emptyStateTe
|
|
|
1885
1888
|
retryThreadRestore,
|
|
1886
1889
|
threadId,
|
|
1887
1890
|
]);
|
|
1891
|
+
useEffect(() => {
|
|
1892
|
+
if (threadRestoreError !== "not-found") {
|
|
1893
|
+
desktopIdentityRestoreRetryPendingRef.current = false;
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (!threadId ||
|
|
1897
|
+
!onThreadRestoreNotFound ||
|
|
1898
|
+
missingThreadNotifiedRef.current === threadId ||
|
|
1899
|
+
(agentChatSurface === "desktop" &&
|
|
1900
|
+
(!desktopIdentityAuthenticated ||
|
|
1901
|
+
desktopIdentityUnauthenticated ||
|
|
1902
|
+
desktopIdentityRestoreRetryPendingRef.current))) {
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
missingThreadNotifiedRef.current = threadId;
|
|
1906
|
+
onThreadRestoreNotFound();
|
|
1907
|
+
}, [
|
|
1908
|
+
agentChatSurface,
|
|
1909
|
+
desktopIdentityAuthenticated,
|
|
1910
|
+
desktopIdentityUnauthenticated,
|
|
1911
|
+
onThreadRestoreNotFound,
|
|
1912
|
+
threadId,
|
|
1913
|
+
threadRestoreError,
|
|
1914
|
+
]);
|
|
1888
1915
|
const onSaveThreadRef = useRef(onSaveThread);
|
|
1889
1916
|
onSaveThreadRef.current = onSaveThread;
|
|
1890
1917
|
const onGenerateTitleRef = useRef(onGenerateTitle);
|
|
@@ -1970,7 +1970,11 @@ export function MultiTabAssistantChat({ showTabBar = true, renderHeader, renderO
|
|
|
1970
1970
|
else {
|
|
1971
1971
|
chatRefs.current.delete(tabId);
|
|
1972
1972
|
}
|
|
1973
|
-
}, threadId: tabId, tabId: tabId, browserTabId: browserTabId, contextScope: scope, contextNamespace: contextNamespace, isolateHistoryByScope: isolateHistoryByScope, isActiveComposer: tabId === activeThreadId, apiUrl: apiUrl, isNewThread: newThreadIds.current.has(tabId) || isNewThread(tabId),
|
|
1973
|
+
}, threadId: tabId, tabId: tabId, browserTabId: browserTabId, contextScope: scope, contextNamespace: contextNamespace, isolateHistoryByScope: isolateHistoryByScope, isActiveComposer: tabId === activeThreadId, apiUrl: apiUrl, isNewThread: newThreadIds.current.has(tabId) || isNewThread(tabId), onThreadRestoreNotFound: tabId === activeThreadId &&
|
|
1974
|
+
(props.agentChatSurface !== "desktop" ||
|
|
1975
|
+
props.desktopIdentityAuthenticated === true)
|
|
1976
|
+
? clearActiveTab
|
|
1977
|
+
: undefined, isThreadStateLoading: isLoading, onMessageCountChange: (count) => setMessageCounts((prev) => prev[tabId] === count
|
|
1974
1978
|
? prev
|
|
1975
1979
|
: { ...prev, [tabId]: count }), onSaveThread: handleSaveThread, onGenerateTitle: handleGenerateTitle, onSlashCommand: handleSlashCommand, selectedModel: modelSelection?.model, selectedEngine: modelSelection?.engine, selectedEffort: modelSelection?.effort ?? DEFAULT_REASONING_EFFORT, composerSlot: props.composerSlot, defaultModel: defaultModel, availableModels: availableModels, modelListLoading: modelListLoading, onModelChange: handleModelChange, onEffortChange: handleEffortChange, onForkChat: () => handleForkChat(tabId),
|
|
1976
1980
|
// Sub-agent tabs are read-only: sending a new message from the
|
|
@@ -62,11 +62,11 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
|
|
|
62
62
|
error: string;
|
|
63
63
|
states?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
states: {
|
|
66
67
|
clientId: number;
|
|
67
68
|
state: string;
|
|
68
69
|
}[];
|
|
69
|
-
error?: undefined;
|
|
70
70
|
}>>;
|
|
71
71
|
/**
|
|
72
72
|
* GET /_agent-native/collab/:docId/users
|
|
@@ -77,9 +77,9 @@ export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("
|
|
|
77
77
|
error: string;
|
|
78
78
|
users?: undefined;
|
|
79
79
|
} | {
|
|
80
|
+
error?: undefined;
|
|
80
81
|
users: {
|
|
81
82
|
clientId: number;
|
|
82
83
|
lastSeen: number;
|
|
83
84
|
}[];
|
|
84
|
-
error?: undefined;
|
|
85
85
|
}>>;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -11,14 +11,14 @@
|
|
|
11
11
|
* DELETE /_agent-native/notifications/:id — delete
|
|
12
12
|
*/
|
|
13
13
|
export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
|
|
14
|
+
error?: undefined;
|
|
14
15
|
count: number;
|
|
15
16
|
updated?: undefined;
|
|
16
|
-
error?: undefined;
|
|
17
17
|
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
|
+
error?: undefined;
|
|
19
20
|
count?: undefined;
|
|
20
21
|
updated: number;
|
|
21
|
-
error?: undefined;
|
|
22
22
|
ok?: undefined;
|
|
23
23
|
} | {
|
|
24
24
|
count?: undefined;
|
|
@@ -26,8 +26,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
error: string;
|
|
27
27
|
ok?: undefined;
|
|
28
28
|
} | {
|
|
29
|
+
error?: undefined;
|
|
29
30
|
count?: undefined;
|
|
30
31
|
updated?: undefined;
|
|
31
|
-
error?: undefined;
|
|
32
32
|
ok: boolean;
|
|
33
33
|
}>>;
|
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
+
error?: undefined;
|
|
44
45
|
summary: import("./types.js").TraceSummary;
|
|
45
46
|
spans: import("./types.js").TraceSpan[];
|
|
46
47
|
id?: undefined;
|
|
47
|
-
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
+
error?: undefined;
|
|
50
51
|
summary?: undefined;
|
|
51
52
|
spans?: undefined;
|
|
52
53
|
id: string;
|
|
53
|
-
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
60
|
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
+
error?: undefined;
|
|
62
63
|
summary?: undefined;
|
|
63
64
|
spans?: undefined;
|
|
64
65
|
id?: undefined;
|
|
65
|
-
error?: undefined;
|
|
66
66
|
ok: boolean;
|
|
67
67
|
}>>;
|
|
@@ -75,7 +75,6 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
-
message?: undefined;
|
|
79
78
|
deleted?: undefined;
|
|
80
79
|
found?: undefined;
|
|
81
80
|
id?: undefined;
|
|
@@ -92,8 +91,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
92
91
|
provider?: undefined;
|
|
93
92
|
registered?: undefined;
|
|
94
93
|
label?: undefined;
|
|
95
|
-
} | {
|
|
96
94
|
message?: undefined;
|
|
95
|
+
} | {
|
|
97
96
|
deleted?: undefined;
|
|
98
97
|
id?: undefined;
|
|
99
98
|
providers?: undefined;
|
|
@@ -102,8 +101,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
102
101
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
103
102
|
registered?: undefined;
|
|
104
103
|
label?: undefined;
|
|
105
|
-
} | {
|
|
106
104
|
message?: undefined;
|
|
105
|
+
} | {
|
|
107
106
|
deleted?: undefined;
|
|
108
107
|
providers?: undefined;
|
|
109
108
|
count?: undefined;
|
|
@@ -112,8 +111,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
112
111
|
id: string;
|
|
113
112
|
registered?: undefined;
|
|
114
113
|
label?: undefined;
|
|
115
|
-
} | {
|
|
116
114
|
message?: undefined;
|
|
115
|
+
} | {
|
|
117
116
|
found?: undefined;
|
|
118
117
|
providers?: undefined;
|
|
119
118
|
count?: undefined;
|
|
@@ -122,6 +121,7 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
122
121
|
id: string;
|
|
123
122
|
registered?: undefined;
|
|
124
123
|
label?: undefined;
|
|
124
|
+
message?: undefined;
|
|
125
125
|
} | {
|
|
126
126
|
deleted?: undefined;
|
|
127
127
|
found?: undefined;
|
|
@@ -330,39 +330,39 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
330
330
|
label?: undefined;
|
|
331
331
|
} | {
|
|
332
332
|
id?: undefined;
|
|
333
|
-
count?: undefined;
|
|
334
333
|
deleted?: undefined;
|
|
335
334
|
message?: undefined;
|
|
336
335
|
providers?: undefined;
|
|
336
|
+
count?: undefined;
|
|
337
337
|
found: boolean;
|
|
338
338
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
339
339
|
registered?: undefined;
|
|
340
340
|
label?: undefined;
|
|
341
341
|
} | {
|
|
342
|
-
count?: undefined;
|
|
343
342
|
deleted?: undefined;
|
|
344
343
|
message?: undefined;
|
|
345
344
|
providers?: undefined;
|
|
345
|
+
count?: undefined;
|
|
346
346
|
provider?: undefined;
|
|
347
347
|
found: boolean;
|
|
348
348
|
id: string;
|
|
349
349
|
registered?: undefined;
|
|
350
350
|
label?: undefined;
|
|
351
351
|
} | {
|
|
352
|
-
count?: undefined;
|
|
353
352
|
found?: undefined;
|
|
354
353
|
message?: undefined;
|
|
355
354
|
providers?: undefined;
|
|
355
|
+
count?: undefined;
|
|
356
356
|
provider?: undefined;
|
|
357
357
|
deleted: boolean;
|
|
358
358
|
id: string;
|
|
359
359
|
registered?: undefined;
|
|
360
360
|
label?: undefined;
|
|
361
361
|
} | {
|
|
362
|
-
count?: undefined;
|
|
363
362
|
deleted?: undefined;
|
|
364
363
|
found?: undefined;
|
|
365
364
|
providers?: undefined;
|
|
365
|
+
count?: undefined;
|
|
366
366
|
provider?: undefined;
|
|
367
367
|
registered: boolean;
|
|
368
368
|
id: string;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,16 +34,16 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
status?: undefined;
|
|
38
37
|
ok?: undefined;
|
|
38
|
+
status?: undefined;
|
|
39
39
|
} | {
|
|
40
40
|
ok: boolean;
|
|
41
41
|
status: string;
|
|
42
42
|
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
+
ok?: undefined;
|
|
44
45
|
error: string;
|
|
45
46
|
removed?: undefined;
|
|
46
|
-
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
48
|
ok: boolean;
|
|
49
49
|
removed: boolean;
|
|
@@ -54,9 +54,9 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
54
54
|
* or the current stored value without changing anything.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
+
ok?: undefined;
|
|
57
58
|
error: string;
|
|
58
59
|
note?: undefined;
|
|
59
|
-
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
61
|
ok: boolean;
|
|
62
62
|
note?: undefined;
|
package/dist/server/auth.js
CHANGED
|
@@ -2277,8 +2277,14 @@ function createAuthGuardFn(app) {
|
|
|
2277
2277
|
// health exposes only aggregate readiness and a trivial `SELECT 1`.
|
|
2278
2278
|
// Without this bypass the gate below 401s anonymous /_agent-native/*
|
|
2279
2279
|
// requests before either probe can run.
|
|
2280
|
-
if (p === "/_agent-native/ping" ||
|
|
2280
|
+
if (p === "/_agent-native/ping" ||
|
|
2281
|
+
p === "/_agent-native/health" ||
|
|
2282
|
+
// The credential self-check is read by an unauthenticated monitor. Without
|
|
2283
|
+
// this the gate 401s it, the monitor reads a non-JSON body as "route not
|
|
2284
|
+
// deployed", and the check silently never runs.
|
|
2285
|
+
p === "/_agent-native/health/google") {
|
|
2281
2286
|
return;
|
|
2287
|
+
}
|
|
2282
2288
|
if (getMethod(event) === "GET" && p.startsWith("/_agent-native/avatar/")) {
|
|
2283
2289
|
return;
|
|
2284
2290
|
}
|
|
@@ -31,7 +31,7 @@ import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
|
|
|
31
31
|
import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
|
|
32
32
|
import { renderMagicLinkEmail, renderResetPasswordEmail, renderVerifySignupEmail, } from "./email-templates.js";
|
|
33
33
|
import { getEmailReadiness, sendEmail } from "./email.js";
|
|
34
|
-
import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
34
|
+
import { recordActiveGoogleSignInCredentials, resolveGoogleSignInCredentials, } from "./google-oauth-credentials.js";
|
|
35
35
|
import { readMagicLinkSignupAttribution } from "./magic-link-attribution.js";
|
|
36
36
|
import { getRequestContext, hasContinuationLocalRequestContext, } from "./request-context.js";
|
|
37
37
|
export { getAuthLoginMode, resolveAuthLoginMode, resolveAuthLoginModeFromReadiness, } from "./auth-login-mode.js";
|
|
@@ -865,14 +865,28 @@ async function createBetterAuthInstance(config) {
|
|
|
865
865
|
...config?.socialProviders,
|
|
866
866
|
};
|
|
867
867
|
const extraScopes = config?.googleScopes ?? [];
|
|
868
|
+
const configuredGoogleProvider = typeof config?.socialProviders?.google === "function"
|
|
869
|
+
? await config.socialProviders.google()
|
|
870
|
+
: config?.socialProviders?.google;
|
|
871
|
+
const configuredGoogleCredentials = configuredGoogleProvider &&
|
|
872
|
+
typeof configuredGoogleProvider.clientId === "string" &&
|
|
873
|
+
typeof configuredGoogleProvider.clientSecret === "string"
|
|
874
|
+
? {
|
|
875
|
+
clientId: configuredGoogleProvider.clientId,
|
|
876
|
+
clientSecret: configuredGoogleProvider.clientSecret,
|
|
877
|
+
}
|
|
878
|
+
: null;
|
|
868
879
|
const googleCredentials = extraScopes.length > 0
|
|
869
880
|
? process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET
|
|
870
881
|
? {
|
|
871
882
|
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
872
883
|
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
873
884
|
}
|
|
874
|
-
:
|
|
875
|
-
: resolveGoogleSignInCredentials();
|
|
885
|
+
: configuredGoogleCredentials
|
|
886
|
+
: (resolveGoogleSignInCredentials() ?? configuredGoogleCredentials);
|
|
887
|
+
// Publish the pair actually wired to the provider so the credential
|
|
888
|
+
// self-check probes what the callback uses, not what it would prefer.
|
|
889
|
+
recordActiveGoogleSignInCredentials(googleCredentials);
|
|
876
890
|
if (googleCredentials) {
|
|
877
891
|
// When the template requests broader scopes (Gmail, Calendar, etc.)
|
|
878
892
|
// ask for them on the primary sign-in flow so a separate "Connect
|
|
@@ -884,6 +898,7 @@ async function createBetterAuthInstance(config) {
|
|
|
884
898
|
const baseScopes = ["openid", "email", "profile"];
|
|
885
899
|
const mergedScopes = Array.from(new Set([...baseScopes, ...extraScopes]));
|
|
886
900
|
socialProviders.google = {
|
|
901
|
+
...(configuredGoogleProvider ?? {}),
|
|
887
902
|
clientId: googleCredentials.clientId,
|
|
888
903
|
clientSecret: googleCredentials.clientSecret,
|
|
889
904
|
...(extraScopes.length > 0
|
|
@@ -53,6 +53,7 @@ import { createEmbedStartRouteHandler } from "./embed-route.js";
|
|
|
53
53
|
import { shouldReportError } from "./error-noise-filter.js";
|
|
54
54
|
import { FRAMEWORK_AUTH_EARLY_PATHS, getH3App, awaitBootstrap, markDefaultPluginProvided, markFrameworkRoutesReadyBeforeBootstrap, trackPluginInit, } from "./framework-request-handler.js";
|
|
55
55
|
import { createGatewayAccessCheckHandler } from "./gateway-access-check.js";
|
|
56
|
+
import { checkGoogleSignInCredential } from "./google-credential-check.js";
|
|
56
57
|
import { getAppBasePath, getOrigin } from "./google-oauth.js";
|
|
57
58
|
import { createGoogleRealtimeSessionHandler } from "./google-realtime-session.js";
|
|
58
59
|
import { readBody, DEFAULT_UPLOAD_MAX_FILE_BYTES, isAllowedUploadMimeType, } from "./h3-helpers.js";
|
|
@@ -1002,6 +1003,17 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
1002
1003
|
}));
|
|
1003
1004
|
}
|
|
1004
1005
|
if (!options.disableHealth) {
|
|
1006
|
+
// Registered before `/health` because h3 matches by prefix, and the
|
|
1007
|
+
// health handler would otherwise swallow this path.
|
|
1008
|
+
getH3App(nitroApp).use(`${P}/health/google`, defineEventHandler(async (event) => {
|
|
1009
|
+
setResponseHeader(event, "cache-control", "no-store");
|
|
1010
|
+
const result = await checkGoogleSignInCredential();
|
|
1011
|
+
// `invalid` is the fleet-wide outage shape: the deploy is up and
|
|
1012
|
+
// healthy while nobody can sign in. Page on it.
|
|
1013
|
+
if (result.status === "invalid")
|
|
1014
|
+
setResponseStatus(event, 503);
|
|
1015
|
+
return result;
|
|
1016
|
+
}));
|
|
1005
1017
|
getH3App(nitroApp).use(`${P}/health`, defineEventHandler(async (event) => {
|
|
1006
1018
|
setResponseHeader(event, "cache-control", "no-store");
|
|
1007
1019
|
const schema = event.url?.searchParams.get("schema") === "1" ||
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type GoogleCredentialStatus =
|
|
2
|
+
/** Google accepted the client id and secret. */
|
|
3
|
+
"valid"
|
|
4
|
+
/** Google rejected the client id or secret — sign-in is broken. */
|
|
5
|
+
| "invalid"
|
|
6
|
+
/** No sign-in credentials are configured on this deploy. */
|
|
7
|
+
| "unconfigured"
|
|
8
|
+
/** Google could not be reached, or answered something unrecognised. */
|
|
9
|
+
| "unknown";
|
|
10
|
+
export interface GoogleCredentialCheck {
|
|
11
|
+
status: GoogleCredentialStatus;
|
|
12
|
+
clientId: string | null;
|
|
13
|
+
/** Both credential pairs are set to different Google clients. */
|
|
14
|
+
mismatchedPairs: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Where the probed pair came from. `active` is the pair Better Auth wired to
|
|
17
|
+
* the provider; `preferred` means auth had not initialised yet and this fell
|
|
18
|
+
* back to the preferred pair, which a scoped template may not use.
|
|
19
|
+
*/
|
|
20
|
+
credentialSource: "active" | "preferred";
|
|
21
|
+
/** Google's `error` field, or the transport failure, when there was one. */
|
|
22
|
+
reason: string | null;
|
|
23
|
+
checkedAt: number;
|
|
24
|
+
}
|
|
25
|
+
/** Test seam: drop the memoised result. */
|
|
26
|
+
export declare function resetGoogleCredentialCheckCache(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Ask Google whether this deploy's sign-in credentials still authenticate.
|
|
29
|
+
*
|
|
30
|
+
* The app's own callback collapses every Google failure into one error page,
|
|
31
|
+
* so a wrong secret is invisible from outside. This is the signal a monitor
|
|
32
|
+
* can read: it needs no browser, no consent grant, and no access to the secret
|
|
33
|
+
* beyond the process that already holds it.
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkGoogleSignInCredential(options?: {
|
|
36
|
+
ttlMs?: number;
|
|
37
|
+
now?: () => number;
|
|
38
|
+
}): Promise<GoogleCredentialCheck>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { describeGoogleSignInCredentialPairs, getActiveGoogleSignInCredentials, resolveGoogleSignInCredentials, } from "./google-oauth-credentials.js";
|
|
2
|
+
const GOOGLE_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";
|
|
3
|
+
/**
|
|
4
|
+
* Google authenticates the client before it looks at the code, so a
|
|
5
|
+
* deliberately invalid code separates the two failures: `invalid_client` means
|
|
6
|
+
* the secret is wrong, `invalid_grant` means the secret authenticated and only
|
|
7
|
+
* the code was rejected. The redirect_uri is never reached and is a constant.
|
|
8
|
+
*/
|
|
9
|
+
const PROBE_CODE = "agent-native-credential-probe";
|
|
10
|
+
const PROBE_REDIRECT_URI = "https://example.com/agent-native-credential-probe";
|
|
11
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
12
|
+
const REQUEST_TIMEOUT_MS = 10_000;
|
|
13
|
+
let cached = null;
|
|
14
|
+
/** Test seam: drop the memoised result. */
|
|
15
|
+
export function resetGoogleCredentialCheckCache() {
|
|
16
|
+
cached = null;
|
|
17
|
+
}
|
|
18
|
+
async function probeGoogle(clientId, clientSecret) {
|
|
19
|
+
let response;
|
|
20
|
+
try {
|
|
21
|
+
response = await fetch(GOOGLE_TOKEN_ENDPOINT, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
24
|
+
body: new URLSearchParams({
|
|
25
|
+
grant_type: "authorization_code",
|
|
26
|
+
code: PROBE_CODE,
|
|
27
|
+
client_id: clientId,
|
|
28
|
+
client_secret: clientSecret,
|
|
29
|
+
redirect_uri: PROBE_REDIRECT_URI,
|
|
30
|
+
}),
|
|
31
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Unreachable is not the same as wrong. Reporting "valid" here would
|
|
36
|
+
// recreate the exact blind spot this check exists to remove.
|
|
37
|
+
return {
|
|
38
|
+
status: "unknown",
|
|
39
|
+
reason: error instanceof Error ? error.message : "fetch failed",
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
let error = null;
|
|
43
|
+
try {
|
|
44
|
+
const body = (await response.json());
|
|
45
|
+
if (typeof body?.error === "string")
|
|
46
|
+
error = body.error;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
error = null;
|
|
50
|
+
}
|
|
51
|
+
if (error === "invalid_grant")
|
|
52
|
+
return { status: "valid", reason: error };
|
|
53
|
+
if (error === "invalid_client")
|
|
54
|
+
return { status: "invalid", reason: error };
|
|
55
|
+
if (response.ok) {
|
|
56
|
+
// A probe code must never mint a token. Something is not the API we think.
|
|
57
|
+
return { status: "unknown", reason: "unexpected token grant" };
|
|
58
|
+
}
|
|
59
|
+
return { status: "unknown", reason: error ?? `http ${response.status}` };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Ask Google whether this deploy's sign-in credentials still authenticate.
|
|
63
|
+
*
|
|
64
|
+
* The app's own callback collapses every Google failure into one error page,
|
|
65
|
+
* so a wrong secret is invisible from outside. This is the signal a monitor
|
|
66
|
+
* can read: it needs no browser, no consent grant, and no access to the secret
|
|
67
|
+
* beyond the process that already holds it.
|
|
68
|
+
*/
|
|
69
|
+
export async function checkGoogleSignInCredential(options) {
|
|
70
|
+
const now = options?.now ?? Date.now;
|
|
71
|
+
const ttlMs = options?.ttlMs ?? DEFAULT_TTL_MS;
|
|
72
|
+
const at = now();
|
|
73
|
+
// Prefer what Better Auth actually wired up. A template requesting broader
|
|
74
|
+
// scopes runs on GOOGLE_CLIENT_*, so re-deriving the preferred pair here
|
|
75
|
+
// would test a credential the callback never touches.
|
|
76
|
+
const active = getActiveGoogleSignInCredentials();
|
|
77
|
+
if (cached &&
|
|
78
|
+
cached.expiresAt > at &&
|
|
79
|
+
cached.activeCredentialsVersion === active.version) {
|
|
80
|
+
return cached.value;
|
|
81
|
+
}
|
|
82
|
+
const pairs = describeGoogleSignInCredentialPairs();
|
|
83
|
+
const credentials = active.recorded
|
|
84
|
+
? active.credentials
|
|
85
|
+
: resolveGoogleSignInCredentials();
|
|
86
|
+
const credentialSource = active.recorded
|
|
87
|
+
? "active"
|
|
88
|
+
: "preferred";
|
|
89
|
+
const value = credentials
|
|
90
|
+
? {
|
|
91
|
+
...(await probeGoogle(credentials.clientId, credentials.clientSecret)),
|
|
92
|
+
clientId: credentials.clientId,
|
|
93
|
+
mismatchedPairs: pairs.mismatched,
|
|
94
|
+
credentialSource,
|
|
95
|
+
checkedAt: at,
|
|
96
|
+
}
|
|
97
|
+
: {
|
|
98
|
+
status: "unconfigured",
|
|
99
|
+
clientId: null,
|
|
100
|
+
mismatchedPairs: pairs.mismatched,
|
|
101
|
+
credentialSource,
|
|
102
|
+
reason: null,
|
|
103
|
+
checkedAt: at,
|
|
104
|
+
};
|
|
105
|
+
// Only memoise answers Google actually gave. Caching a transport failure for
|
|
106
|
+
// five minutes would hide a recovery for five minutes.
|
|
107
|
+
if (value.status !== "unknown") {
|
|
108
|
+
cached = {
|
|
109
|
+
value,
|
|
110
|
+
expiresAt: at + ttlMs,
|
|
111
|
+
activeCredentialsVersion: active.version,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
@@ -44,6 +44,34 @@ export declare function resolveGoogleProviderCredentialCandidatesWithReader(opti
|
|
|
44
44
|
*/
|
|
45
45
|
export declare function resolveGoogleSignInCredentials(): GoogleOAuthCredentials | null;
|
|
46
46
|
export declare function hasGoogleSignInCredentials(): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Record the pair Better Auth actually handed to the Google provider.
|
|
49
|
+
*
|
|
50
|
+
* The effective pair is not always the preferred one: a template asking for
|
|
51
|
+
* broader scopes is wired to GOOGLE_CLIENT_ID/SECRET instead. Anything testing
|
|
52
|
+
* "the credential the callback will use" must read this rather than
|
|
53
|
+
* re-deriving it, or it will verify a pair nothing reads and report healthy.
|
|
54
|
+
*/
|
|
55
|
+
export declare function recordActiveGoogleSignInCredentials(credentials: GoogleOAuthCredentials | null): void;
|
|
56
|
+
/** Test seam: forget what Better Auth wired, as if it had not initialised. */
|
|
57
|
+
export declare function resetActiveGoogleSignInCredentials(): void;
|
|
58
|
+
export declare function getActiveGoogleSignInCredentials(): {
|
|
59
|
+
credentials: GoogleOAuthCredentials | null;
|
|
60
|
+
recorded: boolean;
|
|
61
|
+
version: number;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Which sign-in credential pairs are configured, and whether they disagree.
|
|
65
|
+
*
|
|
66
|
+
* `mismatched` is the state that hid the 2026-08-20 outage: two pairs naming
|
|
67
|
+
* different Google clients, where only the winner is ever read. Callers report
|
|
68
|
+
* it; the resolver only warns.
|
|
69
|
+
*/
|
|
70
|
+
export declare function describeGoogleSignInCredentialPairs(): {
|
|
71
|
+
signInClientId: string | null;
|
|
72
|
+
providerClientId: string | null;
|
|
73
|
+
mismatched: boolean;
|
|
74
|
+
};
|
|
47
75
|
export declare function resolveGoogleProviderCredentials(): GoogleOAuthCredentials | null;
|
|
48
76
|
export declare function resolveGoogleLegacyProviderCredentials(): GoogleOAuthCredentials | null;
|
|
49
77
|
export declare function resolveGoogleProviderCredentialCandidates(): GoogleOAuthCredentials[];
|
|
@@ -75,6 +75,51 @@ export function resolveGoogleSignInCredentials() {
|
|
|
75
75
|
export function hasGoogleSignInCredentials() {
|
|
76
76
|
return resolveGoogleSignInCredentials() !== null;
|
|
77
77
|
}
|
|
78
|
+
let activeSignInCredentials = null;
|
|
79
|
+
let activeSignInCredentialsRecorded = false;
|
|
80
|
+
let activeSignInCredentialsVersion = 0;
|
|
81
|
+
/**
|
|
82
|
+
* Record the pair Better Auth actually handed to the Google provider.
|
|
83
|
+
*
|
|
84
|
+
* The effective pair is not always the preferred one: a template asking for
|
|
85
|
+
* broader scopes is wired to GOOGLE_CLIENT_ID/SECRET instead. Anything testing
|
|
86
|
+
* "the credential the callback will use" must read this rather than
|
|
87
|
+
* re-deriving it, or it will verify a pair nothing reads and report healthy.
|
|
88
|
+
*/
|
|
89
|
+
export function recordActiveGoogleSignInCredentials(credentials) {
|
|
90
|
+
activeSignInCredentials = credentials;
|
|
91
|
+
activeSignInCredentialsRecorded = true;
|
|
92
|
+
activeSignInCredentialsVersion += 1;
|
|
93
|
+
}
|
|
94
|
+
/** Test seam: forget what Better Auth wired, as if it had not initialised. */
|
|
95
|
+
export function resetActiveGoogleSignInCredentials() {
|
|
96
|
+
activeSignInCredentials = null;
|
|
97
|
+
activeSignInCredentialsRecorded = false;
|
|
98
|
+
activeSignInCredentialsVersion += 1;
|
|
99
|
+
}
|
|
100
|
+
export function getActiveGoogleSignInCredentials() {
|
|
101
|
+
return {
|
|
102
|
+
credentials: activeSignInCredentials,
|
|
103
|
+
recorded: activeSignInCredentialsRecorded,
|
|
104
|
+
version: activeSignInCredentialsVersion,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Which sign-in credential pairs are configured, and whether they disagree.
|
|
109
|
+
*
|
|
110
|
+
* `mismatched` is the state that hid the 2026-08-20 outage: two pairs naming
|
|
111
|
+
* different Google clients, where only the winner is ever read. Callers report
|
|
112
|
+
* it; the resolver only warns.
|
|
113
|
+
*/
|
|
114
|
+
export function describeGoogleSignInCredentialPairs() {
|
|
115
|
+
const signIn = readCredentialPair("GOOGLE_SIGN_IN_CLIENT_ID", "GOOGLE_SIGN_IN_CLIENT_SECRET");
|
|
116
|
+
const provider = readCredentialPair("GOOGLE_CLIENT_ID", "GOOGLE_CLIENT_SECRET");
|
|
117
|
+
return {
|
|
118
|
+
signInClientId: signIn?.clientId ?? null,
|
|
119
|
+
providerClientId: provider?.clientId ?? null,
|
|
120
|
+
mismatched: Boolean(signIn && provider && signIn.clientId !== provider.clientId),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
78
123
|
export function resolveGoogleProviderCredentials() {
|
|
79
124
|
return readCredentialPair(GOOGLE_PRIMARY_PROVIDER_CREDENTIAL_KEYS.clientIdKey, GOOGLE_PRIMARY_PROVIDER_CREDENTIAL_KEYS.clientSecretKey);
|
|
80
125
|
}
|
|
@@ -26,8 +26,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
|
|
|
26
26
|
expiresAt?: undefined;
|
|
27
27
|
ttlSeconds?: undefined;
|
|
28
28
|
} | {
|
|
29
|
+
error?: undefined;
|
|
29
30
|
token: string;
|
|
30
31
|
expiresAt: string;
|
|
31
32
|
ttlSeconds: number;
|
|
32
|
-
error?: undefined;
|
|
33
33
|
}>>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.168.
|
|
3
|
+
"version": "0.168.5",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|