@agent-native/core 0.149.6 → 0.151.0
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/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +83 -45
- package/corpus/templates/analytics/changelog/2026-08-11-analytics-dashboards-keep-the-selected-tab-when-reopened.md +6 -0
- package/corpus/templates/assets/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/brain/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/calendar/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/clips/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/clips/changelog/2026-08-11-clips-recovers-from-transient-updates-and-avoids-macos-priva.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +32 -176
- package/corpus/templates/clips/desktop/src/lib/updater.ts +52 -37
- package/corpus/templates/clips/desktop/src/styles.css +1 -166
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +2 -0
- package/corpus/templates/clips/desktop/src-tauri/Info.plist +4 -0
- package/corpus/templates/clips/desktop/src-tauri/src/eventkit.rs +28 -1
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +48 -1
- package/corpus/templates/clips/desktop/src-tauri/src/permission_status.rs +3 -0
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +5 -0
- package/corpus/templates/clips/server/plugins/auth.ts +3 -0
- package/corpus/templates/content/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +2 -1
- package/corpus/templates/design/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +5 -1
- package/corpus/templates/design/changelog/2026-08-11-design-agent-prompts-copy-successfully-in-the-desktop-app.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/dispatch/app/global.css +2 -2
- package/corpus/templates/dispatch/changelog/2026-08-10-desktop-workspace-sign-in.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-11-dispatch-no-longer-shows-the-generic-chat-starter-among-defa.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-11-integrations-and-scheduled-work-open-as-first-class-electron-pages.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-08-11-sidebar-text-is-slightly-easier-to-read-in-light-and-dark-mo.md +6 -0
- package/corpus/templates/dispatch/server/lib/identity-sso.ts +57 -31
- package/corpus/templates/dispatch/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/dispatch/server/plugins/identity-sso.ts +82 -33
- package/corpus/templates/dispatch/server/plugins/org-apps-directory.ts +7 -15
- package/corpus/templates/dispatch/server/plugins/setup-dispatch.ts +9 -1
- package/corpus/templates/dispatch/shared/feature-flags.ts +15 -0
- package/corpus/templates/factory/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/forms/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +8 -7
- package/corpus/templates/forms/app/hooks/use-forms.ts +8 -1
- package/corpus/templates/forms/changelog/2026-08-11-fixed-public-forms-so-conditional-questions-stay-hidden-unti.md +6 -0
- package/corpus/templates/forms/server/handlers/submissions.ts +2 -1
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +37 -2
- package/corpus/templates/forms/shared/page-url.ts +54 -0
- package/corpus/templates/forms/shared/types.ts +4 -4
- package/corpus/templates/mail/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/plan/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/slides/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/corpus/templates/slides/actions/add-slide.ts +0 -27
- package/corpus/templates/slides/actions/import-file.ts +28 -9
- package/corpus/templates/slides/actions/view-screen.ts +4 -4
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +8 -1
- package/corpus/templates/slides/app/components/editor/AiEditingMarker.tsx +25 -0
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +43 -48
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +9 -6
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +1 -9
- package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +2 -0
- package/corpus/templates/slides/app/components/editor/ImageOverlay.tsx +0 -26
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +904 -217
- package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
- package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +11 -0
- package/corpus/templates/slides/app/components/editor/inline-edit-session.ts +16 -0
- package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +22 -6
- package/corpus/templates/slides/app/components/editor/style-clipboard.ts +61 -0
- package/corpus/templates/slides/app/components/layout/Layout.tsx +26 -2
- package/corpus/templates/slides/app/components/ui/context-menu.tsx +1 -0
- package/corpus/templates/slides/app/context/DeckContext.tsx +75 -25
- package/corpus/templates/slides/app/global.css +1 -1
- package/corpus/templates/slides/app/i18n/en-US.ts +6 -1
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
- package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +12 -12
- package/corpus/templates/slides/app/lib/slide-agent-context.ts +52 -0
- package/corpus/templates/slides/app/lib/slide-image-replacement.ts +61 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +55 -116
- package/corpus/templates/slides/app/pages/Index.tsx +28 -3
- package/corpus/templates/slides/changelog/2026-08-11-agent-chat-now-follows-the-current-slide-and-selection.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-deck-titles-now-use-slide-content-instead-of-placeholder-filenames.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-history-now-opens-from-the-deck-overflow-menu.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-images-can-be-dropped-directly-onto-slides-and-resized-or-re.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-new-decks-now-transition-directly-to-a-full-page-editor-load.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-selected-slide-layers-can-be-nudged-precisely-with-arrow-key.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-can-copy-and-paste-element-styles-with-cmd-option-c-v.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-slides-shows-one-compact-ai-marker-and-keeps-your-current-sl.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-text-edits-preserve-slide-layout-when-no-changes-are-made.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-the-style-toolbar-now-uses-the-full-available-width.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-11-undo-and-redo-now-move-one-visible-slide-state-at-a-time.md +6 -0
- package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -2
- package/corpus/templates/slides/shared/deck-title.ts +56 -0
- package/corpus/templates/tasks/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/agent/run-store.js +6 -1
- package/dist/agent/thread-data-builder.js +19 -44
- package/dist/agent/types.d.ts +13 -0
- package/dist/catalog.json +1 -0
- package/dist/cli/create.js +1 -1
- package/dist/client/AssistantChat.js +58 -10
- package/dist/client/FileStorageSetupCard.d.ts +6 -0
- package/dist/client/FileStorageSetupCard.js +18 -0
- package/dist/client/NewWorkspaceAppFlow.js +10 -0
- package/dist/client/agent-chat-adapter.js +87 -11
- package/dist/client/agent-chat.d.ts +1 -1
- package/dist/client/agent-chat.js +8 -3
- package/dist/client/chat/attachment-adapters.js +10 -0
- package/dist/client/chat/message-components.d.ts +4 -1
- package/dist/client/chat/message-components.js +37 -13
- package/dist/client/chat/tool-call-display.js +14 -0
- package/dist/client/chat-first/apps-rail.d.ts +1 -1
- package/dist/client/chat-first/apps-rail.js +6 -6
- package/dist/client/chat-first/copy.js +1 -0
- package/dist/client/chat-first/types.d.ts +1 -0
- package/dist/client/extensions/EmbeddedExtension.js +1 -1
- package/dist/client/extensions/ExtensionViewer.js +1 -1
- package/dist/client/extensions/InlineExtensionFrame.js +1 -1
- package/dist/client/onboarding/FirstRunOnboarding.js +17 -2
- package/dist/client/settings/FileStorageSettingsForm.d.ts +2 -0
- package/dist/client/settings/FileStorageSettingsForm.js +113 -0
- package/dist/client/settings/SettingsPanel.js +2 -1
- package/dist/client/sse-event-processor.js +21 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/extensions/actions.js +2 -2
- package/dist/extensions/html-shell.js +3 -1
- package/dist/file-upload/pre-upload-attachments.d.ts +15 -10
- package/dist/file-upload/pre-upload-attachments.js +68 -24
- package/dist/file-upload/registry.d.ts +4 -5
- package/dist/file-upload/registry.js +7 -14
- package/dist/file-upload/s3.d.ts +9 -0
- package/dist/file-upload/s3.js +168 -0
- package/dist/file-upload/types.d.ts +3 -3
- package/dist/file-upload/types.js +2 -2
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.js +23 -1
- package/dist/onboarding/default-steps.js +69 -0
- package/dist/onboarding/types.d.ts +2 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat/browser-team-tools.js +20 -0
- package/dist/server/agent-chat-plugin.js +12 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.js +45 -21
- package/dist/server/better-auth-instance.d.ts +2 -0
- package/dist/server/better-auth-instance.js +2 -2
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.js +26 -16
- package/dist/server/identity-sso-store.d.ts +3 -0
- package/dist/server/identity-sso-store.js +45 -0
- package/dist/server/identity-sso.d.ts +20 -10
- package/dist/server/identity-sso.js +79 -31
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
- package/dist/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
- package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
- package/docs/content/authentication.mdx +27 -0
- package/docs/content/cross-app-sso.mdx +38 -8
- package/docs/content/extensions.mdx +15 -15
- package/docs/content/file-uploads.mdx +10 -12
- package/docs/content/generative-ui.mdx +6 -1
- package/package.json +2 -2
- package/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/src/templates/default/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +24 -1
- package/src/templates/workspace-core/.agents/skills/delegate-to-agent/SKILL.md +28 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -2
- package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +1 -1
- package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +3 -1
|
@@ -11,7 +11,8 @@ import { mountDbAdminRoutes } from "../db-admin/routes.js";
|
|
|
11
11
|
import { getDbExec, isProductionServerlessFunctionRuntime, } from "../db/client.js";
|
|
12
12
|
import { getDatabaseRuntimeFingerprint, getRuntimeDebugFingerprint, runDatabaseSchemaHealthCheck, } from "../db/runtime-diagnostics.js";
|
|
13
13
|
import { ssrfSafeFetch } from "../extensions/url-safety.js";
|
|
14
|
-
import { uploadFile, getActiveFileUploadProviderForRequest, listFileUploadProviders, } from "../file-upload/index.js";
|
|
14
|
+
import { uploadFile, getActiveFileUploadProviderForRequest, listFileUploadProviders, registerFileUploadProvider, } from "../file-upload/index.js";
|
|
15
|
+
import { s3FileUploadProvider } from "../file-upload/s3.js";
|
|
15
16
|
import { handleMcpConnect } from "../mcp/connect-route.js";
|
|
16
17
|
import { handleMcpOAuth, handleMcpOAuthAuthorizationServerMetadata, handleMcpOAuthProtectedResourceMetadata, } from "../mcp/oauth-route.js";
|
|
17
18
|
import { MCP_ROUTE_PREFIXES } from "../mcp/route-paths.js";
|
|
@@ -51,7 +52,6 @@ import { createGatewayAccessCheckHandler } from "./gateway-access-check.js";
|
|
|
51
52
|
import { getAppBasePath, getOrigin } from "./google-oauth.js";
|
|
52
53
|
import { createGoogleRealtimeSessionHandler } from "./google-realtime-session.js";
|
|
53
54
|
import { readBody, DEFAULT_UPLOAD_MAX_FILE_BYTES, isAllowedUploadMimeType, } from "./h3-helpers.js";
|
|
54
|
-
import { isIdentitySsoEnabled } from "./identity-sso-store.js";
|
|
55
55
|
import { handleIdentitySso } from "./identity-sso.js";
|
|
56
56
|
import { createOpenRouteHandler } from "./open-route.js";
|
|
57
57
|
import { createPollEventsHandler } from "./poll-events.js";
|
|
@@ -889,6 +889,12 @@ function wireRouteErrorCapture(nitroApp) {
|
|
|
889
889
|
}
|
|
890
890
|
});
|
|
891
891
|
}
|
|
892
|
+
export function ensureS3FileUploadProvider() {
|
|
893
|
+
if (listFileUploadProviders().some((provider) => provider.id === s3FileUploadProvider.id)) {
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
registerFileUploadProvider(s3FileUploadProvider);
|
|
897
|
+
}
|
|
892
898
|
export function createCoreRoutesPlugin(options = {}) {
|
|
893
899
|
return async (nitroApp) => {
|
|
894
900
|
markDefaultPluginProvided(nitroApp, "core-routes");
|
|
@@ -905,6 +911,12 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
905
911
|
});
|
|
906
912
|
try {
|
|
907
913
|
const P = FRAMEWORK_ROUTE_PREFIX;
|
|
914
|
+
// Keep the framework-owned S3-compatible provider available even when an
|
|
915
|
+
// app does not mount the optional onboarding plugin. The settings CTA and
|
|
916
|
+
// the upload route share this registry. An app may register its own
|
|
917
|
+
// provider under the conventional `s3` id, so preserve that explicit
|
|
918
|
+
// registration instead of replacing it during core bootstrap.
|
|
919
|
+
ensureS3FileUploadProvider();
|
|
908
920
|
// This response is a side-effect-free static contract used by the SSR
|
|
909
921
|
// shell. Mount it before optional default-plugin/bootstrap work so a
|
|
910
922
|
// browser's automatic rules fetch cannot inherit the cold-start wait.
|
|
@@ -3077,22 +3089,20 @@ export function createCoreRoutesPlugin(options = {}) {
|
|
|
3077
3089
|
}));
|
|
3078
3090
|
}
|
|
3079
3091
|
}
|
|
3080
|
-
// Cross-app SSO ("Sign in with Agent-Native") — CLIENT side.
|
|
3081
|
-
//
|
|
3082
|
-
// means the route is never even registered: zero new surface, existing
|
|
3083
|
-
// auth byte-for-byte unchanged. `/login` 302s to the identity hub;
|
|
3092
|
+
// Cross-app SSO ("Sign in with Agent-Native") — CLIENT side. `/login`
|
|
3093
|
+
// 302s to the identity hub;
|
|
3084
3094
|
// `/callback` verifies the hub-issued A2A-signed identity JWT and JIT-
|
|
3085
3095
|
// links the verified email into this app's local Better Auth store. The
|
|
3086
|
-
// handler
|
|
3087
|
-
//
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
}
|
|
3096
|
+
// handler fails closed unless direct web SSO is configured or the
|
|
3097
|
+
// packaged Desktop SSO Canary requests a canonical Agent Native app.
|
|
3098
|
+
// Mounting the handler unconditionally lets that request-scoped decision
|
|
3099
|
+
// work.
|
|
3100
|
+
getH3App(nitroApp).use(`${P}/identity`, defineEventHandler(async (event) => {
|
|
3101
|
+
// Framework strips the mount prefix; what remains is the subpath
|
|
3102
|
+
// after `/identity` (e.g. `/login`, `/callback`).
|
|
3103
|
+
const subpath = event.url?.pathname || "";
|
|
3104
|
+
return handleIdentitySso(event, subpath);
|
|
3105
|
+
}));
|
|
3096
3106
|
if (!options.disableOpenRoute) {
|
|
3097
3107
|
// Stable deep-link route. External agents (MCP/A2A) surface
|
|
3098
3108
|
// `/_agent-native/open?app=…&view=…&<recordId>=…` links; this resolves
|
|
@@ -35,6 +35,9 @@ export declare function getIdentityHubUrl(): string | undefined;
|
|
|
35
35
|
* env-unset-no-op invariant is asserted against.
|
|
36
36
|
*/
|
|
37
37
|
export declare function isIdentitySsoEnabled(): boolean;
|
|
38
|
+
export declare function isDesktopSsoCanaryUserAgent(userAgent: string | undefined): boolean;
|
|
39
|
+
export declare function isCanonicalAgentNativeAppOrigin(origin: string | undefined): boolean;
|
|
40
|
+
export declare function isCanonicalAgentNativeAppRequest(host: string | undefined, forwardedProtocol: string | undefined): boolean;
|
|
38
41
|
/**
|
|
39
42
|
* The conditional "Sign in with Agent-Native" entry injected into the login
|
|
40
43
|
* page — ONLY when the feature is enabled. Returns an empty string when
|
|
@@ -25,6 +25,25 @@ import { randomBytes } from "node:crypto";
|
|
|
25
25
|
import { getDbExec, isConnectionError, intType, isPostgres, } from "../db/client.js";
|
|
26
26
|
import { ensureTableExists } from "../db/ddl-guard.js";
|
|
27
27
|
let _initPromise;
|
|
28
|
+
const DESKTOP_SSO_CANARY_USER_AGENT = /AgentNativeDesktopSsoCanary\//i;
|
|
29
|
+
const CANONICAL_IDENTITY_SSO_APP_ORIGINS = new Set([
|
|
30
|
+
"https://analytics.agent-native.com",
|
|
31
|
+
"https://assets.agent-native.com",
|
|
32
|
+
"https://brain.agent-native.com",
|
|
33
|
+
"https://calendar.agent-native.com",
|
|
34
|
+
"https://chat.agent-native.com",
|
|
35
|
+
"https://clips.agent-native.com",
|
|
36
|
+
"https://content.agent-native.com",
|
|
37
|
+
"https://crm.agent-native.com",
|
|
38
|
+
"https://design.agent-native.com",
|
|
39
|
+
"https://dispatch.agent-native.com",
|
|
40
|
+
"https://forms.agent-native.com",
|
|
41
|
+
"https://macros.agent-native.com",
|
|
42
|
+
"https://mail.agent-native.com",
|
|
43
|
+
"https://plan.agent-native.com",
|
|
44
|
+
"https://slides.agent-native.com",
|
|
45
|
+
"https://tasks.agent-native.com",
|
|
46
|
+
]);
|
|
28
47
|
// ---------------------------------------------------------------------------
|
|
29
48
|
// Feature switch — the SINGLE source of truth for whether the federated-SSO
|
|
30
49
|
// client is active. Lives here (a leaf module with no dependency on auth.ts)
|
|
@@ -61,6 +80,32 @@ export function getIdentityHubUrl() {
|
|
|
61
80
|
export function isIdentitySsoEnabled() {
|
|
62
81
|
return !!getIdentityHubUrl();
|
|
63
82
|
}
|
|
83
|
+
export function isDesktopSsoCanaryUserAgent(userAgent) {
|
|
84
|
+
return DESKTOP_SSO_CANARY_USER_AGENT.test(userAgent ?? "");
|
|
85
|
+
}
|
|
86
|
+
export function isCanonicalAgentNativeAppOrigin(origin) {
|
|
87
|
+
if (!origin)
|
|
88
|
+
return false;
|
|
89
|
+
try {
|
|
90
|
+
const parsed = new URL(origin);
|
|
91
|
+
return (parsed.protocol === "https:" &&
|
|
92
|
+
!parsed.username &&
|
|
93
|
+
!parsed.password &&
|
|
94
|
+
parsed.pathname === "/" &&
|
|
95
|
+
!parsed.search &&
|
|
96
|
+
!parsed.hash &&
|
|
97
|
+
CANONICAL_IDENTITY_SSO_APP_ORIGINS.has(parsed.origin));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export function isCanonicalAgentNativeAppRequest(host, forwardedProtocol) {
|
|
104
|
+
if (!host || forwardedProtocol !== "https") {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
return isCanonicalAgentNativeAppOrigin(`https://${host}`);
|
|
108
|
+
}
|
|
64
109
|
/**
|
|
65
110
|
* The conditional "Sign in with Agent-Native" entry injected into the login
|
|
66
111
|
* page — ONLY when the feature is enabled. Returns an empty string when
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
* an identity authority (Dispatch) so a user logged in there can land in this
|
|
7
7
|
* app without re-entering credentials.
|
|
8
8
|
*
|
|
9
|
-
* Opt-in, OFF by default, fully reversible.
|
|
10
|
-
*
|
|
9
|
+
* Opt-in, OFF by default, fully reversible. Direct web federation is gated on
|
|
10
|
+
* `AGENT_NATIVE_IDENTITY_HUB_URL`; the packaged Desktop SSO Canary may use the
|
|
11
|
+
* canonical Dispatch authority on canonical hosted apps, where Dispatch's
|
|
12
|
+
* default-off feature flag controls rollout:
|
|
11
13
|
*
|
|
12
|
-
* - UNSET →
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* - UNSET → normal browser requests remain protected and the login page
|
|
15
|
+
* renders no SSO button. Packaged Canary requests on canonical app origins
|
|
16
|
+
* may reach the flow, but Dispatch refuses them unless its rollout flag
|
|
17
|
+
* allows the user.
|
|
15
18
|
* - SET (e.g. `https://dispatch.agent-native.com`) → two routes mount:
|
|
16
19
|
* GET /_agent-native/identity/login
|
|
17
20
|
* 302 → `<HUB>/_agent-native/identity/authorize?app=<id>
|
|
@@ -59,6 +62,15 @@ export declare const IDENTITY_SSO_PROVIDER_ID = "agent-native";
|
|
|
59
62
|
* as an identity assertion.
|
|
60
63
|
*/
|
|
61
64
|
export declare const IDENTITY_SSO_SCOPE = "identity";
|
|
65
|
+
export declare const IDENTITY_SSO_DESKTOP_COMPLETE_PATH = "/_agent-native/identity/desktop-complete";
|
|
66
|
+
/**
|
|
67
|
+
* Direct web federation stays explicitly configured through
|
|
68
|
+
* `AGENT_NATIVE_IDENTITY_HUB_URL`. The packaged Desktop SSO Canary may use the
|
|
69
|
+
* canonical Dispatch authority without per-app deployment configuration, but
|
|
70
|
+
* only for canonical HTTPS Agent Native app origins. Dispatch's default-off
|
|
71
|
+
* feature flag remains the authoritative rollout gate.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveIdentityHubUrl(event: H3Event): string | undefined;
|
|
62
74
|
/**
|
|
63
75
|
* Handle a `/_agent-native/identity/*` request. `subpath` is the part after
|
|
64
76
|
* `/identity` (e.g. `/login`, `/callback`). Returns a 404 Response whenever
|
|
@@ -68,10 +80,8 @@ export declare const IDENTITY_SSO_SCOPE = "identity";
|
|
|
68
80
|
export declare function handleIdentitySso(event: H3Event, subpath: string): Promise<Response>;
|
|
69
81
|
/**
|
|
70
82
|
* Whether the given (already base-path-stripped) request path is one of the
|
|
71
|
-
* SSO routes that must bypass the blanket auth guard.
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* signed in to THIS app. Returns false when the feature is disabled, so the
|
|
75
|
-
* guard's behaviour is unchanged with the env unset.
|
|
83
|
+
* two SSO routes that must bypass the blanket auth guard. The handler makes
|
|
84
|
+
* the request-scoped enablement decision and fails closed with 404; no other
|
|
85
|
+
* identity subpath bypasses authentication.
|
|
76
86
|
*/
|
|
77
87
|
export declare function isIdentitySsoBypassPath(p: string): boolean;
|
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
* an identity authority (Dispatch) so a user logged in there can land in this
|
|
7
7
|
* app without re-entering credentials.
|
|
8
8
|
*
|
|
9
|
-
* Opt-in, OFF by default, fully reversible.
|
|
10
|
-
*
|
|
9
|
+
* Opt-in, OFF by default, fully reversible. Direct web federation is gated on
|
|
10
|
+
* `AGENT_NATIVE_IDENTITY_HUB_URL`; the packaged Desktop SSO Canary may use the
|
|
11
|
+
* canonical Dispatch authority on canonical hosted apps, where Dispatch's
|
|
12
|
+
* default-off feature flag controls rollout:
|
|
11
13
|
*
|
|
12
|
-
* - UNSET →
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* - UNSET → normal browser requests remain protected and the login page
|
|
15
|
+
* renders no SSO button. Packaged Canary requests on canonical app origins
|
|
16
|
+
* may reach the flow, but Dispatch refuses them unless its rollout flag
|
|
17
|
+
* allows the user.
|
|
15
18
|
* - SET (e.g. `https://dispatch.agent-native.com`) → two routes mount:
|
|
16
19
|
* GET /_agent-native/identity/login
|
|
17
20
|
* 302 → `<HUB>/_agent-native/identity/authorize?app=<id>
|
|
@@ -41,8 +44,8 @@
|
|
|
41
44
|
* and mint the same framework session. Removing the env returns the app to
|
|
42
45
|
* its prior auth with no residue.
|
|
43
46
|
*/
|
|
44
|
-
import {
|
|
45
|
-
import { getMethod } from "h3";
|
|
47
|
+
import { randomBytes } from "node:crypto";
|
|
48
|
+
import { getHeader, getMethod } from "h3";
|
|
46
49
|
import * as jose from "jose";
|
|
47
50
|
import { GOOGLE_AUTH_REQUIRED_MESSAGE, isGoogleSignInRequiredForEmail, } from "../org/auth-policy.js";
|
|
48
51
|
import { SIGN_IN_ENTRY_PATH } from "../shared/sign-in-journey.js";
|
|
@@ -50,7 +53,7 @@ import { getAppName } from "./app-name.js";
|
|
|
50
53
|
import { getSession, safeReturnPath, isExpectedAuthFailure } from "./auth.js";
|
|
51
54
|
import { getBetterAuth, getBetterAuthInternalAdapter, } from "./better-auth-instance.js";
|
|
52
55
|
import { createOAuthSession, getOrigin } from "./google-oauth.js";
|
|
53
|
-
import { createSsoState, consumeSsoState, isJtiReplayed, getIdentityHubUrl, isIdentitySsoEnabled, identitySsoLoginButtonHtml, } from "./identity-sso-store.js";
|
|
56
|
+
import { createSsoState, consumeSsoState, isJtiReplayed, getIdentityHubUrl, isIdentitySsoEnabled, identitySsoLoginButtonHtml, isCanonicalAgentNativeAppRequest, isDesktopSsoCanaryUserAgent, } from "./identity-sso-store.js";
|
|
54
57
|
export { getIdentityHubUrl, isIdentitySsoEnabled, identitySsoLoginButtonHtml };
|
|
55
58
|
/**
|
|
56
59
|
* The provider id recorded on the additive `account` row we link for an
|
|
@@ -67,8 +70,35 @@ export const IDENTITY_SSO_PROVIDER_ID = "agent-native";
|
|
|
67
70
|
* as an identity assertion.
|
|
68
71
|
*/
|
|
69
72
|
export const IDENTITY_SSO_SCOPE = "identity";
|
|
73
|
+
export const IDENTITY_SSO_DESKTOP_COMPLETE_PATH = "/_agent-native/identity/desktop-complete";
|
|
74
|
+
const DESKTOP_COMPLETION_NONCE = /^[A-Za-z0-9_-]{32,128}$/;
|
|
75
|
+
const DESKTOP_IDENTITY_HUB_URL = "https://dispatch.agent-native.com";
|
|
70
76
|
/** Identity tokens older than this are rejected even if `exp` is generous. */
|
|
71
77
|
const MAX_TOKEN_AGE_SECONDS = 10 * 60;
|
|
78
|
+
/**
|
|
79
|
+
* Direct web federation stays explicitly configured through
|
|
80
|
+
* `AGENT_NATIVE_IDENTITY_HUB_URL`. The packaged Desktop SSO Canary may use the
|
|
81
|
+
* canonical Dispatch authority without per-app deployment configuration, but
|
|
82
|
+
* only for canonical HTTPS Agent Native app origins. Dispatch's default-off
|
|
83
|
+
* feature flag remains the authoritative rollout gate.
|
|
84
|
+
*/
|
|
85
|
+
export function resolveIdentityHubUrl(event) {
|
|
86
|
+
const configured = getIdentityHubUrl();
|
|
87
|
+
if (configured)
|
|
88
|
+
return configured;
|
|
89
|
+
if (!isDesktopSsoCanaryUserAgent(getHeader(event, "user-agent"))) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
if (!isCanonicalAgentNativeAppRequest(getHeader(event, "host"), getHeader(event, "x-forwarded-proto"))) {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
return DESKTOP_IDENTITY_HUB_URL;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
72
102
|
/**
|
|
73
103
|
* A stable id for THIS app, sent to the hub as `?app=` so the authority can
|
|
74
104
|
* record / display which app requested sign-in. Best-effort, non-secret,
|
|
@@ -137,23 +167,12 @@ function errorPage(message, loginPath) {
|
|
|
137
167
|
`<p>${safe}</p><a href="${safeHref}">Back to sign in</a></div></body></html>`, 400);
|
|
138
168
|
}
|
|
139
169
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* ever used as the `password` argument to Better Auth's own
|
|
144
|
-
* `signUpEmail` / `signInEmail` — never returned, logged, or sent anywhere.
|
|
170
|
+
* Create a strong, random, NEVER-exposed credential for a JIT-created SSO
|
|
171
|
+
* user. It is used once by Better Auth's signup path and is intentionally
|
|
172
|
+
* unavailable to every login surface, including later SSO retries.
|
|
145
173
|
*/
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
// A salted SHA-256 over secret + email: stable across function executions
|
|
149
|
-
// (so the same SSO user always derives the same stand-in password) but
|
|
150
|
-
// unguessable without the deployment secret. This account's only sign-in
|
|
151
|
-
// path is the signature-verified hub token, so the value is never used by
|
|
152
|
-
// anyone but Better Auth's own signUpEmail.
|
|
153
|
-
const digest = createHash("sha256")
|
|
154
|
-
.update(`${secret}:agent-native-sso:${email}`)
|
|
155
|
-
.digest("base64url");
|
|
156
|
-
return `an-sso_${digest}`;
|
|
174
|
+
function createUnusableSsoCredential() {
|
|
175
|
+
return `an-sso_${randomBytes(32).toString("base64url")}`;
|
|
157
176
|
}
|
|
158
177
|
/**
|
|
159
178
|
* JIT-link the verified hub identity into THIS app's local Better Auth
|
|
@@ -193,7 +212,7 @@ async function jitLinkIdentity(identity) {
|
|
|
193
212
|
await auth.api.signUpEmail({
|
|
194
213
|
body: {
|
|
195
214
|
email: identity.email,
|
|
196
|
-
password:
|
|
215
|
+
password: createUnusableSsoCredential(),
|
|
197
216
|
name: identity.name || identity.email.split("@")[0] || "User",
|
|
198
217
|
},
|
|
199
218
|
});
|
|
@@ -304,7 +323,7 @@ async function verifyIdentityToken(token, expectedAudience) {
|
|
|
304
323
|
* route somehow gets mounted.
|
|
305
324
|
*/
|
|
306
325
|
export async function handleIdentitySso(event, subpath) {
|
|
307
|
-
const hub =
|
|
326
|
+
const hub = resolveIdentityHubUrl(event);
|
|
308
327
|
if (!hub) {
|
|
309
328
|
return new Response("Not found", { status: 404 });
|
|
310
329
|
}
|
|
@@ -413,15 +432,44 @@ export async function handleIdentitySso(event, subpath) {
|
|
|
413
432
|
const dest = safeReturnPath(stateResult.returnPath);
|
|
414
433
|
return redirect(event, dest);
|
|
415
434
|
}
|
|
435
|
+
if (sub === "/desktop-complete") {
|
|
436
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
437
|
+
return new Response("Method not allowed", { status: 405 });
|
|
438
|
+
}
|
|
439
|
+
let nonce = "";
|
|
440
|
+
try {
|
|
441
|
+
const url = new URL(event.node?.req?.url ?? event.path ?? "/", "http://an.invalid");
|
|
442
|
+
nonce = url.searchParams.get("nonce") || "";
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
return new Response("Invalid completion request", { status: 400 });
|
|
446
|
+
}
|
|
447
|
+
if (!DESKTOP_COMPLETION_NONCE.test(nonce)) {
|
|
448
|
+
return new Response("Invalid completion request", { status: 400 });
|
|
449
|
+
}
|
|
450
|
+
const current = await getSession(event).catch(() => null);
|
|
451
|
+
if (!current?.email) {
|
|
452
|
+
return new Response("Authentication required", { status: 401 });
|
|
453
|
+
}
|
|
454
|
+
return new Response('<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8">' +
|
|
455
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1">' +
|
|
456
|
+
"<title>Signed in</title></head><body>Signed in. You can close this window.</body></html>", {
|
|
457
|
+
status: 200,
|
|
458
|
+
headers: {
|
|
459
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
460
|
+
"Cache-Control": "no-store",
|
|
461
|
+
"Content-Security-Policy": "default-src 'none'; style-src 'none'",
|
|
462
|
+
"Referrer-Policy": "no-referrer",
|
|
463
|
+
},
|
|
464
|
+
});
|
|
465
|
+
}
|
|
416
466
|
return new Response("Not found", { status: 404 });
|
|
417
467
|
}
|
|
418
468
|
/**
|
|
419
469
|
* Whether the given (already base-path-stripped) request path is one of the
|
|
420
|
-
* SSO routes that must bypass the blanket auth guard.
|
|
421
|
-
*
|
|
422
|
-
*
|
|
423
|
-
* signed in to THIS app. Returns false when the feature is disabled, so the
|
|
424
|
-
* guard's behaviour is unchanged with the env unset.
|
|
470
|
+
* two SSO routes that must bypass the blanket auth guard. The handler makes
|
|
471
|
+
* the request-scoped enablement decision and fails closed with 404; no other
|
|
472
|
+
* identity subpath bypasses authentication.
|
|
425
473
|
*/
|
|
426
474
|
export function isIdentitySsoBypassPath(p) {
|
|
427
475
|
if (!isIdentitySsoEnabled())
|
package/dist/server/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { createEmbedStartRouteHandler, buildEmbedStartPath, type EmbedStartRoute
|
|
|
8
8
|
export { createEmbedSessionTicket, consumeEmbedSessionTicket, normalizeEmbedTargetPath, requestHasEmbedAuthMarker, resolveEmbedSessionFromRequest, setEmbedSessionCookie, signEmbedSessionToken, verifyEmbedSessionToken, type ConsumedEmbedSessionTicket, type ConsumeEmbedSessionTicketOptions, type EmbedSessionTicket, type EmbedSessionTicketInput, type EmbedSessionTokenClaims, type ResolvedEmbedSession, type VerifyEmbedSessionTokenResult, } from "./embed-session.js";
|
|
9
9
|
export { createSSEHandler, type SSEHandlerOptions } from "./sse.js";
|
|
10
10
|
export { mountAuthMiddleware, autoMountAuth, getSession, COOKIE_NAME, addSession, removeSession, getSessionEmail, getFrameworkSessionCookieValues, setFrameworkSessionCookie, clearFrameworkSessionCookies, runAuthGuard, setDesktopExchange, setDesktopExchangeError, safeReturnPath, type DesktopExchangeErrorPayload, type AuthSession, type AuthOptions, } from "./auth.js";
|
|
11
|
-
export { handleIdentitySso, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, } from "./identity-sso.js";
|
|
11
|
+
export { handleIdentitySso, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, IDENTITY_SSO_DESKTOP_COMPLETE_PATH, } from "./identity-sso.js";
|
|
12
12
|
export { requireEnvKey, type MissingKeyResponse } from "./missing-key.js";
|
|
13
13
|
export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } from "./org-admin.js";
|
|
14
14
|
export { verifyCaptcha, type CaptchaVerifyResult } from "./captcha.js";
|
package/dist/server/index.js
CHANGED
|
@@ -8,7 +8,7 @@ export { createEmbedStartRouteHandler, buildEmbedStartPath, } from "./embed-rout
|
|
|
8
8
|
export { createEmbedSessionTicket, consumeEmbedSessionTicket, normalizeEmbedTargetPath, requestHasEmbedAuthMarker, resolveEmbedSessionFromRequest, setEmbedSessionCookie, signEmbedSessionToken, verifyEmbedSessionToken, } from "./embed-session.js";
|
|
9
9
|
export { createSSEHandler } from "./sse.js";
|
|
10
10
|
export { mountAuthMiddleware, autoMountAuth, getSession, COOKIE_NAME, addSession, removeSession, getSessionEmail, getFrameworkSessionCookieValues, setFrameworkSessionCookie, clearFrameworkSessionCookies, runAuthGuard, setDesktopExchange, setDesktopExchangeError, safeReturnPath, } from "./auth.js";
|
|
11
|
-
export { handleIdentitySso, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, } from "./identity-sso.js";
|
|
11
|
+
export { handleIdentitySso, getIdentityHubUrl, isIdentitySsoEnabled, isIdentitySsoBypassPath, identitySsoLoginButtonHtml, IDENTITY_SSO_PROVIDER_ID, IDENTITY_SSO_SCOPE, IDENTITY_SSO_DESKTOP_COMPLETE_PATH, } from "./identity-sso.js";
|
|
12
12
|
export { requireEnvKey } from "./missing-key.js";
|
|
13
13
|
export { assertCurrentRequestUserIsOrgAdmin, currentRequestUserIsOrgAdmin, } from "./org-admin.js";
|
|
14
14
|
export { verifyCaptcha } from "./captcha.js";
|
|
@@ -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
|
}>>;
|
|
@@ -49,6 +49,34 @@ sendToAgentChat({
|
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### Keep user text separate from injected context
|
|
53
|
+
|
|
54
|
+
Treat the visible `message` as the user's request or the shortest clear
|
|
55
|
+
description of an app-initiated operation. Put context the user did not type —
|
|
56
|
+
IDs, URLs, filenames, current selection or screen state, serialized records,
|
|
57
|
+
upload instructions, and bounded source excerpts — in the `context` field.
|
|
58
|
+
`context` is model input carried through the agent turn; it is stripped from
|
|
59
|
+
the rendered user message, so do not concatenate it into `message` with labels,
|
|
60
|
+
blank-line sections, or `<context>` tags yourself.
|
|
61
|
+
|
|
62
|
+
Use the related surface for each kind of supporting input:
|
|
63
|
+
|
|
64
|
+
| Surface | Use for |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `message` | User-authored intent or a concise app action description |
|
|
67
|
+
| `context` | Derived metadata and bounded text needed to carry out that intent |
|
|
68
|
+
| `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
|
|
69
|
+
| `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
|
|
70
|
+
|
|
71
|
+
If an app builds a prompt from a form, selection, upload, or editor state, keep
|
|
72
|
+
the visible message short and pass the assembled details as `context`. Preserve
|
|
73
|
+
the user's actual freeform text in `message` when it is the request; do not
|
|
74
|
+
restate it as metadata in `context` unless the agent needs a structured copy.
|
|
75
|
+
|
|
76
|
+
This boundary applies to auto-submitted turns and prefills. A hidden context
|
|
77
|
+
field is not a license to send unbounded records or secrets: cap excerpts,
|
|
78
|
+
prefer stable IDs and URLs, and use an action or resource lookup for full data.
|
|
79
|
+
|
|
52
80
|
**From the UI, in the background:**
|
|
53
81
|
|
|
54
82
|
```ts
|
|
@@ -49,6 +49,34 @@ sendToAgentChat({
|
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### Keep user text separate from injected context
|
|
53
|
+
|
|
54
|
+
Treat the visible `message` as the user's request or the shortest clear
|
|
55
|
+
description of an app-initiated operation. Put context the user did not type —
|
|
56
|
+
IDs, URLs, filenames, current selection or screen state, serialized records,
|
|
57
|
+
upload instructions, and bounded source excerpts — in the `context` field.
|
|
58
|
+
`context` is model input carried through the agent turn; it is stripped from
|
|
59
|
+
the rendered user message, so do not concatenate it into `message` with labels,
|
|
60
|
+
blank-line sections, or `<context>` tags yourself.
|
|
61
|
+
|
|
62
|
+
Use the related surface for each kind of supporting input:
|
|
63
|
+
|
|
64
|
+
| Surface | Use for |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `message` | User-authored intent or a concise app action description |
|
|
67
|
+
| `context` | Derived metadata and bounded text needed to carry out that intent |
|
|
68
|
+
| `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
|
|
69
|
+
| `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
|
|
70
|
+
|
|
71
|
+
If an app builds a prompt from a form, selection, upload, or editor state, keep
|
|
72
|
+
the visible message short and pass the assembled details as `context`. Preserve
|
|
73
|
+
the user's actual freeform text in `message` when it is the request; do not
|
|
74
|
+
restate it as metadata in `context` unless the agent needs a structured copy.
|
|
75
|
+
|
|
76
|
+
This boundary applies to auto-submitted turns and prefills. A hidden context
|
|
77
|
+
field is not a license to send unbounded records or secrets: cap excerpts,
|
|
78
|
+
prefer stable IDs and URLs, and use an action or resource lookup for full data.
|
|
79
|
+
|
|
52
80
|
**From the UI, in the background:**
|
|
53
81
|
|
|
54
82
|
```ts
|
|
@@ -168,10 +168,33 @@ Set `A2A_SECRET` (same value) on all apps that must verify each other's identity
|
|
|
168
168
|
Each hosted `*.agent-native.com` app has its **own user store**, so "sign in once" is identity federation, not a shared cookie. **Dispatch is the identity authority.**
|
|
169
169
|
|
|
170
170
|
- **Opt-in per app via one env var:** set `AGENT_NATIVE_IDENTITY_HUB_URL=https://dispatch.agent-native.com` and the app shows a "Sign in with Agent-Native" option. **Unset = zero behavior change** — the whole path is dormant. Reversible at any time.
|
|
171
|
-
- **Flow:** app → `GET <hub>/_agent-native/identity/authorize?app=&redirect_uri=&state=` → user logs in at Dispatch → 302 back with a short-lived (`≤5min`) `A2A_SECRET`-signed identity JWT (`sub`/`email`/`name`/`org_domain`/`scope:"identity"`).
|
|
171
|
+
- **Flow:** app → `GET <hub>/_agent-native/identity/authorize?app=&redirect_uri=&state=` → user logs in at Dispatch → 302 back with a short-lived (`≤5min`) `A2A_SECRET`-signed identity JWT (`sub`/`email`/`name`/`org_domain`/`scope:"identity"`). Canonical clients require an exact registered app ID, origin, and callback path; localhost remains available for development. App verifies the token, **JIT-links strictly by verified email** (existing same-email user → reused unchanged; new email → created), then mints a normal local session.
|
|
172
172
|
- **Invariant (do not break):** identity rows are only ever **added** — never modified, renamed, or deleted. Enabling SSO logs users out, but they always log back into the **same email-matched account with data intact**. Email is the only thing that crosses the trust boundary; the app never trusts a user id, role, or org from the wire.
|
|
173
173
|
- **Canary rollout:** deploy with the env unset everywhere (no-op) → set it on **one** app (mail) only → verify (logout → SSO → Dispatch → back to the same pre-existing account, data intact, direct logins still work) → expand app-by-app → rollback = unset the env on that app's deploy (instant, no data change).
|
|
174
174
|
|
|
175
|
+
### Packaged Desktop workspace sign-in
|
|
176
|
+
|
|
177
|
+
For canonical first-party hosted apps, the packaged Desktop SSO Canary may compose the same
|
|
178
|
+
federation into one workspace sign-in. Dispatch owns the default-off
|
|
179
|
+
`desktop.workspace-sso` availability flag; every app still owns its local
|
|
180
|
+
session. The flag is never an auth or authorization boundary. Keep nonce,
|
|
181
|
+
signature, exact origin/callback, authenticated-session, app-binding, cookie
|
|
182
|
+
allowlist, revocation, and credential-custody checks unconditional. When the
|
|
183
|
+
flag is Off or unreadable, Desktop must leave ordinary per-app sign-in, sign-out,
|
|
184
|
+
and Settings unchanged. Never extend the broker to custom or third-party apps,
|
|
185
|
+
and never expose cookies, identity tokens, or provider credentials through IPC.
|
|
186
|
+
Canonical hosted apps recognize packaged Desktop requests without per-app
|
|
187
|
+
identity-hub environment configuration. Ordinary browsers and self-hosted apps
|
|
188
|
+
still require `AGENT_NATIVE_IDENTITY_HUB_URL`.
|
|
189
|
+
Stable Desktop builds must not initialize the broker until a later release is
|
|
190
|
+
separately approved.
|
|
191
|
+
Treat the Canary user-agent marker only as an availability hint, never as
|
|
192
|
+
remote attestation or an authentication boundary. Bind supervised acceptance
|
|
193
|
+
to exact signed-artifact provenance.
|
|
194
|
+
The Canary must not intercept anonymous app sign-in navigation. The user
|
|
195
|
+
explicitly chooses workspace sign-in in Desktop Settings for the active
|
|
196
|
+
registered app; ordinary app sign-in remains the default path.
|
|
197
|
+
|
|
175
198
|
Full runbook + flow detail: [Cross-App SSO doc](/docs/cross-app-sso).
|
|
176
199
|
|
|
177
200
|
## Builder Browser Access
|
|
@@ -49,6 +49,34 @@ sendToAgentChat({
|
|
|
49
49
|
});
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
### Keep user text separate from injected context
|
|
53
|
+
|
|
54
|
+
Treat the visible `message` as the user's request or the shortest clear
|
|
55
|
+
description of an app-initiated operation. Put context the user did not type —
|
|
56
|
+
IDs, URLs, filenames, current selection or screen state, serialized records,
|
|
57
|
+
upload instructions, and bounded source excerpts — in the `context` field.
|
|
58
|
+
`context` is model input carried through the agent turn; it is stripped from
|
|
59
|
+
the rendered user message, so do not concatenate it into `message` with labels,
|
|
60
|
+
blank-line sections, or `<context>` tags yourself.
|
|
61
|
+
|
|
62
|
+
Use the related surface for each kind of supporting input:
|
|
63
|
+
|
|
64
|
+
| Surface | Use for |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `message` | User-authored intent or a concise app action description |
|
|
67
|
+
| `context` | Derived metadata and bounded text needed to carry out that intent |
|
|
68
|
+
| `setAgentChatContextItem` | Context staged for a later user-submitted prompt; keep it keyed so updates replace stale context |
|
|
69
|
+
| `images`, `referenceImagePaths`, attachments | Binary or visual inputs; describe only the handling instructions in `context` |
|
|
70
|
+
|
|
71
|
+
If an app builds a prompt from a form, selection, upload, or editor state, keep
|
|
72
|
+
the visible message short and pass the assembled details as `context`. Preserve
|
|
73
|
+
the user's actual freeform text in `message` when it is the request; do not
|
|
74
|
+
restate it as metadata in `context` unless the agent needs a structured copy.
|
|
75
|
+
|
|
76
|
+
This boundary applies to auto-submitted turns and prefills. A hidden context
|
|
77
|
+
field is not a license to send unbounded records or secrets: cap excerpts,
|
|
78
|
+
prefer stable IDs and URLs, and use an action or resource lookup for full data.
|
|
79
|
+
|
|
52
80
|
**From the UI, in the background:**
|
|
53
81
|
|
|
54
82
|
```ts
|
|
@@ -129,8 +129,12 @@ Extensions have full access to app data via helpers injected into the iframe
|
|
|
129
129
|
in chat, record passive control/selection output at
|
|
130
130
|
`inline-ui:<extensionId>:output` in application state so the agent can read it
|
|
131
131
|
later with `readAppState`.
|
|
132
|
-
- `agentNative.chat.send(message, opts?)`
|
|
133
|
-
value back into the current agent chat.
|
|
132
|
+
- `agentNative.chat.send(message, opts?)` - send a visible prompt or selected
|
|
133
|
+
value back into the current agent chat. The extension bridge keeps messages
|
|
134
|
+
draft-only unless `opts.submit === true`; pass `{ submit: true }` only from a
|
|
135
|
+
user-triggered Apply or Submit action. Never call it from polling, refresh, or
|
|
136
|
+
error handlers; use application state or `agentNative.ui.output` for passive
|
|
137
|
+
results.
|
|
134
138
|
|
|
135
139
|
For transient inline generative UI, `extensionData` is host-browser
|
|
136
140
|
`localStorage`: the agent cannot read it, it does not sync across devices, it
|
|
@@ -137,7 +137,7 @@ await dbExec("UPDATE notes SET title = 'Updated Title' WHERE id = 'abc'");
|
|
|
137
137
|
| `extensionData.get(collection, id, opts?)` | Get a single item by id | `extensionData.get('todos', 'todo-1')` |
|
|
138
138
|
| `extensionData.remove(collection, id, opts?)` | Delete an item | `extensionData.remove('todos', 'todo-1')` |
|
|
139
139
|
| `agentNative.ui.output(value, opts?)` | Record passive inline UI output in application state | `agentNative.ui.output({ threshold })` |
|
|
140
|
-
| `agentNative.chat.send(message, opts?)` | Send a visible prompt or selected value back to chat | `agentNative.chat.send('Use Q2', { context: { q: 2 } })`
|
|
140
|
+
| `agentNative.chat.send(message, opts?)` | Send a visible prompt or selected value back to chat | `agentNative.chat.send('Use Q2', { submit: true, context: { q: 2 } })` |
|
|
141
141
|
|
|
142
142
|
## Persisting Custom Data
|
|
143
143
|
|
|
@@ -76,7 +76,9 @@ The stored payload includes `value`, `updatedAt`, `extensionId`, and
|
|
|
76
76
|
|
|
77
77
|
Use `agentNative.chat.send(message, opts?)` or `sendToAgentChat(message, opts?)`
|
|
78
78
|
for a visible Apply/Submit button that should send a prompt or selected value
|
|
79
|
-
into the chat.
|
|
79
|
+
into the chat. Pass `{ submit: true }` explicitly for that user-triggered
|
|
80
|
+
action; extension messages are draft-only otherwise. Never call these helpers
|
|
81
|
+
from polling, refresh, or error handlers.
|
|
80
82
|
|
|
81
83
|
## App Data And State
|
|
82
84
|
|
|
@@ -161,6 +161,33 @@ These deploys must provide `APP_NAME` or a derivable app URL (`APP_URL`,
|
|
|
161
161
|
subdomains, set `AGENT_NATIVE_SHARE_COOKIE_DOMAIN=1` alongside
|
|
162
162
|
`COOKIE_DOMAIN`.
|
|
163
163
|
|
|
164
|
+
## Packaged Desktop workspace SSO {#desktop-workspace-sso}
|
|
165
|
+
|
|
166
|
+
The signed Agent Native Desktop SSO Canary composes hosted federation into one
|
|
167
|
+
workspace sign-in for canonical first-party apps. Dispatch remains the identity
|
|
168
|
+
authority, while every app mints and owns its normal local session. Electron
|
|
169
|
+
keeps the authority session in a dedicated partition and copies only the target
|
|
170
|
+
app's allowlisted session cookie into `persist:app-<id>`; provider credentials,
|
|
171
|
+
identity tokens, and another app's cookies never enter renderer IPC.
|
|
172
|
+
|
|
173
|
+
The Dispatch-owned `desktop.workspace-sso` feature flag is default-off and
|
|
174
|
+
controls availability only. Missing, malformed, unreadable, and explicit-Off
|
|
175
|
+
state preserve ordinary per-app sign-in and sign-out. Nonce binding, signed
|
|
176
|
+
exchange validation, exact origin and callback allowlists, authenticated-session
|
|
177
|
+
requirements, session isolation, and revocation remain unconditional in every
|
|
178
|
+
flag state. Custom and third-party apps are excluded.
|
|
179
|
+
Canonical hosted apps recognize packaged Desktop requests without per-app
|
|
180
|
+
identity-hub environment configuration. Ordinary browsers and self-hosted apps
|
|
181
|
+
still require `AGENT_NATIVE_IDENTITY_HUB_URL` to opt in.
|
|
182
|
+
Stable Desktop builds do not initialize the broker until a later release is
|
|
183
|
+
separately approved.
|
|
184
|
+
The Canary user-agent marker is an availability hint rather than remote
|
|
185
|
+
attestation. Authentication and authorization never trust it; supervised
|
|
186
|
+
acceptance binds the client through exact signed-artifact provenance.
|
|
187
|
+
The Canary does not intercept anonymous app sign-in navigation. The user must
|
|
188
|
+
explicitly choose workspace sign-in in Desktop Settings for the active
|
|
189
|
+
registered app; ordinary app sign-in remains the default path.
|
|
190
|
+
|
|
164
191
|
## QA Accounts {#qa-accounts}
|
|
165
192
|
|
|
166
193
|
Local development and tests skip password-signup email verification by default,
|