@agent-native/core 0.149.0 → 0.149.2
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +2 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +150 -46
- package/corpus/templates/analytics/app/global.css +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +37 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -0
- package/corpus/templates/analytics/changelog/2026-08-10-dashboard-tables-can-be-copied-and-sorted-by-multiple-column.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/assets/app/global.css +1 -1
- package/corpus/templates/assets/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/brain/app/global.css +1 -1
- package/corpus/templates/brain/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-10-clips-desktop-sign-in-now-starts-with-magic-links-and-google.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.css +1 -0
- package/corpus/templates/clips/desktop/src/app.tsx +274 -137
- package/corpus/templates/clips/desktop/src/styles.css +35 -0
- package/corpus/templates/content/app/global.css +1 -0
- package/corpus/templates/crm/app/global.css +1 -1
- package/corpus/templates/crm/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/forms/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/app/global.css +1 -0
- package/corpus/templates/plan/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/corpus/templates/plan/server/ui-plan-html.ts +4 -1
- package/corpus/templates/slides/AGENTS.md +11 -10
- package/corpus/templates/slides/actions/add-deck.ts +2 -0
- package/corpus/templates/slides/actions/add-slide.ts +15 -1
- package/corpus/templates/slides/actions/create-deck.ts +18 -6
- package/corpus/templates/slides/actions/get-deck.ts +29 -1
- package/corpus/templates/slides/actions/patch-deck.ts +139 -10
- package/corpus/templates/slides/actions/save-deck.ts +24 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +46 -0
- package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +194 -3
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +10 -1
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +134 -618
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +74 -63
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -1
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +13 -1
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +56 -174
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +239 -116
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -7
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +45 -0
- package/corpus/templates/slides/app/components/layout/AgentWorkIndicator.tsx +1 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +34 -35
- package/corpus/templates/slides/app/components/layout/layout-route-policy.ts +2 -5
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +45 -40
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +63 -16
- package/corpus/templates/slides/app/hooks/use-generating-slide-preview.ts +25 -3
- package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +5 -4
- package/corpus/templates/slides/app/lib/export-pdf-client.ts +84 -25
- package/corpus/templates/slides/app/lib/import-uploaded-deck.ts +11 -0
- package/corpus/templates/slides/app/lib/recent-references.ts +23 -1
- package/corpus/templates/slides/app/lib/slide-animation-elements.ts +41 -3
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +16 -27
- package/corpus/templates/slides/app/pages/Index.tsx +256 -98
- package/corpus/templates/slides/changelog/2026-08-08-google-slides-export-now-tells-you-why-a-deck-fell-back-to-a.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-delete-key-now-removes-a-selected-image.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-editing-a-slide-in-an-imported-deck-no-longer-fails-to-save.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-fixed-pdf-and-custom-size-powerpoint-imports-rendering-with-.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-images-from-sites-without-cors-no-longer-export-blank.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-pdf-headings-no-longer-lose-the-space-between-color.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-imported-slide-text-no-longer-renders-too-large-for-its-box.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-large-decks-no-longer-render-slowly-or-glitch-in-the-editor.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-revise-highlighted-slide-text-with-ai.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-09-the-deck-editor-no-longer-stays-dimmed-after-the-window-narro.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-ai-editing-avatar-badge.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-generated-decks-keep-a-clear-human-readable-title.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-new-deck-creation-now-shows-progress-opens-the-editor-direct.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-presentation-reveals-now-follow-valid-targets.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-editing-controls-are-consolidated-into-one-focused-me.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-now-verify-and-restyle-every-imported-slide-in-one-pa.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-slides-only-suggests-a-google-connection-for-relevant-reques.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-10-the-google-slides-import-option-is-now-labeled-slides.md +6 -0
- package/corpus/templates/slides/server/handlers/import/html-converter.ts +115 -22
- package/corpus/templates/slides/server/handlers/import/pdf-fidelity-parser.ts +11 -2
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/slides/server/lib/fetch-remote-image.ts +192 -0
- package/corpus/templates/slides/server/lib/image-proxy-url.ts +192 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -3
- package/corpus/templates/slides/server/routes/api/image-proxy.get.ts +65 -0
- package/corpus/templates/slides/shared/api.ts +2 -1
- package/corpus/templates/slides/shared/deck-title.ts +135 -0
- package/corpus/templates/tasks/app/components/tasks/TaskListRow.tsx +4 -1
- package/dist/agent/engine/ai-sdk-engine.js +1 -1
- package/dist/agent/production-agent.d.ts +21 -1
- package/dist/agent/production-agent.js +129 -10
- package/dist/agent/tool-approval-store.d.ts +18 -0
- package/dist/agent/tool-approval-store.js +122 -0
- package/dist/agent/types.d.ts +1 -1
- package/dist/cli/code-agent-executor.js +11 -2
- package/dist/client/AgentPanel.js +31 -6
- package/dist/client/AssistantChat.d.ts +3 -2
- package/dist/client/AssistantChat.js +7 -20
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat-adapter.js +11 -0
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/chat/markdown-renderer.js +14 -2
- package/dist/client/chat/message-components.d.ts +6 -2
- package/dist/client/chat/message-components.js +25 -47
- package/dist/client/chat/tool-call-display.d.ts +3 -5
- package/dist/client/chat/tool-call-display.js +7 -212
- package/dist/client/chat-first/apps-rail.js +3 -3
- package/dist/client/chat-first/browser-pane.js +4 -1
- package/dist/client/chat-first/chat-history.d.ts +3 -1
- package/dist/client/chat-first/chat-history.js +2 -2
- package/dist/client/chat-first/surface-tabs.d.ts +1 -1
- package/dist/client/chat-first/surface-tabs.js +52 -41
- package/dist/client/chat-first/types.d.ts +3 -0
- package/dist/client/chat-first.d.ts +10 -0
- package/dist/client/chat-first.js +37 -2
- package/dist/client/components/ui/message-scroller.js +4 -1
- package/dist/client/i18n.js +3 -1
- package/dist/client/progress/RunsTray.js +14 -1
- package/dist/client/resources/McpConnectionSuggestion.js +9 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/use-chat-models.js +156 -80
- package/dist/integrations/webhook-handler.js +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/auth.js +262 -31
- package/dist/server/onboarding-html.js +56 -9
- package/dist/shared/reasoning-effort.d.ts +8 -8
- package/dist/shared/reasoning-effort.js +10 -10
- package/dist/shared/streaming-text-smoothing.js +41 -11
- package/dist/styles/agent-native.css +19 -32
- package/dist/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
- package/docs/content/template-chat.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/changelog/2026-08-10-full-page-chat-now-uses-the-available-width-up-to-1000px-for.md +6 -0
|
@@ -928,7 +928,7 @@ async function processIncomingMessage(incoming, options, opts = {}) {
|
|
|
928
928
|
// leave a user-facing Slack reply empty.
|
|
929
929
|
maxOutputTokens: resolveMainChatMaxOutputTokens(resolvedModel),
|
|
930
930
|
// Explicitly resolve the normal chat default so an empty-final
|
|
931
|
-
// retry can step its
|
|
931
|
+
// retry can step its effort down rather than
|
|
932
932
|
// repeatedly letting the engine choose Medium.
|
|
933
933
|
reasoningEffort: normalizeReasoningEffortForRequest(resolvedModel, undefined),
|
|
934
934
|
}, undefined, {
|
|
@@ -93,8 +93,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
93
93
|
label?: undefined;
|
|
94
94
|
message?: undefined;
|
|
95
95
|
} | {
|
|
96
|
-
count?: undefined;
|
|
97
96
|
id?: undefined;
|
|
97
|
+
count?: undefined;
|
|
98
98
|
deleted?: undefined;
|
|
99
99
|
providers?: undefined;
|
|
100
100
|
found: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -37,17 +37,17 @@ export declare function createWriteSecretHandler(): import("h3").EventHandlerWit
|
|
|
37
37
|
ok?: undefined;
|
|
38
38
|
status?: undefined;
|
|
39
39
|
} | {
|
|
40
|
+
error?: undefined;
|
|
40
41
|
ok: boolean;
|
|
41
42
|
status: string;
|
|
42
|
-
error?: undefined;
|
|
43
43
|
} | {
|
|
44
44
|
ok?: undefined;
|
|
45
45
|
error: string;
|
|
46
46
|
removed?: undefined;
|
|
47
47
|
} | {
|
|
48
|
+
error?: undefined;
|
|
48
49
|
ok: boolean;
|
|
49
50
|
removed: boolean;
|
|
50
|
-
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — validate an optional candidate value
|
|
@@ -58,13 +58,13 @@ export declare function createTestSecretHandler(): import("h3").EventHandlerWith
|
|
|
58
58
|
error: string;
|
|
59
59
|
note?: undefined;
|
|
60
60
|
} | {
|
|
61
|
+
error?: undefined;
|
|
61
62
|
ok: boolean;
|
|
62
63
|
note?: undefined;
|
|
63
|
-
error?: undefined;
|
|
64
64
|
} | {
|
|
65
|
+
error?: undefined;
|
|
65
66
|
ok: boolean;
|
|
66
67
|
note: string;
|
|
67
|
-
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,11 +95,11 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
+
error?: undefined;
|
|
98
99
|
ok: boolean;
|
|
99
100
|
key: string;
|
|
100
|
-
error?: undefined;
|
|
101
101
|
} | {
|
|
102
|
+
error?: undefined;
|
|
102
103
|
ok: boolean;
|
|
103
104
|
removed: boolean;
|
|
104
|
-
error?: undefined;
|
|
105
105
|
}>>;
|
package/dist/server/auth.js
CHANGED
|
@@ -63,7 +63,7 @@ import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
|
|
|
63
63
|
import { getAllowedCorsOrigin, readCorsAllowedOrigins, } from "./cors-origins.js";
|
|
64
64
|
import { readDesktopSso, writeDesktopSso, clearDesktopSso, } from "./desktop-sso.js";
|
|
65
65
|
import { resolveGoogleSignInCredentials } from "./google-oauth-credentials.js";
|
|
66
|
-
import { isElectron as isElectronRequest, getAppBasePath, getAppUrl, getOrigin, encodeOAuthState, decodeOAuthState, createOAuthSession, oauthCallbackResponse, oauthErrorPage, resolveOAuthRedirectUri, isAllowedOAuthRedirectUri, } from "./google-oauth.js";
|
|
66
|
+
import { isElectron as isElectronRequest, getAppBasePath, getAppUrl, getOrigin, encodeOAuthState, decodeOAuthState, createOAuthSession, oauthCallbackResponse, oauthDesktopExchangePage, oauthErrorPage, resolveOAuthRedirectUri, isAllowedOAuthRedirectUri, } from "./google-oauth.js";
|
|
67
67
|
// Pure env-read feature switch from a leaf module (no dependency back on
|
|
68
68
|
// auth.ts), so the guard and the SSO route handler share one validator and
|
|
69
69
|
// can never disagree about whether federated SSO is enabled.
|
|
@@ -1025,6 +1025,8 @@ function areGenericGoogleOAuthRoutesEnabled(app) {
|
|
|
1025
1025
|
}
|
|
1026
1026
|
const _desktopExchanges = new Map();
|
|
1027
1027
|
const DESKTOP_EXCHANGE_ERROR_PREFIX = "__error__::";
|
|
1028
|
+
const DESKTOP_MAGIC_LINK_CHALLENGE_PREFIX = "__magic-link-challenge__::";
|
|
1029
|
+
const DESKTOP_MAGIC_LINK_EXCHANGE_PREFIX = "__magic-link-exchange__::";
|
|
1028
1030
|
const DESKTOP_AUTH_TOKEN_BODY_ORIGINS = new Set([
|
|
1029
1031
|
"tauri://localhost",
|
|
1030
1032
|
"http://tauri.localhost",
|
|
@@ -1033,6 +1035,90 @@ const DESKTOP_AUTH_TOKEN_BODY_ORIGINS = new Set([
|
|
|
1033
1035
|
]);
|
|
1034
1036
|
// 5-minute TTL for exchange entries (short — single-use tokens).
|
|
1035
1037
|
const DESKTOP_EXCHANGE_TTL_MS = 5 * 60 * 1000;
|
|
1038
|
+
function normalizeDesktopFlowId(value) {
|
|
1039
|
+
if (typeof value !== "string")
|
|
1040
|
+
return null;
|
|
1041
|
+
const flowId = value.trim();
|
|
1042
|
+
return /^[A-Za-z0-9_-]{1,128}$/.test(flowId) ? flowId : null;
|
|
1043
|
+
}
|
|
1044
|
+
function normalizeDesktopFlowVerifier(value) {
|
|
1045
|
+
if (typeof value !== "string")
|
|
1046
|
+
return null;
|
|
1047
|
+
const verifier = value.trim();
|
|
1048
|
+
return /^[A-Za-z0-9_-]{32,128}$/.test(verifier) ? verifier : null;
|
|
1049
|
+
}
|
|
1050
|
+
function desktopFlowVerifierHash(verifier) {
|
|
1051
|
+
return crypto.createHash("sha256").update(verifier).digest("base64url");
|
|
1052
|
+
}
|
|
1053
|
+
function matchesDesktopFlowVerifier(verifier, expectedHash) {
|
|
1054
|
+
const actual = Buffer.from(desktopFlowVerifierHash(verifier));
|
|
1055
|
+
const expected = Buffer.from(expectedHash);
|
|
1056
|
+
return (actual.length === expected.length &&
|
|
1057
|
+
crypto.timingSafeEqual(actual, expected));
|
|
1058
|
+
}
|
|
1059
|
+
function parseDesktopExchangeStoredEntry(packed) {
|
|
1060
|
+
if (packed.startsWith(DESKTOP_MAGIC_LINK_CHALLENGE_PREFIX)) {
|
|
1061
|
+
const verifierHash = packed.slice(DESKTOP_MAGIC_LINK_CHALLENGE_PREFIX.length);
|
|
1062
|
+
return verifierHash ? { challenge: true, verifierHash } : null;
|
|
1063
|
+
}
|
|
1064
|
+
if (packed.startsWith(DESKTOP_EXCHANGE_ERROR_PREFIX)) {
|
|
1065
|
+
const raw = packed.slice(DESKTOP_EXCHANGE_ERROR_PREFIX.length);
|
|
1066
|
+
return {
|
|
1067
|
+
error: JSON.parse(Buffer.from(raw, "base64url").toString()),
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
const isMagicLinkExchange = packed.startsWith(DESKTOP_MAGIC_LINK_EXCHANGE_PREFIX);
|
|
1071
|
+
const encoded = isMagicLinkExchange
|
|
1072
|
+
? packed.slice(DESKTOP_MAGIC_LINK_EXCHANGE_PREFIX.length)
|
|
1073
|
+
: packed;
|
|
1074
|
+
const verifierSeparator = isMagicLinkExchange ? encoded.indexOf("::") : -1;
|
|
1075
|
+
const verifierHash = verifierSeparator >= 0 ? encoded.slice(0, verifierSeparator) : undefined;
|
|
1076
|
+
const tokenAndEmail = verifierSeparator >= 0 ? encoded.slice(verifierSeparator + 2) : encoded;
|
|
1077
|
+
const sepIdx = tokenAndEmail.indexOf("::");
|
|
1078
|
+
if (sepIdx === -1)
|
|
1079
|
+
return null;
|
|
1080
|
+
return {
|
|
1081
|
+
token: tokenAndEmail.slice(0, sepIdx),
|
|
1082
|
+
email: tokenAndEmail.slice(sepIdx + 2),
|
|
1083
|
+
...(verifierHash ? { verifierHash } : {}),
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
function isDesktopMagicLinkCallbackPath(value) {
|
|
1087
|
+
try {
|
|
1088
|
+
return new URL(value, "http://agent-native.invalid").pathname.endsWith("/_agent-native/auth/magic-link/desktop-callback");
|
|
1089
|
+
}
|
|
1090
|
+
catch {
|
|
1091
|
+
// coercion-ok: malformed callback URLs are treated as non-desktop callbacks.
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
function withDesktopMagicLinkFlow(callbackURL, flow) {
|
|
1096
|
+
const url = new URL(callbackURL, "http://agent-native.invalid");
|
|
1097
|
+
url.searchParams.set("flow_id", flow.flowId);
|
|
1098
|
+
url.searchParams.set("verifier", flow.verifier);
|
|
1099
|
+
return `${url.pathname}${url.search}${url.hash}`;
|
|
1100
|
+
}
|
|
1101
|
+
async function persistDesktopMagicLinkChallenge(flowId, verifierHash) {
|
|
1102
|
+
await addSession(`dex:${flowId}`, `${DESKTOP_MAGIC_LINK_CHALLENGE_PREFIX}${verifierHash}`);
|
|
1103
|
+
}
|
|
1104
|
+
async function issueDesktopMagicLinkFlow() {
|
|
1105
|
+
const flowId = crypto.randomBytes(32).toString("base64url");
|
|
1106
|
+
const verifier = crypto.randomBytes(32).toString("base64url");
|
|
1107
|
+
const verifierHash = desktopFlowVerifierHash(verifier);
|
|
1108
|
+
_desktopExchanges.set(flowId, {
|
|
1109
|
+
challenge: true,
|
|
1110
|
+
verifierHash,
|
|
1111
|
+
expiresAt: Date.now() + DESKTOP_EXCHANGE_TTL_MS,
|
|
1112
|
+
});
|
|
1113
|
+
try {
|
|
1114
|
+
await persistDesktopMagicLinkChallenge(flowId, verifierHash);
|
|
1115
|
+
}
|
|
1116
|
+
catch (error) {
|
|
1117
|
+
_desktopExchanges.delete(flowId);
|
|
1118
|
+
throw error;
|
|
1119
|
+
}
|
|
1120
|
+
return { flowId, verifier };
|
|
1121
|
+
}
|
|
1036
1122
|
export function setDesktopExchange(flowId, token, email) {
|
|
1037
1123
|
_desktopExchanges.set(flowId, {
|
|
1038
1124
|
token,
|
|
@@ -1059,9 +1145,12 @@ export function setDesktopExchangeError(flowId, error) {
|
|
|
1059
1145
|
* Non-fatal — if the DB isn't ready yet the in-memory Map still works for
|
|
1060
1146
|
* same-instance requests.
|
|
1061
1147
|
*/
|
|
1062
|
-
async function persistDesktopExchangeToDB(flowId, token, email) {
|
|
1148
|
+
async function persistDesktopExchangeToDB(flowId, token, email, verifierHash) {
|
|
1063
1149
|
try {
|
|
1064
|
-
|
|
1150
|
+
const packed = verifierHash
|
|
1151
|
+
? `${DESKTOP_MAGIC_LINK_EXCHANGE_PREFIX}${verifierHash}::${token}::${email}`
|
|
1152
|
+
: `${token}::${email}`;
|
|
1153
|
+
await addSession(`dex:${flowId}`, packed);
|
|
1065
1154
|
}
|
|
1066
1155
|
catch {
|
|
1067
1156
|
// non-fatal — in-memory Map is the primary path
|
|
@@ -1090,8 +1179,12 @@ async function consumeDesktopExchangeFromDB(flowId) {
|
|
|
1090
1179
|
// redeemed with the session table's default 30-day TTL.
|
|
1091
1180
|
const client = getDbExec();
|
|
1092
1181
|
const { rows } = await client.execute({
|
|
1093
|
-
sql: `DELETE FROM sessions WHERE token = ? AND created_at > ? RETURNING email`,
|
|
1094
|
-
args: [
|
|
1182
|
+
sql: `DELETE FROM sessions WHERE token = ? AND created_at > ? AND email NOT LIKE ? RETURNING email`,
|
|
1183
|
+
args: [
|
|
1184
|
+
`dex:${flowId}`,
|
|
1185
|
+
Date.now() - DESKTOP_EXCHANGE_TTL_MS,
|
|
1186
|
+
`${DESKTOP_MAGIC_LINK_CHALLENGE_PREFIX}%`,
|
|
1187
|
+
],
|
|
1095
1188
|
});
|
|
1096
1189
|
forgetCachedSessionEmail(`dex:${flowId}`);
|
|
1097
1190
|
if (rows.length === 0)
|
|
@@ -1099,21 +1192,72 @@ async function consumeDesktopExchangeFromDB(flowId) {
|
|
|
1099
1192
|
const packed = (rows[0].email ?? rows[0][0]);
|
|
1100
1193
|
if (!packed)
|
|
1101
1194
|
return null;
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1195
|
+
return parseDesktopExchangeStoredEntry(packed);
|
|
1196
|
+
}
|
|
1197
|
+
catch {
|
|
1198
|
+
// coercion-ok: a DB fallback outage leaves the exchange pending so polling can retry without consuming a token.
|
|
1199
|
+
return null;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
async function readDesktopMagicLinkChallengeFromDB(flowId) {
|
|
1203
|
+
try {
|
|
1204
|
+
const client = getDbExec();
|
|
1205
|
+
const { rows } = await client.execute({
|
|
1206
|
+
sql: `SELECT email FROM sessions WHERE token = ? AND created_at > ? LIMIT 1`,
|
|
1207
|
+
args: [`dex:${flowId}`, Date.now() - DESKTOP_EXCHANGE_TTL_MS],
|
|
1208
|
+
});
|
|
1209
|
+
if (rows.length === 0)
|
|
1210
|
+
return null;
|
|
1211
|
+
const packed = (rows[0].email ?? rows[0][0]);
|
|
1212
|
+
if (!packed)
|
|
1110
1213
|
return null;
|
|
1111
|
-
|
|
1214
|
+
const entry = parseDesktopExchangeStoredEntry(packed);
|
|
1215
|
+
return entry && "challenge" in entry
|
|
1216
|
+
? { verifierHash: entry.verifierHash }
|
|
1217
|
+
: null;
|
|
1112
1218
|
}
|
|
1113
1219
|
catch {
|
|
1114
1220
|
return null;
|
|
1115
1221
|
}
|
|
1116
1222
|
}
|
|
1223
|
+
async function claimDesktopMagicLinkFlow(flowId, verifier, token, email) {
|
|
1224
|
+
const verifierHash = desktopFlowVerifierHash(verifier);
|
|
1225
|
+
const entry = _desktopExchanges.get(flowId);
|
|
1226
|
+
if (entry) {
|
|
1227
|
+
if (!("challenge" in entry) ||
|
|
1228
|
+
entry.expiresAt < Date.now() ||
|
|
1229
|
+
!matchesDesktopFlowVerifier(verifier, entry.verifierHash)) {
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
1232
|
+
_desktopExchanges.set(flowId, {
|
|
1233
|
+
token,
|
|
1234
|
+
email,
|
|
1235
|
+
verifierHash,
|
|
1236
|
+
expiresAt: Date.now() + DESKTOP_EXCHANGE_TTL_MS,
|
|
1237
|
+
});
|
|
1238
|
+
await persistDesktopExchangeToDB(flowId, token, email, verifierHash);
|
|
1239
|
+
return true;
|
|
1240
|
+
}
|
|
1241
|
+
try {
|
|
1242
|
+
const client = getDbExec();
|
|
1243
|
+
const packed = `${DESKTOP_MAGIC_LINK_EXCHANGE_PREFIX}${verifierHash}::${token}::${email}`;
|
|
1244
|
+
const { rows } = await client.execute({
|
|
1245
|
+
sql: `UPDATE sessions SET email = ?, created_at = ? WHERE token = ? AND created_at > ? AND email = ? RETURNING email`,
|
|
1246
|
+
args: [
|
|
1247
|
+
packed,
|
|
1248
|
+
Date.now(),
|
|
1249
|
+
`dex:${flowId}`,
|
|
1250
|
+
Date.now() - DESKTOP_EXCHANGE_TTL_MS,
|
|
1251
|
+
`${DESKTOP_MAGIC_LINK_CHALLENGE_PREFIX}${verifierHash}`,
|
|
1252
|
+
],
|
|
1253
|
+
});
|
|
1254
|
+
return rows.length > 0;
|
|
1255
|
+
}
|
|
1256
|
+
catch {
|
|
1257
|
+
// coercion-ok: a failed DB claim must not issue a native session token.
|
|
1258
|
+
return false;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1117
1261
|
setInterval(() => {
|
|
1118
1262
|
const now = Date.now();
|
|
1119
1263
|
for (const [k, v] of _desktopExchanges) {
|
|
@@ -2640,15 +2784,7 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2640
2784
|
emailDomain: email.split("@")[1] || "",
|
|
2641
2785
|
});
|
|
2642
2786
|
if (flowId && sessionToken) {
|
|
2643
|
-
|
|
2644
|
-
token: sessionToken,
|
|
2645
|
-
email,
|
|
2646
|
-
expiresAt: Date.now() + DESKTOP_EXCHANGE_TTL_MS,
|
|
2647
|
-
});
|
|
2648
|
-
// Also persist to DB for cross-instance durability (Cloudflare
|
|
2649
|
-
// Workers, multi-region). Fire-and-forget — in-memory Map is
|
|
2650
|
-
// still the primary fast path for same-instance requests.
|
|
2651
|
-
void persistDesktopExchangeToDB(flowId, sessionToken, email);
|
|
2787
|
+
setDesktopExchange(flowId, sessionToken, email);
|
|
2652
2788
|
logGoogleOAuthDebug(event, "callback-exchange-stored", {
|
|
2653
2789
|
flowId,
|
|
2654
2790
|
desktop,
|
|
@@ -2687,13 +2823,23 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2687
2823
|
return { error: "Method not allowed" };
|
|
2688
2824
|
}
|
|
2689
2825
|
const query = getQuery(event);
|
|
2690
|
-
const flowId = query.flow_id;
|
|
2826
|
+
const flowId = normalizeDesktopFlowId(query.flow_id);
|
|
2691
2827
|
if (!flowId) {
|
|
2692
2828
|
setResponseStatus(event, 400);
|
|
2693
2829
|
return { error: "Missing flow_id" };
|
|
2694
2830
|
}
|
|
2831
|
+
const verifier = normalizeDesktopFlowVerifier(getQuery(event).verifier);
|
|
2695
2832
|
let entry = _desktopExchanges.get(flowId);
|
|
2696
2833
|
if (!entry || entry.expiresAt < Date.now()) {
|
|
2834
|
+
const challenge = await readDesktopMagicLinkChallengeFromDB(flowId);
|
|
2835
|
+
if (challenge) {
|
|
2836
|
+
if (!verifier ||
|
|
2837
|
+
!matchesDesktopFlowVerifier(verifier, challenge.verifierHash)) {
|
|
2838
|
+
setResponseStatus(event, 403);
|
|
2839
|
+
return { error: "Invalid desktop exchange verifier." };
|
|
2840
|
+
}
|
|
2841
|
+
return { pending: true, flow: oauthDebugFlowId(flowId) };
|
|
2842
|
+
}
|
|
2697
2843
|
// In-memory miss — fall back to the DB-persisted entry. This handles
|
|
2698
2844
|
// cross-instance routing (Cloudflare Workers, multi-region) where the
|
|
2699
2845
|
// OAuth callback and the polling request may hit different isolates.
|
|
@@ -2709,11 +2855,35 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2709
2855
|
entry =
|
|
2710
2856
|
"error" in fromDb
|
|
2711
2857
|
? { error: fromDb.error, expiresAt: Date.now() + 1 }
|
|
2712
|
-
:
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2858
|
+
: "challenge" in fromDb
|
|
2859
|
+
? {
|
|
2860
|
+
challenge: true,
|
|
2861
|
+
verifierHash: fromDb.verifierHash,
|
|
2862
|
+
expiresAt: Date.now() + 1,
|
|
2863
|
+
}
|
|
2864
|
+
: {
|
|
2865
|
+
token: fromDb.token,
|
|
2866
|
+
email: fromDb.email,
|
|
2867
|
+
...(fromDb.verifierHash
|
|
2868
|
+
? { verifierHash: fromDb.verifierHash }
|
|
2869
|
+
: {}),
|
|
2870
|
+
expiresAt: Date.now() + 1,
|
|
2871
|
+
};
|
|
2872
|
+
}
|
|
2873
|
+
if ("challenge" in entry) {
|
|
2874
|
+
if (!verifier ||
|
|
2875
|
+
!matchesDesktopFlowVerifier(verifier, entry.verifierHash)) {
|
|
2876
|
+
setResponseStatus(event, 403);
|
|
2877
|
+
return { error: "Invalid desktop exchange verifier." };
|
|
2878
|
+
}
|
|
2879
|
+
return { pending: true, flow: oauthDebugFlowId(flowId) };
|
|
2880
|
+
}
|
|
2881
|
+
if ("token" in entry && entry.verifierHash) {
|
|
2882
|
+
if (!verifier ||
|
|
2883
|
+
!matchesDesktopFlowVerifier(verifier, entry.verifierHash)) {
|
|
2884
|
+
setResponseStatus(event, 403);
|
|
2885
|
+
return { error: "Invalid desktop exchange verifier." };
|
|
2886
|
+
}
|
|
2717
2887
|
}
|
|
2718
2888
|
_desktopExchanges.delete(flowId);
|
|
2719
2889
|
// Also wipe the DB-persisted entry so it cannot be replayed via the
|
|
@@ -2735,6 +2905,13 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2735
2905
|
// still make a follow-up /auth/session?_session=... request, but the
|
|
2736
2906
|
// OAuth handoff should not depend on that second request succeeding.
|
|
2737
2907
|
setFrameworkSessionCookie(event, entry.token);
|
|
2908
|
+
if (isElectronRequest(event)) {
|
|
2909
|
+
await writeDesktopSso({
|
|
2910
|
+
email: entry.email,
|
|
2911
|
+
token: entry.token,
|
|
2912
|
+
expiresAt: Date.now() + sessionMaxAge * 1000,
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2738
2915
|
setResponseHeader(event, "Referrer-Policy", "no-referrer");
|
|
2739
2916
|
logGoogleOAuthDebug(event, "exchange-success", {
|
|
2740
2917
|
flowId,
|
|
@@ -2742,6 +2919,52 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
2742
2919
|
});
|
|
2743
2920
|
return { token: entry.token, email: entry.email };
|
|
2744
2921
|
}));
|
|
2922
|
+
// Magic-link verification happens in the system browser, so native shells
|
|
2923
|
+
// need the verified browser session copied into the same one-time exchange
|
|
2924
|
+
// used by Google OAuth before they can establish their own WebView session.
|
|
2925
|
+
app.use("/_agent-native/auth/magic-link/desktop-callback", defineEventHandler(async (event) => {
|
|
2926
|
+
if (!isReadMethod(event)) {
|
|
2927
|
+
setResponseStatus(event, 405);
|
|
2928
|
+
return { error: "Method not allowed" };
|
|
2929
|
+
}
|
|
2930
|
+
const flowId = normalizeDesktopFlowId(getQuery(event).flow_id);
|
|
2931
|
+
const verifier = normalizeDesktopFlowVerifier(getQuery(event).verifier);
|
|
2932
|
+
if (!flowId) {
|
|
2933
|
+
setResponseStatus(event, 400);
|
|
2934
|
+
return { error: "Missing flow_id" };
|
|
2935
|
+
}
|
|
2936
|
+
if (!verifier) {
|
|
2937
|
+
return oauthErrorPage(AUTH_MAGIC_LINK_FALLBACK);
|
|
2938
|
+
}
|
|
2939
|
+
const session = await getSession(event);
|
|
2940
|
+
if (!session?.email || !session.token) {
|
|
2941
|
+
setDesktopExchangeError(flowId, {
|
|
2942
|
+
message: AUTH_MAGIC_LINK_FALLBACK,
|
|
2943
|
+
code: "callback_error",
|
|
2944
|
+
});
|
|
2945
|
+
return oauthErrorPage(AUTH_MAGIC_LINK_FALLBACK);
|
|
2946
|
+
}
|
|
2947
|
+
try {
|
|
2948
|
+
// Better Auth owns the browser cookie, while native clients resolve
|
|
2949
|
+
// the exchanged token through the framework session table.
|
|
2950
|
+
await addSession(session.token, session.email);
|
|
2951
|
+
}
|
|
2952
|
+
catch (error) {
|
|
2953
|
+
captureAuthError(error, {
|
|
2954
|
+
route: "magic-link",
|
|
2955
|
+
email: session.email,
|
|
2956
|
+
});
|
|
2957
|
+
setDesktopExchangeError(flowId, {
|
|
2958
|
+
message: AUTH_MAGIC_LINK_FALLBACK,
|
|
2959
|
+
code: "callback_error",
|
|
2960
|
+
});
|
|
2961
|
+
return oauthErrorPage(AUTH_MAGIC_LINK_FALLBACK);
|
|
2962
|
+
}
|
|
2963
|
+
if (!(await claimDesktopMagicLinkFlow(flowId, verifier, session.token, session.email))) {
|
|
2964
|
+
return oauthErrorPage(AUTH_MAGIC_LINK_FALLBACK);
|
|
2965
|
+
}
|
|
2966
|
+
return oauthDesktopExchangePage("Sign-in complete. You can return to the app.");
|
|
2967
|
+
}));
|
|
2745
2968
|
// Initialize Better Auth. Forward `googleScopes` into the BetterAuthConfig
|
|
2746
2969
|
// so the social provider requests the broader product scopes (Gmail,
|
|
2747
2970
|
// Calendar, etc.) up front during the primary sign-in — eliminating the
|
|
@@ -3050,9 +3273,10 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
3050
3273
|
const body = await readBody(event);
|
|
3051
3274
|
const rawEmail = typeof body?.email === "string" ? body.email : "";
|
|
3052
3275
|
const email = normalizeAuthEmail(rawEmail);
|
|
3053
|
-
|
|
3276
|
+
let callbackURL = typeof body?.callbackURL === "string"
|
|
3054
3277
|
? safeReturnPath(body.callbackURL)
|
|
3055
3278
|
: "/";
|
|
3279
|
+
let desktopFlow;
|
|
3056
3280
|
if (!email) {
|
|
3057
3281
|
setResponseStatus(event, 400);
|
|
3058
3282
|
return { error: VALID_AUTH_EMAIL_MESSAGE };
|
|
@@ -3062,6 +3286,10 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
3062
3286
|
return { error: GOOGLE_AUTH_REQUIRED_MESSAGE };
|
|
3063
3287
|
}
|
|
3064
3288
|
try {
|
|
3289
|
+
if (isDesktopMagicLinkCallbackPath(callbackURL)) {
|
|
3290
|
+
desktopFlow = await issueDesktopMagicLinkFlow();
|
|
3291
|
+
callbackURL = withDesktopMagicLinkFlow(callbackURL, desktopFlow);
|
|
3292
|
+
}
|
|
3065
3293
|
await auth.api.signInMagicLink({
|
|
3066
3294
|
body: {
|
|
3067
3295
|
email,
|
|
@@ -3070,7 +3298,10 @@ async function mountBetterAuthRoutes(app, options) {
|
|
|
3070
3298
|
},
|
|
3071
3299
|
headers: event.headers,
|
|
3072
3300
|
});
|
|
3073
|
-
return {
|
|
3301
|
+
return {
|
|
3302
|
+
ok: true,
|
|
3303
|
+
...(desktopFlow ?? {}),
|
|
3304
|
+
};
|
|
3074
3305
|
}
|
|
3075
3306
|
catch (e) {
|
|
3076
3307
|
if (!isExpectedAuthFailure(e)) {
|
|
@@ -2965,6 +2965,7 @@ ${signInJourneyInlineScript()}
|
|
|
2965
2965
|
}
|
|
2966
2966
|
function __anFinishOAuthExchange(ret, flowId, sessionToken) {
|
|
2967
2967
|
__anGoogleSignInInFlight = false;
|
|
2968
|
+
__anMagicLinkInFlight = false;
|
|
2968
2969
|
if (__anIsBuilderPreview()) {
|
|
2969
2970
|
if (sessionToken) {
|
|
2970
2971
|
__anSetOAuthDebug('OAuth exchange redeemed; applying session bridge to embedded app', flowId);
|
|
@@ -3220,6 +3221,7 @@ ${identitySsoScript}
|
|
|
3220
3221
|
var __anOAuthPollTimer = null;
|
|
3221
3222
|
var __anOAuthPollCount = 0;
|
|
3222
3223
|
var __anGoogleSignInInFlight = false;
|
|
3224
|
+
var __anMagicLinkInFlight = false;
|
|
3223
3225
|
var __anGoogleRecoverBound = false;
|
|
3224
3226
|
function __anNewOAuthFlowId() {
|
|
3225
3227
|
try {
|
|
@@ -3253,15 +3255,26 @@ ${identitySsoScript}
|
|
|
3253
3255
|
if (showDebugOverlay) debug.classList.add('show');
|
|
3254
3256
|
}
|
|
3255
3257
|
}
|
|
3256
|
-
function
|
|
3258
|
+
function __anStopOAuthExchangePolling() {
|
|
3257
3259
|
if (__anOAuthPollTimer) {
|
|
3258
3260
|
clearInterval(__anOAuthPollTimer);
|
|
3259
3261
|
__anOAuthPollTimer = null;
|
|
3260
3262
|
}
|
|
3263
|
+
}
|
|
3264
|
+
function __anShowAuthExchangeError(err, btn, message, kind) {
|
|
3265
|
+
__anStopOAuthExchangePolling();
|
|
3261
3266
|
err.textContent = message;
|
|
3262
|
-
err.classList.add('show');
|
|
3267
|
+
err.classList.add('show', 'error');
|
|
3263
3268
|
btn.disabled = false;
|
|
3264
|
-
|
|
3269
|
+
if (kind === 'magic-link') {
|
|
3270
|
+
__anMagicLinkInFlight = false;
|
|
3271
|
+
showMagicLinkForm();
|
|
3272
|
+
} else {
|
|
3273
|
+
__anGoogleSignInInFlight = false;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
function __anShowOAuthError(err, btn, message) {
|
|
3277
|
+
__anShowAuthExchangeError(err, btn, message, 'google');
|
|
3265
3278
|
}
|
|
3266
3279
|
function __anRecoverGoogleSignInAfterReturn() {
|
|
3267
3280
|
// The user left for the Google sign-in window and came back. If the flow
|
|
@@ -3311,14 +3324,17 @@ ${identitySsoScript}
|
|
|
3311
3324
|
__anShowOAuthError(err, btn, __anT('failedToConnect'));
|
|
3312
3325
|
}
|
|
3313
3326
|
}
|
|
3314
|
-
function __anWaitForOAuthExchange(flowId, ret, btn, err) {
|
|
3327
|
+
function __anWaitForOAuthExchange(flowId, ret, btn, err, kind, verifier) {
|
|
3315
3328
|
var started = Date.now();
|
|
3316
3329
|
var timeoutMs = 5 * 60 * 1000;
|
|
3330
|
+
var isMagicLink = kind === 'magic-link';
|
|
3317
3331
|
__anOAuthPollCount = 0;
|
|
3318
3332
|
async function check() {
|
|
3319
3333
|
__anOAuthPollCount++;
|
|
3320
3334
|
try {
|
|
3321
|
-
var
|
|
3335
|
+
var exchangeParams = '?flow_id=' + encodeURIComponent(flowId);
|
|
3336
|
+
if (verifier) exchangeParams += '&verifier=' + encodeURIComponent(verifier);
|
|
3337
|
+
var res = await fetch(__anPath('/_agent-native/auth/desktop-exchange') + exchangeParams, { credentials: 'include' });
|
|
3322
3338
|
var data = await res.json().catch(function() { return {}; });
|
|
3323
3339
|
if (data && (data.email || data.token)) {
|
|
3324
3340
|
if (__anOAuthPollTimer) clearInterval(__anOAuthPollTimer);
|
|
@@ -3328,7 +3344,12 @@ ${identitySsoScript}
|
|
|
3328
3344
|
}
|
|
3329
3345
|
if (data && data.error) {
|
|
3330
3346
|
__anSetOAuthDebug('OAuth exchange returned an error: ' + (data.message || data.error), flowId);
|
|
3331
|
-
|
|
3347
|
+
__anShowAuthExchangeError(
|
|
3348
|
+
err,
|
|
3349
|
+
btn,
|
|
3350
|
+
__anAuthErrorText(data, isMagicLink ? __anT('magicLinkFailed') : __anT('googleNotConfigured')),
|
|
3351
|
+
isMagicLink ? 'magic-link' : 'google',
|
|
3352
|
+
);
|
|
3332
3353
|
return;
|
|
3333
3354
|
}
|
|
3334
3355
|
if (data && data.pending && (__anOAuthPollCount === 1 || __anOAuthPollCount % 5 === 0)) {
|
|
@@ -3340,10 +3361,17 @@ ${identitySsoScript}
|
|
|
3340
3361
|
}
|
|
3341
3362
|
}
|
|
3342
3363
|
if (Date.now() - started > timeoutMs) {
|
|
3343
|
-
|
|
3364
|
+
__anShowAuthExchangeError(
|
|
3365
|
+
err,
|
|
3366
|
+
btn,
|
|
3367
|
+
isMagicLink
|
|
3368
|
+
? __anT('magicLinkFailed')
|
|
3369
|
+
: __anT('googleNeverFinished') + ' Flow ' + __anFlowDebugId(flowId) + '.',
|
|
3370
|
+
isMagicLink ? 'magic-link' : 'google',
|
|
3371
|
+
);
|
|
3344
3372
|
}
|
|
3345
3373
|
}
|
|
3346
|
-
|
|
3374
|
+
__anStopOAuthExchangePolling();
|
|
3347
3375
|
__anOAuthPollTimer = setInterval(check, 1000);
|
|
3348
3376
|
setTimeout(check, 500);
|
|
3349
3377
|
}
|
|
@@ -3479,6 +3507,8 @@ ${googleOnly
|
|
|
3479
3507
|
__anSetAuthView('magicLinkSent');
|
|
3480
3508
|
}
|
|
3481
3509
|
function showMagicLinkForm() {
|
|
3510
|
+
__anStopOAuthExchangePolling();
|
|
3511
|
+
__anMagicLinkInFlight = false;
|
|
3482
3512
|
var form = document.getElementById('magic-link-form');
|
|
3483
3513
|
if (!form) return;
|
|
3484
3514
|
hideMagicLinkSuccess();
|
|
@@ -3851,6 +3881,7 @@ ${googleOnly
|
|
|
3851
3881
|
var msg = document.getElementById('m-msg');
|
|
3852
3882
|
var emailInput = document.getElementById('m-email');
|
|
3853
3883
|
var email = __anNormalizeAuthEmail(emailInput && emailInput.value);
|
|
3884
|
+
var isDesktopMagicLink = __anIsAgentNativeDesktop();
|
|
3854
3885
|
msg.classList.remove('show', 'error', 'success');
|
|
3855
3886
|
if (!__anIsValidAuthEmail(email)) {
|
|
3856
3887
|
__anShowEmailValidationError(emailInput, msg);
|
|
@@ -3858,12 +3889,18 @@ ${googleOnly
|
|
|
3858
3889
|
}
|
|
3859
3890
|
var originalLabel = btn.textContent;
|
|
3860
3891
|
btn.disabled = true;
|
|
3892
|
+
__anMagicLinkInFlight = isDesktopMagicLink;
|
|
3861
3893
|
btn.textContent = __anT('sending');
|
|
3862
3894
|
try {
|
|
3863
3895
|
var res = await fetch(__anPath('/_agent-native/auth/magic-link'), {
|
|
3864
3896
|
method: 'POST',
|
|
3865
3897
|
headers: { 'Content-Type': 'application/json' },
|
|
3866
|
-
body: JSON.stringify({
|
|
3898
|
+
body: JSON.stringify({
|
|
3899
|
+
email: email,
|
|
3900
|
+
callbackURL: isDesktopMagicLink
|
|
3901
|
+
? __anPath('/_agent-native/auth/magic-link/desktop-callback')
|
|
3902
|
+
: __anResumeHref(),
|
|
3903
|
+
}),
|
|
3867
3904
|
});
|
|
3868
3905
|
var data = await res.json().catch(function(error) {
|
|
3869
3906
|
console.warn('[auth] Could not parse magic-link response', error);
|
|
@@ -3873,13 +3910,23 @@ ${googleOnly
|
|
|
3873
3910
|
btn.disabled = false;
|
|
3874
3911
|
btn.textContent = originalLabel;
|
|
3875
3912
|
showMagicLinkSuccess(email);
|
|
3913
|
+
if (isDesktopMagicLink) {
|
|
3914
|
+
var magicFlowId = data && typeof data.flowId === 'string' ? data.flowId : '';
|
|
3915
|
+
var magicVerifier = data && typeof data.verifier === 'string' ? data.verifier : '';
|
|
3916
|
+
if (!magicFlowId || !magicVerifier) {
|
|
3917
|
+
throw new Error('The sign-in flow was not initialized.');
|
|
3918
|
+
}
|
|
3919
|
+
__anWaitForOAuthExchange(magicFlowId, __anResumeHref(), btn, msg, 'magic-link', magicVerifier);
|
|
3920
|
+
}
|
|
3876
3921
|
return;
|
|
3877
3922
|
}
|
|
3923
|
+
__anMagicLinkInFlight = false;
|
|
3878
3924
|
msg.textContent = __anAuthErrorText(data, __anT('magicLinkFailed'));
|
|
3879
3925
|
msg.classList.add('show', 'error');
|
|
3880
3926
|
btn.disabled = false;
|
|
3881
3927
|
btn.textContent = originalLabel;
|
|
3882
3928
|
} catch (err) {
|
|
3929
|
+
__anMagicLinkInFlight = false;
|
|
3883
3930
|
msg.textContent = __anT('networkErrorDashRetry');
|
|
3884
3931
|
msg.classList.add('show', 'error');
|
|
3885
3932
|
btn.disabled = false;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare const REASONING_EFFORTS: readonly ["auto", "none", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
2
2
|
export type ReasoningEffort = (typeof REASONING_EFFORTS)[number];
|
|
3
3
|
/**
|
|
4
|
-
* Shared chat always chooses an explicit
|
|
4
|
+
* Shared chat always chooses an explicit effort tier. Keep `auto` in the
|
|
5
5
|
* accepted type only so older persisted selections and external callers can
|
|
6
|
-
* migrate cleanly; new UI and engine defaults resolve it to
|
|
6
|
+
* migrate cleanly; new UI and engine defaults resolve it to High.
|
|
7
7
|
*/
|
|
8
8
|
export declare const DEFAULT_REASONING_EFFORT: ReasoningEffort;
|
|
9
9
|
export declare const REASONING_EFFORT_LABELS: Record<ReasoningEffort, string>;
|
|
@@ -19,22 +19,22 @@ export declare function normalizeReasoningEffortForRequest(model: string | undef
|
|
|
19
19
|
export declare function reasoningEffortLabel(effort: ReasoningEffort | undefined): string;
|
|
20
20
|
/**
|
|
21
21
|
* Resolve a user-facing selection for a model. Legacy `auto`, missing values,
|
|
22
|
-
* and tiers unsupported by the newly selected model all become
|
|
23
|
-
*
|
|
24
|
-
* back to
|
|
25
|
-
* effort through `normalizeReasoningEffortForModel`.
|
|
22
|
+
* and tiers unsupported by the newly selected model all become High.
|
|
23
|
+
* Models without effort controls still retain High in persisted chat state so
|
|
24
|
+
* moving back to an effort-capable model has a predictable default; their
|
|
25
|
+
* engines omit the effort through `normalizeReasoningEffortForModel`.
|
|
26
26
|
*/
|
|
27
27
|
export declare function resolveReasoningEffortSelection(model: string | undefined, effort: ReasoningEffort | undefined): ReasoningEffort;
|
|
28
28
|
export declare function stepDownReasoningEffort(effort: ReasoningEffort | undefined): ReasoningEffort | undefined;
|
|
29
29
|
export declare function isGPTReasoningModel(model: string): boolean;
|
|
30
30
|
/**
|
|
31
31
|
* Anthropic's adaptive-thinking API is only available on the newer Claude
|
|
32
|
-
* model families. Claude Haiku 4.5
|
|
32
|
+
* model families. Claude Haiku 4.5 supports effort, but it still
|
|
33
33
|
* requires the legacy manual `budget_tokens` configuration.
|
|
34
34
|
*/
|
|
35
35
|
export declare function supportsClaudeAdaptiveThinking(model: string | undefined): boolean;
|
|
36
36
|
/**
|
|
37
|
-
* Map the shared
|
|
37
|
+
* Map the shared effort ladder to Anthropic's manual thinking budgets for
|
|
38
38
|
* models that do not support adaptive thinking (currently Claude Haiku 4.5).
|
|
39
39
|
*/
|
|
40
40
|
export declare function anthropicManualThinkingBudget(effort: ReasoningEffort): 1024 | 4096 | 8000 | 16000 | 32000;
|