@agent-native/core 0.101.6 → 0.101.10
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/core/CHANGELOG.md +28 -0
- package/corpus/core/docs/content/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/artifact-response.ts +255 -12
- package/corpus/core/src/agent/thread-data-builder.ts +84 -11
- package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
- package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
- package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
- package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
- package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
- package/corpus/core/src/integrations/adapters/discord.ts +3 -1
- package/corpus/core/src/integrations/adapters/email.ts +4 -1
- package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
- package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
- package/corpus/core/src/integrations/adapters/slack.ts +38 -9
- package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
- package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
- package/corpus/core/src/integrations/index.ts +1 -0
- package/corpus/core/src/integrations/types.ts +9 -2
- package/corpus/core/src/integrations/webhook-handler.ts +69 -45
- package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
- package/corpus/core/src/server/auth.ts +25 -1
- package/corpus/core/src/server/builder-browser.ts +353 -1
- package/corpus/core/src/server/core-routes-plugin.ts +601 -223
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
- package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
- package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
- package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
- package/corpus/templates/content/AGENTS.md +32 -9
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
- package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
- package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
- package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
- package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
- package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +1071 -170
- package/corpus/templates/content/actions/_database-utils.ts +104 -13
- package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
- package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
- package/corpus/templates/content/actions/_property-utils.ts +7 -1
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
- package/corpus/templates/content/actions/configure-document-property.ts +11 -0
- package/corpus/templates/content/actions/create-content-database.ts +11 -0
- package/corpus/templates/content/actions/create-document.ts +9 -0
- package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
- package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
- package/corpus/templates/content/actions/export-content-source.ts +1 -0
- package/corpus/templates/content/actions/get-content-database.ts +13 -1
- package/corpus/templates/content/actions/get-document.ts +12 -2
- package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
- package/corpus/templates/content/actions/import-content-source.ts +6 -0
- package/corpus/templates/content/actions/list-documents.ts +3 -0
- package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
- package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
- package/corpus/templates/content/actions/pull-document.ts +2 -0
- package/corpus/templates/content/actions/search-documents.ts +5 -1
- package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
- package/corpus/templates/content/actions/update-document.ts +16 -1
- package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
- package/corpus/templates/content/actions/view-screen.ts +15 -2
- package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +250 -49
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
- package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2414 -589
- package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
- package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
- package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
- package/corpus/templates/content/app/global.css +18 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
- package/corpus/templates/content/app/i18n/zh-TW.ts +30 -0
- package/corpus/templates/content/app/i18n-data.ts +339 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
- package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
- package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
- package/corpus/templates/content/parity/matrix.md +27 -25
- package/corpus/templates/content/parity/matrix.ts +50 -2
- package/corpus/templates/content/server/db/schema.ts +51 -0
- package/corpus/templates/content/server/lib/document-context.ts +76 -0
- package/corpus/templates/content/server/lib/public-documents.ts +3 -0
- package/corpus/templates/content/server/plugins/auth.ts +1 -0
- package/corpus/templates/content/server/plugins/db.ts +59 -0
- package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
- package/corpus/templates/content/shared/api.ts +87 -1
- package/corpus/templates/content/shared/builder-mdx.ts +705 -15
- package/corpus/templates/content/shared/content-source.ts +7 -0
- package/corpus/templates/content/shared/nfm.ts +3 -3
- package/corpus/templates/content/shared/properties.ts +2 -0
- package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
- package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
- package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/design/app/i18n/en-US.ts +1 -0
- package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
- package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
- package/corpus/templates/design/shared/review-anchor.ts +48 -0
- package/corpus/templates/plan/server/plan-mdx.ts +53 -7
- package/dist/a2a/artifact-response.d.ts +15 -0
- package/dist/a2a/artifact-response.d.ts.map +1 -1
- package/dist/a2a/artifact-response.js +204 -5
- package/dist/a2a/artifact-response.js.map +1 -1
- package/dist/agent/thread-data-builder.d.ts +6 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +71 -8
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
- package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
- package/dist/client/review/ReviewCommentComposer.js +3 -3
- package/dist/client/review/ReviewCommentComposer.js.map +1 -1
- package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
- package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
- package/dist/client/review/ReviewThreadPanel.js +6 -4
- package/dist/client/review/ReviewThreadPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts +3 -0
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +11 -4
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +8 -4
- package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
- package/dist/client/settings/useBuilderStatus.js +19 -6
- package/dist/client/settings/useBuilderStatus.js.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
- package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
- package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +72 -6
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/adapters/discord.d.ts.map +1 -1
- package/dist/integrations/adapters/discord.js +1 -0
- package/dist/integrations/adapters/discord.js.map +1 -1
- package/dist/integrations/adapters/email.d.ts.map +1 -1
- package/dist/integrations/adapters/email.js +2 -0
- package/dist/integrations/adapters/email.js.map +1 -1
- package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
- package/dist/integrations/adapters/google-docs.js +2 -0
- package/dist/integrations/adapters/google-docs.js.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
- package/dist/integrations/adapters/microsoft-teams.js +1 -0
- package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +21 -6
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/adapters/telegram.d.ts.map +1 -1
- package/dist/integrations/adapters/telegram.js +11 -2
- package/dist/integrations/adapters/telegram.js.map +1 -1
- package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
- package/dist/integrations/adapters/whatsapp.js +3 -0
- package/dist/integrations/adapters/whatsapp.js.map +1 -1
- package/dist/integrations/index.d.ts +1 -1
- package/dist/integrations/index.d.ts.map +1 -1
- package/dist/integrations/index.js.map +1 -1
- package/dist/integrations/types.d.ts +8 -2
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +40 -43
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
- package/dist/server/agent-chat/action-filters-a2a.js +1 -0
- package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
- package/dist/server/auth.d.ts +1 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +19 -2
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts +78 -0
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +246 -1
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +41 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +260 -14
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/docs/content/deployment.mdx +9 -7
- package/docs/content/locales/ar-SA/deployment.mdx +9 -7
- package/docs/content/locales/de-DE/deployment.mdx +9 -7
- package/docs/content/locales/es-ES/deployment.mdx +9 -7
- package/docs/content/locales/fr-FR/deployment.mdx +9 -7
- package/docs/content/locales/hi-IN/deployment.mdx +9 -7
- package/docs/content/locales/ja-JP/deployment.mdx +9 -7
- package/docs/content/locales/ko-KR/deployment.mdx +9 -7
- package/docs/content/locales/pt-BR/deployment.mdx +9 -7
- package/docs/content/locales/zh-CN/deployment.mdx +9 -7
- package/docs/content/locales/zh-TW/deployment.mdx +9 -7
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
appendA2AArtifactLinks,
|
|
5
|
+
extractA2AArtifactIdentities,
|
|
5
6
|
type A2AToolResultSummary,
|
|
6
7
|
} from "../a2a/artifact-response.js";
|
|
7
8
|
import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
import {
|
|
36
37
|
buildAssistantMessage,
|
|
37
38
|
extractThreadMeta,
|
|
39
|
+
threadDataToEngineMessages,
|
|
38
40
|
} from "../agent/thread-data-builder.js";
|
|
39
41
|
import { attachToolSearch } from "../agent/tool-search.js";
|
|
40
42
|
import { createThread, getThread } from "../chat-threads/store.js";
|
|
@@ -63,7 +65,11 @@ import {
|
|
|
63
65
|
} from "./pending-tasks-store.js";
|
|
64
66
|
import { integrationScopeSubjectKey } from "./scope-store.js";
|
|
65
67
|
import { getThreadMapping, saveThreadMapping } from "./thread-mapping-store.js";
|
|
66
|
-
import type {
|
|
68
|
+
import type {
|
|
69
|
+
PlatformAdapter,
|
|
70
|
+
IncomingMessage,
|
|
71
|
+
PlatformDeliveryReceipt,
|
|
72
|
+
} from "./types.js";
|
|
67
73
|
import {
|
|
68
74
|
listIntegrationUsageBudgets,
|
|
69
75
|
releaseIntegrationUsageBudget,
|
|
@@ -710,34 +716,7 @@ async function processIncomingMessage(
|
|
|
710
716
|
}
|
|
711
717
|
const existingMessages: EngineMessage[] = [];
|
|
712
718
|
if (thread?.threadData) {
|
|
713
|
-
|
|
714
|
-
const data = JSON.parse(thread.threadData);
|
|
715
|
-
if (Array.isArray(data.messages)) {
|
|
716
|
-
for (const msg of data.messages) {
|
|
717
|
-
const m = msg.message ?? msg;
|
|
718
|
-
const textContent =
|
|
719
|
-
typeof m.content === "string"
|
|
720
|
-
? m.content
|
|
721
|
-
: Array.isArray(m.content)
|
|
722
|
-
? m.content
|
|
723
|
-
.filter((c: any) => c.type === "text")
|
|
724
|
-
.map((c: any) => c.text)
|
|
725
|
-
.join("\n")
|
|
726
|
-
: "";
|
|
727
|
-
if (m.role === "user") {
|
|
728
|
-
existingMessages.push({
|
|
729
|
-
role: "user",
|
|
730
|
-
content: [{ type: "text", text: textContent }],
|
|
731
|
-
});
|
|
732
|
-
} else if (m.role === "assistant") {
|
|
733
|
-
existingMessages.push({
|
|
734
|
-
role: "assistant",
|
|
735
|
-
content: [{ type: "text", text: textContent }],
|
|
736
|
-
});
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
} catch {}
|
|
719
|
+
existingMessages.push(...threadDataToEngineMessages(thread.threadData));
|
|
741
720
|
}
|
|
742
721
|
|
|
743
722
|
// Add the new user message. Include verified platform identity as lightweight
|
|
@@ -988,12 +967,11 @@ async function processIncomingMessage(
|
|
|
988
967
|
// preview card.
|
|
989
968
|
const baseUrl = process.env.APP_URL || process.env.URL || "";
|
|
990
969
|
const appBaseUrl = baseUrl ? withConfiguredAppBasePath(baseUrl) : "";
|
|
970
|
+
const toolResults = collectToolResultSummaries(completedRun);
|
|
991
971
|
if (!suppressPlatformReply) {
|
|
992
|
-
responseText = appendA2AArtifactLinks(
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
{ baseUrl: appBaseUrl || undefined },
|
|
996
|
-
);
|
|
972
|
+
responseText = appendA2AArtifactLinks(responseText, toolResults, {
|
|
973
|
+
baseUrl: appBaseUrl || undefined,
|
|
974
|
+
});
|
|
997
975
|
}
|
|
998
976
|
const threadDeepLinkUrl =
|
|
999
977
|
appBaseUrl && threadId
|
|
@@ -1002,34 +980,60 @@ async function processIncomingMessage(
|
|
|
1002
980
|
|
|
1003
981
|
// Format and send back to platform — update the "thinking…"
|
|
1004
982
|
// placeholder in place if the adapter supplied one.
|
|
983
|
+
let deliveredResponse:
|
|
984
|
+
| {
|
|
985
|
+
platform: string;
|
|
986
|
+
status: "delivered";
|
|
987
|
+
text: string;
|
|
988
|
+
deliveredAt: string;
|
|
989
|
+
messageRefs?: string[];
|
|
990
|
+
}
|
|
991
|
+
| undefined;
|
|
1005
992
|
if (!suppressPlatformReply) {
|
|
1006
993
|
const outgoing = adapter.formatAgentResponse(responseText, {
|
|
1007
994
|
threadDeepLinkUrl,
|
|
1008
995
|
});
|
|
1009
|
-
let
|
|
996
|
+
let deliveryReceipt: void | PlatformDeliveryReceipt;
|
|
1010
997
|
if (queuedA2AContinuation && progress?.ref) {
|
|
1011
998
|
// Post substantive parent results as a normal thread reply while
|
|
1012
999
|
// the one continuation that claimed this resumable stream keeps
|
|
1013
1000
|
// it open for its eventual terminal result.
|
|
1014
|
-
await adapter.sendResponse(outgoing, incoming, {
|
|
1001
|
+
deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
|
|
1015
1002
|
placeholderRef: opts.placeholderRef,
|
|
1016
1003
|
});
|
|
1017
|
-
delivered = true;
|
|
1018
1004
|
} else if (progress) {
|
|
1019
1005
|
try {
|
|
1020
|
-
await progress.complete(outgoing);
|
|
1021
|
-
delivered = true;
|
|
1006
|
+
deliveryReceipt = await progress.complete(outgoing);
|
|
1022
1007
|
} catch {
|
|
1023
|
-
await adapter.sendResponse(
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1008
|
+
deliveryReceipt = await adapter.sendResponse(
|
|
1009
|
+
outgoing,
|
|
1010
|
+
incoming,
|
|
1011
|
+
{
|
|
1012
|
+
placeholderRef: opts.placeholderRef,
|
|
1013
|
+
},
|
|
1014
|
+
);
|
|
1027
1015
|
}
|
|
1028
1016
|
} else {
|
|
1029
|
-
await adapter.sendResponse(outgoing, incoming, {
|
|
1017
|
+
deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
|
|
1030
1018
|
placeholderRef: opts.placeholderRef,
|
|
1031
1019
|
});
|
|
1032
|
-
|
|
1020
|
+
}
|
|
1021
|
+
const delivered = deliveryReceipt?.status === "delivered";
|
|
1022
|
+
if (!delivered) {
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
`${incoming.platform} response completed without delivery proof`,
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
if (delivered) {
|
|
1028
|
+
deliveredResponse = {
|
|
1029
|
+
platform: incoming.platform,
|
|
1030
|
+
status: "delivered",
|
|
1031
|
+
text: outgoing.text,
|
|
1032
|
+
deliveredAt: new Date().toISOString(),
|
|
1033
|
+
...(deliveryReceipt?.messageRefs?.length
|
|
1034
|
+
? { messageRefs: deliveryReceipt.messageRefs }
|
|
1035
|
+
: {}),
|
|
1036
|
+
};
|
|
1033
1037
|
}
|
|
1034
1038
|
if (slackInputRequest && delivered && incoming.senderId) {
|
|
1035
1039
|
await setIntegrationAwaitingInput({
|
|
@@ -1078,6 +1082,8 @@ async function processIncomingMessage(
|
|
|
1078
1082
|
incoming.text,
|
|
1079
1083
|
completedRun,
|
|
1080
1084
|
thread,
|
|
1085
|
+
deliveredResponse,
|
|
1086
|
+
toolResults,
|
|
1081
1087
|
);
|
|
1082
1088
|
await recordIntegrationUsage({
|
|
1083
1089
|
usage,
|
|
@@ -1533,6 +1539,14 @@ async function persistThreadData(
|
|
|
1533
1539
|
userText: string,
|
|
1534
1540
|
completedRun: ActiveRun,
|
|
1535
1541
|
thread: any,
|
|
1542
|
+
deliveredResponse?: {
|
|
1543
|
+
platform: string;
|
|
1544
|
+
status: "delivered";
|
|
1545
|
+
text: string;
|
|
1546
|
+
deliveredAt: string;
|
|
1547
|
+
messageRefs?: string[];
|
|
1548
|
+
},
|
|
1549
|
+
toolResults: A2AToolResultSummary[] = [],
|
|
1536
1550
|
): Promise<void> {
|
|
1537
1551
|
try {
|
|
1538
1552
|
let repo: any;
|
|
@@ -1556,6 +1570,16 @@ async function persistThreadData(
|
|
|
1556
1570
|
completedRun.events ?? [],
|
|
1557
1571
|
completedRun.runId,
|
|
1558
1572
|
);
|
|
1573
|
+
if (assistantMsg) {
|
|
1574
|
+
assistantMsg.metadata.integrationDeliveryAttempted = true;
|
|
1575
|
+
if (deliveredResponse) {
|
|
1576
|
+
assistantMsg.metadata.integrationDelivery = deliveredResponse;
|
|
1577
|
+
const artifactIdentities = extractA2AArtifactIdentities(toolResults);
|
|
1578
|
+
if (artifactIdentities.length > 0) {
|
|
1579
|
+
assistantMsg.metadata.integrationArtifacts = artifactIdentities;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1559
1583
|
|
|
1560
1584
|
repo.messages.push(userMsg);
|
|
1561
1585
|
if (assistantMsg) {
|
|
@@ -110,6 +110,7 @@ export function assembleA2AFinalResponse(
|
|
|
110
110
|
const finalText = appendA2AArtifactLinks(responseText, [...toolResults], {
|
|
111
111
|
baseUrl: options.baseUrl ?? resolveArtifactBaseUrl(options.event),
|
|
112
112
|
includeReferencedArtifacts: true,
|
|
113
|
+
includePersistedArtifactMarker: true,
|
|
113
114
|
});
|
|
114
115
|
if (terminalError && !finalText.trim()) {
|
|
115
116
|
throw new Error(formatA2ATerminalError(terminalError));
|
|
@@ -104,9 +104,12 @@ import type { BetterAuthConfig } from "./better-auth-instance.js";
|
|
|
104
104
|
import {
|
|
105
105
|
BUILDER_CONNECT_OWNER_COOKIE,
|
|
106
106
|
BUILDER_CONNECT_PARAM,
|
|
107
|
+
BUILDER_RELAY_PATH,
|
|
108
|
+
BUILDER_RELAY_STATE_PARAM,
|
|
107
109
|
BUILDER_STATE_PARAM,
|
|
108
110
|
verifyBuilderCallbackStateAndGetOwner,
|
|
109
111
|
verifyBuilderConnectTokenAndGetOwner,
|
|
112
|
+
verifyBuilderPreviewRelayStateForCallback,
|
|
110
113
|
} from "./builder-browser.js";
|
|
111
114
|
import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
|
|
112
115
|
import {
|
|
@@ -1522,7 +1525,15 @@ function verifiedBuilderConnectOwnerFromUrl(url: string): string | null {
|
|
|
1522
1525
|
return verifyBuilderConnectTokenAndGetOwner(token);
|
|
1523
1526
|
}
|
|
1524
1527
|
|
|
1525
|
-
function shouldBypassAuthForBuilderConnect(
|
|
1528
|
+
export function shouldBypassAuthForBuilderConnect(
|
|
1529
|
+
event: H3Event,
|
|
1530
|
+
p: string,
|
|
1531
|
+
): boolean {
|
|
1532
|
+
// The preview-safe second hop is authenticated by its timestamped HMAC and
|
|
1533
|
+
// one-shot pending row. It cannot carry a browser session from the corporate
|
|
1534
|
+
// callback deployment, so let the route perform that stronger check itself.
|
|
1535
|
+
if (p === BUILDER_RELAY_PATH) return true;
|
|
1536
|
+
|
|
1526
1537
|
if (p === "/_agent-native/builder/connect") {
|
|
1527
1538
|
const url = event.node?.req?.url ?? event.path ?? "/";
|
|
1528
1539
|
return Boolean(verifiedBuilderConnectOwnerFromUrl(url));
|
|
@@ -1537,6 +1548,19 @@ function shouldBypassAuthForBuilderConnect(event: H3Event, p: string): boolean {
|
|
|
1537
1548
|
BUILDER_STATE_PARAM,
|
|
1538
1549
|
)
|
|
1539
1550
|
: null;
|
|
1551
|
+
const relayState =
|
|
1552
|
+
queryStart >= 0
|
|
1553
|
+
? new URLSearchParams(url.slice(queryStart + 1)).get(
|
|
1554
|
+
BUILDER_RELAY_STATE_PARAM,
|
|
1555
|
+
)
|
|
1556
|
+
: null;
|
|
1557
|
+
if (relayState) {
|
|
1558
|
+
try {
|
|
1559
|
+
if (verifyBuilderPreviewRelayStateForCallback(relayState)) return true;
|
|
1560
|
+
} catch {
|
|
1561
|
+
// Dedicated relay secret missing: let the auth guard fail closed.
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1540
1564
|
// The signed `_an_state` authenticates this specific Builder callback
|
|
1541
1565
|
// flow back to our app. A stale localhost session cookie can otherwise
|
|
1542
1566
|
// make the global guard reject the callback before the handler gets to
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createHash,
|
|
3
|
+
createHmac,
|
|
4
|
+
randomBytes,
|
|
5
|
+
timingSafeEqual,
|
|
6
|
+
} from "node:crypto";
|
|
2
7
|
|
|
3
8
|
import type { H3Event } from "h3";
|
|
4
9
|
import { getHeader } from "h3";
|
|
@@ -15,6 +20,349 @@ const BUILDER_BROWSER_HOST = "agent-native-browser";
|
|
|
15
20
|
const BUILDER_BROWSER_CLIENT_ID = "Agent Native Browser";
|
|
16
21
|
|
|
17
22
|
export const BUILDER_CALLBACK_PATH = "/_agent-native/builder/callback";
|
|
23
|
+
export const BUILDER_RELAY_PATH = "/_agent-native/builder/relay";
|
|
24
|
+
export const BUILDER_RELAY_STATE_PARAM = "_an_relay";
|
|
25
|
+
export const BUILDER_RELAY_SECRET_ENV = "AGENT_NATIVE_BUILDER_RELAY_SECRET";
|
|
26
|
+
export const BUILDER_RELAY_TARGET_ORIGINS_ENV =
|
|
27
|
+
"AGENT_NATIVE_BUILDER_RELAY_TARGET_ORIGINS";
|
|
28
|
+
export const BUILDER_RELAY_TIMESTAMP_HEADER = "x-agent-native-relay-timestamp";
|
|
29
|
+
export const BUILDER_RELAY_FLOW_HEADER = "x-agent-native-relay-flow";
|
|
30
|
+
export const BUILDER_RELAY_SIGNATURE_HEADER = "x-agent-native-relay-signature";
|
|
31
|
+
|
|
32
|
+
const BUILDER_RELAY_PURPOSE = "builder-preview-callback-relay";
|
|
33
|
+
const BUILDER_RELAY_STATE_VERSION = 1;
|
|
34
|
+
const BUILDER_RELAY_TTL_MS = 10 * 60 * 1000;
|
|
35
|
+
const BUILDER_RELAY_REQUEST_SKEW_MS = 2 * 60 * 1000;
|
|
36
|
+
|
|
37
|
+
export interface BuilderPreviewRelayState {
|
|
38
|
+
v: 1;
|
|
39
|
+
purpose: typeof BUILDER_RELAY_PURPOSE;
|
|
40
|
+
flowId: string;
|
|
41
|
+
ownerEmail: string;
|
|
42
|
+
targetOrigin: string;
|
|
43
|
+
basePath: string;
|
|
44
|
+
iat: number;
|
|
45
|
+
exp: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface BuilderRelayCredentials {
|
|
49
|
+
privateKey: string;
|
|
50
|
+
publicKey: string;
|
|
51
|
+
userId: string | null;
|
|
52
|
+
orgName: string | null;
|
|
53
|
+
orgKind: string | null;
|
|
54
|
+
subscription: string | null;
|
|
55
|
+
subscriptionLevel: string | null;
|
|
56
|
+
subscriptionName: string | null;
|
|
57
|
+
isEnterprise: boolean | null;
|
|
58
|
+
isFreeAccount: boolean | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface BuilderRelayRequestBody {
|
|
62
|
+
relayState: string;
|
|
63
|
+
credentials: BuilderRelayCredentials;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function builderRelaySecret(): string {
|
|
67
|
+
const secret = process.env[BUILDER_RELAY_SECRET_ENV]?.trim();
|
|
68
|
+
if (!secret) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${BUILDER_RELAY_SECRET_ENV} is required for Builder preview authorization relay.`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (secret.length < 32) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`${BUILDER_RELAY_SECRET_ENV} must be at least 32 characters long.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return secret;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function builderRelayMac(value: string): string {
|
|
82
|
+
return createHmac("sha256", builderRelaySecret())
|
|
83
|
+
.update(value)
|
|
84
|
+
.digest("base64url");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function safeEqualText(expected: string, actual: string): boolean {
|
|
88
|
+
const expectedBuffer = Buffer.from(expected);
|
|
89
|
+
const actualBuffer = Buffer.from(actual);
|
|
90
|
+
return (
|
|
91
|
+
expectedBuffer.length === actualBuffer.length &&
|
|
92
|
+
timingSafeEqual(expectedBuffer, actualBuffer)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeBuilderRelayBasePath(value: string): string | null {
|
|
97
|
+
if (!value) return "";
|
|
98
|
+
if (!value.startsWith("/") || value.includes("?") || value.includes("#")) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const normalized = value.replace(/\/+$/, "");
|
|
102
|
+
if (normalized.split("/").some((part) => part === "." || part === "..")) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return normalized;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function isSafeBuilderRelayTargetOrigin(value: string): boolean {
|
|
109
|
+
try {
|
|
110
|
+
const url = new URL(value);
|
|
111
|
+
if (
|
|
112
|
+
url.origin !== value ||
|
|
113
|
+
url.username ||
|
|
114
|
+
url.password ||
|
|
115
|
+
url.hostname.includes("*")
|
|
116
|
+
)
|
|
117
|
+
return false;
|
|
118
|
+
const hostname = url.hostname.toLowerCase();
|
|
119
|
+
const loopback =
|
|
120
|
+
hostname === "localhost" ||
|
|
121
|
+
hostname === "127.0.0.1" ||
|
|
122
|
+
hostname === "::1" ||
|
|
123
|
+
hostname === "[::1]";
|
|
124
|
+
if (loopback)
|
|
125
|
+
return url.protocol === "http:" && process.env.NODE_ENV !== "production";
|
|
126
|
+
if (url.protocol !== "https:") return false;
|
|
127
|
+
return (
|
|
128
|
+
hostname.endsWith(".netlify.app") ||
|
|
129
|
+
hostname.endsWith(".vercel.app") ||
|
|
130
|
+
hostname === "agent-native.com" ||
|
|
131
|
+
hostname.endsWith(".agent-native.com") ||
|
|
132
|
+
hostname.endsWith(".builder.io") ||
|
|
133
|
+
hostname.endsWith(".builderio.xyz") ||
|
|
134
|
+
hostname.endsWith(".builderio.dev") ||
|
|
135
|
+
hostname.endsWith(".builder.codes")
|
|
136
|
+
);
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function isTrustedBuilderRelayTargetOrigin(value: string): boolean {
|
|
143
|
+
if (!isSafeBuilderRelayTargetOrigin(value)) return false;
|
|
144
|
+
const hostname = new URL(value).hostname.toLowerCase();
|
|
145
|
+
if (
|
|
146
|
+
hostname.endsWith(".netlify.app") &&
|
|
147
|
+
!/^[a-f0-9]{24}--[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.netlify\.app$/.test(
|
|
148
|
+
hostname,
|
|
149
|
+
)
|
|
150
|
+
) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const configured = process.env[BUILDER_RELAY_TARGET_ORIGINS_ENV];
|
|
154
|
+
if (!configured) return false;
|
|
155
|
+
return configured
|
|
156
|
+
.split(",")
|
|
157
|
+
.map((origin) => origin.trim())
|
|
158
|
+
.filter(Boolean)
|
|
159
|
+
.some((origin) => origin === value && !origin.includes("*"));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function signBuilderPreviewRelayState(input: {
|
|
163
|
+
ownerEmail: string;
|
|
164
|
+
targetOrigin: string;
|
|
165
|
+
basePath?: string;
|
|
166
|
+
flowId?: string;
|
|
167
|
+
now?: number;
|
|
168
|
+
}): { state: string; payload: BuilderPreviewRelayState } {
|
|
169
|
+
if (!isSafeBuilderRelayTargetOrigin(input.targetOrigin)) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"Builder relay target origin is not an approved preview origin.",
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
const basePath = normalizeBuilderRelayBasePath(input.basePath ?? "");
|
|
175
|
+
if (basePath === null) throw new Error("Builder relay base path is invalid.");
|
|
176
|
+
const now = input.now ?? Date.now();
|
|
177
|
+
const payload: BuilderPreviewRelayState = {
|
|
178
|
+
v: BUILDER_RELAY_STATE_VERSION,
|
|
179
|
+
purpose: BUILDER_RELAY_PURPOSE,
|
|
180
|
+
flowId: input.flowId ?? randomBytes(24).toString("base64url"),
|
|
181
|
+
ownerEmail: input.ownerEmail,
|
|
182
|
+
targetOrigin: input.targetOrigin,
|
|
183
|
+
basePath,
|
|
184
|
+
iat: now,
|
|
185
|
+
exp: now + BUILDER_RELAY_TTL_MS,
|
|
186
|
+
};
|
|
187
|
+
const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString(
|
|
188
|
+
"base64url",
|
|
189
|
+
);
|
|
190
|
+
return { state: `${encoded}.${builderRelayMac(encoded)}`, payload };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function verifyBuilderPreviewRelayState(
|
|
194
|
+
state: string | null | undefined,
|
|
195
|
+
options: { now?: number } = {},
|
|
196
|
+
): BuilderPreviewRelayState | null {
|
|
197
|
+
if (!state) return null;
|
|
198
|
+
const parts = state.split(".");
|
|
199
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
200
|
+
if (!safeEqualText(builderRelayMac(parts[0]), parts[1])) return null;
|
|
201
|
+
let value: unknown;
|
|
202
|
+
try {
|
|
203
|
+
value = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
if (!value || typeof value !== "object") return null;
|
|
208
|
+
const payload = value as Partial<BuilderPreviewRelayState>;
|
|
209
|
+
const now = options.now ?? Date.now();
|
|
210
|
+
if (
|
|
211
|
+
payload.v !== BUILDER_RELAY_STATE_VERSION ||
|
|
212
|
+
payload.purpose !== BUILDER_RELAY_PURPOSE ||
|
|
213
|
+
typeof payload.flowId !== "string" ||
|
|
214
|
+
!/^[A-Za-z0-9_-]{24,128}$/.test(payload.flowId) ||
|
|
215
|
+
typeof payload.ownerEmail !== "string" ||
|
|
216
|
+
!payload.ownerEmail.includes("@") ||
|
|
217
|
+
typeof payload.targetOrigin !== "string" ||
|
|
218
|
+
!isSafeBuilderRelayTargetOrigin(payload.targetOrigin) ||
|
|
219
|
+
typeof payload.basePath !== "string" ||
|
|
220
|
+
normalizeBuilderRelayBasePath(payload.basePath) !== payload.basePath ||
|
|
221
|
+
typeof payload.iat !== "number" ||
|
|
222
|
+
typeof payload.exp !== "number" ||
|
|
223
|
+
payload.exp <= payload.iat ||
|
|
224
|
+
payload.exp - payload.iat > BUILDER_RELAY_TTL_MS ||
|
|
225
|
+
payload.iat > now + BUILDER_RELAY_REQUEST_SKEW_MS ||
|
|
226
|
+
payload.exp < now
|
|
227
|
+
) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
return payload as BuilderPreviewRelayState;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Corporate callback trust check. Preview-side state verification and relay
|
|
235
|
+
* receipt deliberately do not require this callback-only allowlist.
|
|
236
|
+
*/
|
|
237
|
+
export function verifyBuilderPreviewRelayStateForCallback(
|
|
238
|
+
state: string | null | undefined,
|
|
239
|
+
options: { now?: number } = {},
|
|
240
|
+
): BuilderPreviewRelayState | null {
|
|
241
|
+
const payload = verifyBuilderPreviewRelayState(state, options);
|
|
242
|
+
return payload && isTrustedBuilderRelayTargetOrigin(payload.targetOrigin)
|
|
243
|
+
? payload
|
|
244
|
+
: null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function getBuilderPreviewRelayUrl(
|
|
248
|
+
payload: BuilderPreviewRelayState,
|
|
249
|
+
): string {
|
|
250
|
+
return `${payload.targetOrigin}${payload.basePath}${BUILDER_RELAY_PATH}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function builderRelayBodyDigest(body: string): string {
|
|
254
|
+
return createHash("sha256").update(body).digest("base64url");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function builderRelayRequestSignature(
|
|
258
|
+
timestamp: number,
|
|
259
|
+
flowId: string,
|
|
260
|
+
body: string,
|
|
261
|
+
): string {
|
|
262
|
+
return builderRelayMac(
|
|
263
|
+
`v1.${timestamp}.${flowId}.${builderRelayBodyDigest(body)}`,
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function createBuilderRelayRequest(
|
|
268
|
+
relayState: string,
|
|
269
|
+
credentials: BuilderRelayCredentials,
|
|
270
|
+
options: { now?: number } = {},
|
|
271
|
+
): { body: string; headers: Record<string, string>; url: string } {
|
|
272
|
+
const payload = verifyBuilderPreviewRelayState(relayState, options);
|
|
273
|
+
if (!payload) throw new Error("Builder relay state is invalid or expired.");
|
|
274
|
+
const body = JSON.stringify({
|
|
275
|
+
relayState,
|
|
276
|
+
credentials,
|
|
277
|
+
} satisfies BuilderRelayRequestBody);
|
|
278
|
+
const timestamp = options.now ?? Date.now();
|
|
279
|
+
return {
|
|
280
|
+
body,
|
|
281
|
+
url: getBuilderPreviewRelayUrl(payload),
|
|
282
|
+
headers: {
|
|
283
|
+
"content-type": "application/json",
|
|
284
|
+
[BUILDER_RELAY_TIMESTAMP_HEADER]: String(timestamp),
|
|
285
|
+
[BUILDER_RELAY_FLOW_HEADER]: payload.flowId,
|
|
286
|
+
[BUILDER_RELAY_SIGNATURE_HEADER]: builderRelayRequestSignature(
|
|
287
|
+
timestamp,
|
|
288
|
+
payload.flowId,
|
|
289
|
+
body,
|
|
290
|
+
),
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function verifyBuilderRelayRequest(input: {
|
|
296
|
+
body: string;
|
|
297
|
+
timestamp: string | null | undefined;
|
|
298
|
+
flowId: string | null | undefined;
|
|
299
|
+
signature: string | null | undefined;
|
|
300
|
+
requestOrigin: string;
|
|
301
|
+
requestBasePath: string;
|
|
302
|
+
now?: number;
|
|
303
|
+
}): {
|
|
304
|
+
payload: BuilderPreviewRelayState;
|
|
305
|
+
body: BuilderRelayRequestBody;
|
|
306
|
+
} | null {
|
|
307
|
+
const timestamp = Number(input.timestamp);
|
|
308
|
+
const now = input.now ?? Date.now();
|
|
309
|
+
if (
|
|
310
|
+
!Number.isFinite(timestamp) ||
|
|
311
|
+
Math.abs(now - timestamp) > BUILDER_RELAY_REQUEST_SKEW_MS ||
|
|
312
|
+
!input.flowId ||
|
|
313
|
+
!input.signature ||
|
|
314
|
+
!safeEqualText(
|
|
315
|
+
builderRelayRequestSignature(timestamp, input.flowId, input.body),
|
|
316
|
+
input.signature,
|
|
317
|
+
)
|
|
318
|
+
) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
let body: BuilderRelayRequestBody;
|
|
322
|
+
try {
|
|
323
|
+
body = JSON.parse(input.body) as BuilderRelayRequestBody;
|
|
324
|
+
} catch {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
const payload = verifyBuilderPreviewRelayState(body.relayState, { now });
|
|
328
|
+
if (
|
|
329
|
+
!payload ||
|
|
330
|
+
payload.flowId !== input.flowId ||
|
|
331
|
+
payload.targetOrigin !== input.requestOrigin ||
|
|
332
|
+
payload.basePath !== input.requestBasePath ||
|
|
333
|
+
!body.credentials ||
|
|
334
|
+
typeof body.credentials.privateKey !== "string" ||
|
|
335
|
+
typeof body.credentials.publicKey !== "string" ||
|
|
336
|
+
!body.credentials.privateKey ||
|
|
337
|
+
!body.credentials.publicKey
|
|
338
|
+
) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
const nullableString = (value: unknown): string | null =>
|
|
342
|
+
typeof value === "string" ? value : null;
|
|
343
|
+
const nullableBoolean = (value: unknown): boolean | null =>
|
|
344
|
+
typeof value === "boolean" ? value : null;
|
|
345
|
+
return {
|
|
346
|
+
payload,
|
|
347
|
+
body: {
|
|
348
|
+
relayState: body.relayState,
|
|
349
|
+
// Explicitly rebuild the credential payload. Extra fields such as an
|
|
350
|
+
// attacker-supplied ownerEmail/orgId never reach the credential writer.
|
|
351
|
+
credentials: {
|
|
352
|
+
privateKey: body.credentials.privateKey,
|
|
353
|
+
publicKey: body.credentials.publicKey,
|
|
354
|
+
userId: nullableString(body.credentials.userId),
|
|
355
|
+
orgName: nullableString(body.credentials.orgName),
|
|
356
|
+
orgKind: nullableString(body.credentials.orgKind),
|
|
357
|
+
subscription: nullableString(body.credentials.subscription),
|
|
358
|
+
subscriptionLevel: nullableString(body.credentials.subscriptionLevel),
|
|
359
|
+
subscriptionName: nullableString(body.credentials.subscriptionName),
|
|
360
|
+
isEnterprise: nullableBoolean(body.credentials.isEnterprise),
|
|
361
|
+
isFreeAccount: nullableBoolean(body.credentials.isFreeAccount),
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
};
|
|
365
|
+
}
|
|
18
366
|
|
|
19
367
|
function escapeHtml(value: string): string {
|
|
20
368
|
return value.replace(/[&<>"']/g, (ch) => {
|
|
@@ -516,6 +864,7 @@ export function buildBuilderCliAuthUrl(
|
|
|
516
864
|
state: string | null = null,
|
|
517
865
|
options: {
|
|
518
866
|
previewOrigin?: string;
|
|
867
|
+
relayState?: string;
|
|
519
868
|
tracking?: BuilderConnectTrackingParams;
|
|
520
869
|
} = {},
|
|
521
870
|
): string {
|
|
@@ -536,6 +885,9 @@ export function buildBuilderCliAuthUrl(
|
|
|
536
885
|
if (state) {
|
|
537
886
|
callbackUrl.searchParams.set(BUILDER_STATE_PARAM, state);
|
|
538
887
|
}
|
|
888
|
+
if (options.relayState) {
|
|
889
|
+
callbackUrl.searchParams.set(BUILDER_RELAY_STATE_PARAM, options.relayState);
|
|
890
|
+
}
|
|
539
891
|
// When the cli-auth allow-list forces preview_url onto the gateway origin,
|
|
540
892
|
// the callback would otherwise lose the real opener origin and post its
|
|
541
893
|
// success message to the gateway instead of the preview tab. Embed the
|