@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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
BuilderCmsModelFieldSummary,
|
|
2
3
|
BuilderCmsPublicationTransitionIntent,
|
|
3
4
|
BuilderCmsWriteEffect,
|
|
4
5
|
ContentDatabaseSource,
|
|
@@ -8,7 +9,10 @@ import type {
|
|
|
8
9
|
ContentDatabaseSourceWriteMode,
|
|
9
10
|
} from "../shared/api.js";
|
|
10
11
|
import { BUILDER_CMS_SAFE_WRITE_MODEL as SAFE_WRITE_MODEL } from "../shared/api.js";
|
|
11
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
BUILDER_CMS_BODY_BLOCKS_HASH_KEY,
|
|
14
|
+
builderCmsSourceRowIdentityState,
|
|
15
|
+
} from "./_builder-cms-source-adapter.js";
|
|
12
16
|
import { builderCmsPushModeForTier } from "./_builder-cms-write-settings.js";
|
|
13
17
|
|
|
14
18
|
export type { BuilderCmsWriteEffect };
|
|
@@ -72,6 +76,243 @@ export function builderCmsExecutionIdempotencyKey(args: {
|
|
|
72
76
|
return `builder-cms:${args.sourceId}:${args.changeSetId}:${args.pushMode}`;
|
|
73
77
|
}
|
|
74
78
|
|
|
79
|
+
export const BUILDER_CMS_EXECUTION_MARKER_FIELD = "agentNativeTestNote";
|
|
80
|
+
|
|
81
|
+
function builderFieldName(sourceFieldKey: string) {
|
|
82
|
+
return sourceFieldKey.replace(/^data\./, "").trim();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function builderModelFieldForOperation(args: {
|
|
86
|
+
source: ContentDatabaseSource;
|
|
87
|
+
sourceFieldKey: string;
|
|
88
|
+
}) {
|
|
89
|
+
const name = builderFieldName(args.sourceFieldKey);
|
|
90
|
+
return (
|
|
91
|
+
args.source.metadata.builderModelFields?.find(
|
|
92
|
+
(field) => field.name.trim() === name,
|
|
93
|
+
) ?? null
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function normalizedBuilderFieldKind(field: BuilderCmsModelFieldSummary | null) {
|
|
98
|
+
return [field?.type, field?.inputType]
|
|
99
|
+
.filter((value): value is string => typeof value === "string")
|
|
100
|
+
.join(" ")
|
|
101
|
+
.trim()
|
|
102
|
+
.toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function optionIdFromLabel(label: string) {
|
|
106
|
+
return (
|
|
107
|
+
label
|
|
108
|
+
.trim()
|
|
109
|
+
.toLowerCase()
|
|
110
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
111
|
+
.replace(/^-+|-+$/g, "") || "option"
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function builderOptionLabels(field: BuilderCmsModelFieldSummary | null) {
|
|
116
|
+
const labels = [...(field?.options ?? []), ...(field?.enum ?? [])];
|
|
117
|
+
const seenLabels = new Set<string>();
|
|
118
|
+
const usedIds = new Set<string>();
|
|
119
|
+
const result: Array<{ id: string; label: string }> = [];
|
|
120
|
+
for (const rawLabel of labels) {
|
|
121
|
+
const label = rawLabel.trim();
|
|
122
|
+
const labelKey = label.toLowerCase();
|
|
123
|
+
if (!label || seenLabels.has(labelKey)) continue;
|
|
124
|
+
seenLabels.add(labelKey);
|
|
125
|
+
const baseId = optionIdFromLabel(label);
|
|
126
|
+
let id = baseId;
|
|
127
|
+
let suffix = 2;
|
|
128
|
+
while (usedIds.has(id)) id = `${baseId}-${suffix++}`;
|
|
129
|
+
usedIds.add(id);
|
|
130
|
+
result.push({ id, label });
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function builderNativeFieldValue(args: {
|
|
136
|
+
source: ContentDatabaseSource;
|
|
137
|
+
sourceFieldKey: string;
|
|
138
|
+
value: unknown;
|
|
139
|
+
}): { value?: unknown; blocker?: string } {
|
|
140
|
+
if (args.source.sourceTable !== SAFE_WRITE_MODEL) {
|
|
141
|
+
return { value: args.value };
|
|
142
|
+
}
|
|
143
|
+
const field = builderModelFieldForOperation(args);
|
|
144
|
+
const fieldLabel = field?.label?.trim() || field?.name || args.sourceFieldKey;
|
|
145
|
+
const kind = normalizedBuilderFieldKind(field);
|
|
146
|
+
|
|
147
|
+
if (/\b(date|datetime)\b/.test(kind)) {
|
|
148
|
+
const raw =
|
|
149
|
+
args.value && typeof args.value === "object" && !Array.isArray(args.value)
|
|
150
|
+
? (args.value as { start?: unknown }).start
|
|
151
|
+
: args.value;
|
|
152
|
+
if (typeof raw !== "string" && typeof raw !== "number") {
|
|
153
|
+
return { blocker: `${fieldLabel} must be a valid Builder date.` };
|
|
154
|
+
}
|
|
155
|
+
const timestamp =
|
|
156
|
+
typeof raw === "number" ? raw : new Date(raw.trim()).getTime();
|
|
157
|
+
if (!Number.isFinite(timestamp)) {
|
|
158
|
+
return { blocker: `${fieldLabel} must be a valid Builder date.` };
|
|
159
|
+
}
|
|
160
|
+
return { value: timestamp };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (/\b(file|image)\b/.test(kind)) {
|
|
164
|
+
const candidates = Array.isArray(args.value) ? args.value : [args.value];
|
|
165
|
+
if (candidates.length !== 1 || typeof candidates[0] !== "string") {
|
|
166
|
+
return { blocker: `${fieldLabel} must contain exactly one file URL.` };
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const url = new URL(candidates[0]);
|
|
170
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") throw 0;
|
|
171
|
+
return { value: url.toString() };
|
|
172
|
+
} catch {
|
|
173
|
+
return { blocker: `${fieldLabel} must contain exactly one file URL.` };
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (/\b(reference|relation)\b/.test(kind)) {
|
|
178
|
+
if (
|
|
179
|
+
args.value &&
|
|
180
|
+
typeof args.value === "object" &&
|
|
181
|
+
!Array.isArray(args.value)
|
|
182
|
+
) {
|
|
183
|
+
const reference = args.value as Record<string, unknown>;
|
|
184
|
+
if (
|
|
185
|
+
reference["@type"] === "@builder.io/core:Reference" &&
|
|
186
|
+
typeof reference.id === "string" &&
|
|
187
|
+
reference.id.trim() &&
|
|
188
|
+
typeof reference.model === "string" &&
|
|
189
|
+
reference.model.trim()
|
|
190
|
+
) {
|
|
191
|
+
return {
|
|
192
|
+
value: {
|
|
193
|
+
"@type": "@builder.io/core:Reference",
|
|
194
|
+
id: reference.id.trim(),
|
|
195
|
+
model: reference.model.trim(),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const ids = Array.isArray(args.value) ? args.value : [args.value];
|
|
201
|
+
const id = ids.length === 1 ? ids[0] : null;
|
|
202
|
+
if (typeof id !== "string" || !id.trim() || !field?.model?.trim()) {
|
|
203
|
+
return {
|
|
204
|
+
blocker: `${fieldLabel} must contain one Builder reference with a model.`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
value: {
|
|
209
|
+
"@type": "@builder.io/core:Reference",
|
|
210
|
+
id: id.trim(),
|
|
211
|
+
model: field.model.trim(),
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const choices = builderOptionLabels(field);
|
|
217
|
+
const isList = /\b(list|array|tags?|multi[-_\s]?select)\b/.test(kind);
|
|
218
|
+
if (isList || choices.length > 0) {
|
|
219
|
+
const values = Array.isArray(args.value) ? args.value : [args.value];
|
|
220
|
+
const labels = values.map((value) => {
|
|
221
|
+
if (typeof value !== "string") return null;
|
|
222
|
+
const trimmed = value.trim();
|
|
223
|
+
// Builder Tags/list fields can be free-form: unlike enums and optioned
|
|
224
|
+
// selects, their model metadata has no finite label inventory. Preserve
|
|
225
|
+
// strict mapping whenever choices exist, but pass non-empty labels
|
|
226
|
+
// through when Builder explicitly exposes no choices to map against.
|
|
227
|
+
if (isList && choices.length === 0) return trimmed || null;
|
|
228
|
+
return (
|
|
229
|
+
choices.find(
|
|
230
|
+
(choice) =>
|
|
231
|
+
choice.label.toLowerCase() === trimmed.toLowerCase() ||
|
|
232
|
+
choice.id === trimmed,
|
|
233
|
+
)?.label ?? null
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
if (labels.some((label) => label === null)) {
|
|
237
|
+
return {
|
|
238
|
+
blocker: `${fieldLabel} contains an option that cannot be mapped to a Builder label.`,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return { value: isList ? labels : labels[0] };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { value: args.value };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function hasBuilderRequiredValue(
|
|
248
|
+
value: unknown,
|
|
249
|
+
field: BuilderCmsModelFieldSummary,
|
|
250
|
+
) {
|
|
251
|
+
if (value === null || value === undefined || value === "") return false;
|
|
252
|
+
if (Array.isArray(value) && value.length === 0) return false;
|
|
253
|
+
const kind = normalizedBuilderFieldKind(field);
|
|
254
|
+
if (/\b(date|datetime)\b/.test(kind)) {
|
|
255
|
+
return (
|
|
256
|
+
(typeof value === "number" && Number.isFinite(value)) ||
|
|
257
|
+
(typeof value === "string" && Number.isFinite(new Date(value).getTime()))
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (/\b(file|image)\b/.test(kind)) {
|
|
261
|
+
if (typeof value !== "string") return false;
|
|
262
|
+
try {
|
|
263
|
+
const url = new URL(value);
|
|
264
|
+
return url.protocol === "https:" || url.protocol === "http:";
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (/\b(reference|relation)\b/.test(kind)) {
|
|
270
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
271
|
+
return false;
|
|
272
|
+
const reference = value as Record<string, unknown>;
|
|
273
|
+
return (
|
|
274
|
+
reference["@type"] === "@builder.io/core:Reference" &&
|
|
275
|
+
typeof reference.id === "string" &&
|
|
276
|
+
reference.id.trim().length > 0 &&
|
|
277
|
+
typeof reference.model === "string" &&
|
|
278
|
+
reference.model.trim().length > 0
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (/\b(list|array|tags?|multi[-_\s]?select)\b/.test(kind)) {
|
|
282
|
+
return Array.isArray(value) && value.length > 0;
|
|
283
|
+
}
|
|
284
|
+
if (/\b(number)\b/.test(kind)) {
|
|
285
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
286
|
+
}
|
|
287
|
+
if (/\b(boolean|checkbox)\b/.test(kind)) return typeof value === "boolean";
|
|
288
|
+
if (/\b(string|text|url|select)\b/.test(kind)) {
|
|
289
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
290
|
+
}
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function portableIntentHash(input: string) {
|
|
295
|
+
const hashPart = (seed: number) => {
|
|
296
|
+
let hash = (0x811c9dc5 ^ seed) >>> 0;
|
|
297
|
+
for (let index = 0; index < input.length; index += 1) {
|
|
298
|
+
hash = Math.imul(hash ^ input.charCodeAt(index), 0x01000193) >>> 0;
|
|
299
|
+
}
|
|
300
|
+
hash ^= hash >>> 16;
|
|
301
|
+
hash = Math.imul(hash, 0x85ebca6b) >>> 0;
|
|
302
|
+
hash ^= hash >>> 13;
|
|
303
|
+
hash = Math.imul(hash, 0xc2b2ae35) >>> 0;
|
|
304
|
+
hash ^= hash >>> 16;
|
|
305
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
return [0, 0x9e3779b9, 0x7f4a7c15].map(hashPart).join("");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** A compact, non-secret marker for recovering an ambiguous safe-model POST. */
|
|
312
|
+
export function builderCmsExecutionIntentMarker(idempotencyKey: string) {
|
|
313
|
+
return `agent-native-execution:${portableIntentHash(idempotencyKey)}`;
|
|
314
|
+
}
|
|
315
|
+
|
|
75
316
|
function builderEffectForWrite(args: {
|
|
76
317
|
pushMode: ContentDatabaseSourcePushMode;
|
|
77
318
|
writeMode?: ContentDatabaseSourceWriteMode | null;
|
|
@@ -219,6 +460,42 @@ function mergeBuilderPatch(
|
|
|
219
460
|
return merged;
|
|
220
461
|
}
|
|
221
462
|
|
|
463
|
+
function requiredBuilderReferencePatch(args: {
|
|
464
|
+
source: ContentDatabaseSource;
|
|
465
|
+
targetRow: ContentDatabaseSource["rows"][number] | null;
|
|
466
|
+
effect: BuilderCmsWriteEffect;
|
|
467
|
+
}) {
|
|
468
|
+
if (
|
|
469
|
+
args.source.sourceTable !== SAFE_WRITE_MODEL ||
|
|
470
|
+
args.effect !== "publish" ||
|
|
471
|
+
!args.targetRow
|
|
472
|
+
) {
|
|
473
|
+
return {};
|
|
474
|
+
}
|
|
475
|
+
const data: Record<string, unknown> = {};
|
|
476
|
+
for (const field of args.source.metadata.builderModelFields ?? []) {
|
|
477
|
+
if (
|
|
478
|
+
!field.required ||
|
|
479
|
+
!/\b(reference|relation)\b/.test(normalizedBuilderFieldKind(field)) ||
|
|
480
|
+
!field.model?.trim()
|
|
481
|
+
) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
const sourceFieldKey = `data.${field.name.trim()}`;
|
|
485
|
+
const id =
|
|
486
|
+
args.targetRow.sourceValues?.[
|
|
487
|
+
`__agent_native_builder_reference_id:${sourceFieldKey}`
|
|
488
|
+
];
|
|
489
|
+
if (typeof id !== "string" || !id.trim()) continue;
|
|
490
|
+
data[field.name] = {
|
|
491
|
+
"@type": "@builder.io/core:Reference",
|
|
492
|
+
id: id.trim(),
|
|
493
|
+
model: field.model.trim(),
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
return Object.keys(data).length > 0 ? { data } : {};
|
|
497
|
+
}
|
|
498
|
+
|
|
222
499
|
function builderBodyPatch(changeSet: ContentDatabaseSourceChangeSet) {
|
|
223
500
|
const bodyChange = changeSet.bodyChange;
|
|
224
501
|
const checks: string[] = [];
|
|
@@ -263,9 +540,22 @@ function builderRequestForEffect(args: {
|
|
|
263
540
|
model: string;
|
|
264
541
|
entryId: string | null;
|
|
265
542
|
bodyPatch: Record<string, unknown>;
|
|
543
|
+
currentTitle?: string | null;
|
|
544
|
+
intentMarker?: string;
|
|
266
545
|
}): BuilderCmsExecutionPayload["request"] {
|
|
267
546
|
const entryPath = args.entryId ? `/${encodeURIComponent(args.entryId)}` : "";
|
|
268
547
|
const basePath = `/api/v1/write/${encodeURIComponent(args.model)}${entryPath}`;
|
|
548
|
+
const patchedTitle =
|
|
549
|
+
args.bodyPatch.data &&
|
|
550
|
+
typeof args.bodyPatch.data === "object" &&
|
|
551
|
+
!Array.isArray(args.bodyPatch.data) &&
|
|
552
|
+
typeof (args.bodyPatch.data as Record<string, unknown>).title === "string"
|
|
553
|
+
? (args.bodyPatch.data as Record<string, unknown>).title
|
|
554
|
+
: null;
|
|
555
|
+
const safeEntryName =
|
|
556
|
+
args.model === SAFE_WRITE_MODEL
|
|
557
|
+
? patchedTitle || args.currentTitle?.trim() || null
|
|
558
|
+
: null;
|
|
269
559
|
if (args.effect === "autosave") {
|
|
270
560
|
return {
|
|
271
561
|
method: "PATCH",
|
|
@@ -274,7 +564,10 @@ function builderRequestForEffect(args: {
|
|
|
274
564
|
autoSaveOnly: "true",
|
|
275
565
|
triggerWebhooks: "false",
|
|
276
566
|
},
|
|
277
|
-
body:
|
|
567
|
+
body: {
|
|
568
|
+
...args.bodyPatch,
|
|
569
|
+
...(safeEntryName ? { name: safeEntryName } : {}),
|
|
570
|
+
},
|
|
278
571
|
};
|
|
279
572
|
}
|
|
280
573
|
if (args.effect === "update_in_place") {
|
|
@@ -284,7 +577,10 @@ function builderRequestForEffect(args: {
|
|
|
284
577
|
query: {
|
|
285
578
|
triggerWebhooks: "true",
|
|
286
579
|
},
|
|
287
|
-
body:
|
|
580
|
+
body: {
|
|
581
|
+
...args.bodyPatch,
|
|
582
|
+
...(safeEntryName ? { name: safeEntryName } : {}),
|
|
583
|
+
},
|
|
288
584
|
};
|
|
289
585
|
}
|
|
290
586
|
if (args.effect === "publish") {
|
|
@@ -321,11 +617,70 @@ function builderRequestForEffect(args: {
|
|
|
321
617
|
},
|
|
322
618
|
body: {
|
|
323
619
|
...args.bodyPatch,
|
|
620
|
+
...(safeEntryName ? { name: safeEntryName } : {}),
|
|
621
|
+
data: {
|
|
622
|
+
...((args.bodyPatch.data && typeof args.bodyPatch.data === "object"
|
|
623
|
+
? args.bodyPatch.data
|
|
624
|
+
: {}) as Record<string, unknown>),
|
|
625
|
+
...(args.intentMarker
|
|
626
|
+
? { [BUILDER_CMS_EXECUTION_MARKER_FIELD]: args.intentMarker }
|
|
627
|
+
: {}),
|
|
628
|
+
},
|
|
324
629
|
published: "draft",
|
|
325
630
|
},
|
|
326
631
|
};
|
|
327
632
|
}
|
|
328
633
|
|
|
634
|
+
function builderRequiredFieldBlockers(args: {
|
|
635
|
+
source: ContentDatabaseSource;
|
|
636
|
+
effect: BuilderCmsWriteEffect;
|
|
637
|
+
targetRow: ContentDatabaseSource["rows"][number] | null;
|
|
638
|
+
request: BuilderCmsExecutionPayload["request"];
|
|
639
|
+
}) {
|
|
640
|
+
if (
|
|
641
|
+
args.source.sourceTable !== SAFE_WRITE_MODEL ||
|
|
642
|
+
(args.effect !== "create_draft" && args.effect !== "publish")
|
|
643
|
+
) {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
const modelFields = args.source.metadata.builderModelFields;
|
|
647
|
+
// Legacy persisted execution gates predate model-schema snapshots. Preserve
|
|
648
|
+
// their exact behavior; newly prepared safe-model gates carry the schema and
|
|
649
|
+
// are validated below before they can become ready.
|
|
650
|
+
if (!modelFields?.length) return [];
|
|
651
|
+
const existingData = Object.fromEntries(
|
|
652
|
+
Object.entries(args.targetRow?.sourceValues ?? {}).flatMap(([key, value]) =>
|
|
653
|
+
key.startsWith("data.") ? [[key.slice("data.".length), value]] : [],
|
|
654
|
+
),
|
|
655
|
+
);
|
|
656
|
+
const requestData =
|
|
657
|
+
args.request.body.data &&
|
|
658
|
+
typeof args.request.body.data === "object" &&
|
|
659
|
+
!Array.isArray(args.request.body.data)
|
|
660
|
+
? (args.request.body.data as Record<string, unknown>)
|
|
661
|
+
: {};
|
|
662
|
+
const effectiveData = { ...existingData, ...requestData };
|
|
663
|
+
if (
|
|
664
|
+
effectiveData.blocks === undefined &&
|
|
665
|
+
typeof args.targetRow?.sourceValues?.[BUILDER_CMS_BODY_BLOCKS_HASH_KEY] ===
|
|
666
|
+
"string" &&
|
|
667
|
+
args.targetRow.sourceValues[BUILDER_CMS_BODY_BLOCKS_HASH_KEY].trim()
|
|
668
|
+
) {
|
|
669
|
+
// Body blocks are intentionally stored outside sourceValues. A reconciled
|
|
670
|
+
// body hash proves that the hydrated Builder body exists without copying a
|
|
671
|
+
// large blocks payload into SQL merely to satisfy this gate.
|
|
672
|
+
effectiveData.blocks = ["reconciled-builder-body"];
|
|
673
|
+
}
|
|
674
|
+
return modelFields.flatMap((field) => {
|
|
675
|
+
if (!field.required) return [];
|
|
676
|
+
const value = effectiveData[field.name];
|
|
677
|
+
if (hasBuilderRequiredValue(value, field)) return [];
|
|
678
|
+
return [
|
|
679
|
+
`Required Builder field ${field.label?.trim() || field.name} is missing or invalid.`,
|
|
680
|
+
];
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
329
684
|
function builderSafetyChecks(args: {
|
|
330
685
|
source: ContentDatabaseSource;
|
|
331
686
|
changeSet: ContentDatabaseSourceChangeSet;
|
|
@@ -339,13 +694,25 @@ function builderSafetyChecks(args: {
|
|
|
339
694
|
hasBodyOperation: boolean;
|
|
340
695
|
bodyChecks: string[];
|
|
341
696
|
bodyBlockers: string[];
|
|
697
|
+
fieldBlockers: string[];
|
|
698
|
+
requiredFieldBlockers: string[];
|
|
342
699
|
}) {
|
|
343
700
|
const checks = [
|
|
344
701
|
"Requires explicit approval before execution.",
|
|
345
702
|
"Uses the stored execution idempotency key.",
|
|
346
703
|
...args.bodyChecks,
|
|
347
704
|
];
|
|
348
|
-
const blockers: string[] = [
|
|
705
|
+
const blockers: string[] = [
|
|
706
|
+
...args.bodyBlockers,
|
|
707
|
+
...args.fieldBlockers,
|
|
708
|
+
...args.requiredFieldBlockers,
|
|
709
|
+
];
|
|
710
|
+
|
|
711
|
+
if (args.source.sourceTable !== SAFE_WRITE_MODEL) {
|
|
712
|
+
blockers.push(
|
|
713
|
+
`Live Builder writes are only allowed for ${SAFE_WRITE_MODEL}.`,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
349
716
|
|
|
350
717
|
if (args.operations.length === 0 && !args.hasBodyOperation) {
|
|
351
718
|
blockers.push("No Builder operations are available for this change.");
|
|
@@ -396,14 +763,6 @@ function builderSafetyChecks(args: {
|
|
|
396
763
|
if (allowedModes?.length && !allowedModes.includes(args.pushMode)) {
|
|
397
764
|
blockers.push(`Push mode ${args.pushMode} is not allowed for this source.`);
|
|
398
765
|
}
|
|
399
|
-
if (
|
|
400
|
-
args.source.capabilities.liveWritesEnabled === true &&
|
|
401
|
-
args.source.sourceTable !== SAFE_WRITE_MODEL
|
|
402
|
-
) {
|
|
403
|
-
blockers.push(
|
|
404
|
-
`Live Builder writes are only allowed for ${SAFE_WRITE_MODEL}.`,
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
766
|
if (args.source.capabilities.liveWritesEnabled !== true) {
|
|
408
767
|
checks.push("Does not run while live Builder writes are disabled.");
|
|
409
768
|
if (
|
|
@@ -465,6 +824,7 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
465
824
|
}
|
|
466
825
|
|
|
467
826
|
const {
|
|
827
|
+
targetRow,
|
|
468
828
|
target,
|
|
469
829
|
entryId: targetEntryId,
|
|
470
830
|
sourceQualifiedId: targetSourceQualifiedId,
|
|
@@ -478,14 +838,44 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
478
838
|
entryId: targetEntryId,
|
|
479
839
|
publicationTransition: args.publicationTransition,
|
|
480
840
|
});
|
|
481
|
-
const operations =
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
841
|
+
const operations: BuilderCmsExecutionOperation[] = [];
|
|
842
|
+
const fieldBlockers: string[] = [];
|
|
843
|
+
for (const field of args.changeSet.fieldChanges) {
|
|
844
|
+
let candidateValue: unknown = field.proposedValue;
|
|
845
|
+
if (
|
|
846
|
+
args.source.sourceTable === SAFE_WRITE_MODEL &&
|
|
847
|
+
field.builderValueJson !== undefined
|
|
848
|
+
) {
|
|
849
|
+
try {
|
|
850
|
+
candidateValue = JSON.parse(field.builderValueJson) as unknown;
|
|
851
|
+
} catch {
|
|
852
|
+
fieldBlockers.push(
|
|
853
|
+
`${field.propertyName ?? field.sourceFieldKey} has invalid Builder provider JSON.`,
|
|
854
|
+
);
|
|
855
|
+
continue;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
const converted = builderNativeFieldValue({
|
|
859
|
+
source: args.source,
|
|
860
|
+
sourceFieldKey: field.sourceFieldKey,
|
|
861
|
+
value: candidateValue,
|
|
862
|
+
});
|
|
863
|
+
if (converted.blocker) {
|
|
864
|
+
fieldBlockers.push(converted.blocker);
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
operations.push({
|
|
868
|
+
sourceFieldKey: field.sourceFieldKey,
|
|
869
|
+
localFieldKey: field.localFieldKey,
|
|
870
|
+
value: converted.value,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
486
873
|
const bodyDiffPatch = builderBodyPatch(args.changeSet);
|
|
487
874
|
const bodyPatch = mergeBuilderPatch(
|
|
488
|
-
|
|
875
|
+
mergeBuilderPatch(
|
|
876
|
+
requiredBuilderReferencePatch({ source: args.source, targetRow, effect }),
|
|
877
|
+
nestedBuilderPatch(operations),
|
|
878
|
+
),
|
|
489
879
|
bodyDiffPatch.patch,
|
|
490
880
|
);
|
|
491
881
|
// State-preserving effects must not include `published` in the body. Builder
|
|
@@ -496,6 +886,23 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
496
886
|
model: args.source.sourceTable,
|
|
497
887
|
entryId: targetEntryId,
|
|
498
888
|
bodyPatch,
|
|
889
|
+
currentTitle: targetRow?.sourceDisplayKey ?? null,
|
|
890
|
+
intentMarker:
|
|
891
|
+
effect === "create_draft" && args.source.sourceTable === SAFE_WRITE_MODEL
|
|
892
|
+
? builderCmsExecutionIntentMarker(
|
|
893
|
+
builderCmsExecutionIdempotencyKey({
|
|
894
|
+
sourceId: args.source.id,
|
|
895
|
+
changeSetId: args.changeSet.id,
|
|
896
|
+
pushMode,
|
|
897
|
+
}),
|
|
898
|
+
)
|
|
899
|
+
: undefined,
|
|
900
|
+
});
|
|
901
|
+
const requiredFieldBlockers = builderRequiredFieldBlockers({
|
|
902
|
+
source: args.source,
|
|
903
|
+
effect,
|
|
904
|
+
targetRow,
|
|
905
|
+
request,
|
|
499
906
|
});
|
|
500
907
|
const safety = builderSafetyChecks({
|
|
501
908
|
source: args.source,
|
|
@@ -513,6 +920,8 @@ export function buildBuilderCmsExecutionPlan(args: {
|
|
|
513
920
|
hasBodyOperation: bodyDiffPatch.hasBodyOperation,
|
|
514
921
|
bodyChecks: bodyDiffPatch.checks,
|
|
515
922
|
bodyBlockers: bodyDiffPatch.blockers,
|
|
923
|
+
fieldBlockers,
|
|
924
|
+
requiredFieldBlockers,
|
|
516
925
|
});
|
|
517
926
|
const state: ContentDatabaseSourceExecutionState =
|
|
518
927
|
safety.blockers.length > 0
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import { request as httpRequest } from "node:http";
|
|
2
|
-
import type { ClientRequest, IncomingMessage, RequestOptions } from "node:http";
|
|
3
|
-
import { request as httpsRequest } from "node:https";
|
|
4
|
-
|
|
5
1
|
import { resolveBuilderCredential } from "@agent-native/core/server";
|
|
6
2
|
|
|
7
3
|
export interface BuilderCmsWriteRequest {
|
|
@@ -17,14 +13,10 @@ export interface BuilderCmsWriteResult {
|
|
|
17
13
|
entryId?: string;
|
|
18
14
|
responseBody: unknown;
|
|
19
15
|
error?: string;
|
|
16
|
+
ambiguity?: "timeout" | "transport";
|
|
20
17
|
}
|
|
21
18
|
|
|
22
19
|
type FetchLike = typeof fetch;
|
|
23
|
-
type NodeRequestLike = (
|
|
24
|
-
url: URL,
|
|
25
|
-
options: RequestOptions,
|
|
26
|
-
callback: (response: IncomingMessage) => void,
|
|
27
|
-
) => ClientRequest;
|
|
28
20
|
|
|
29
21
|
function builderWriteApiHost() {
|
|
30
22
|
return (
|
|
@@ -97,52 +89,12 @@ function buildWriteResult(args: {
|
|
|
97
89
|
};
|
|
98
90
|
}
|
|
99
91
|
|
|
100
|
-
async function executeNodeRequest(args: {
|
|
101
|
-
url: URL;
|
|
102
|
-
method: "POST" | "PATCH";
|
|
103
|
-
headers: Record<string, string>;
|
|
104
|
-
body: string;
|
|
105
|
-
requestImpl?: NodeRequestLike;
|
|
106
|
-
}): Promise<BuilderCmsWriteResult> {
|
|
107
|
-
const requestImpl =
|
|
108
|
-
args.requestImpl ??
|
|
109
|
-
(args.url.protocol === "http:" ? httpRequest : httpsRequest);
|
|
110
|
-
|
|
111
|
-
return await new Promise((resolve, reject) => {
|
|
112
|
-
const request = requestImpl(
|
|
113
|
-
args.url,
|
|
114
|
-
{
|
|
115
|
-
method: args.method,
|
|
116
|
-
headers: args.headers,
|
|
117
|
-
},
|
|
118
|
-
(response) => {
|
|
119
|
-
const chunks: Buffer[] = [];
|
|
120
|
-
response.on("data", (chunk: Buffer | string) => {
|
|
121
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
122
|
-
});
|
|
123
|
-
response.on("end", () => {
|
|
124
|
-
const status = response.statusCode ?? 0;
|
|
125
|
-
resolve(
|
|
126
|
-
buildWriteResult({
|
|
127
|
-
ok: status >= 200 && status < 300,
|
|
128
|
-
status,
|
|
129
|
-
responseText: Buffer.concat(chunks).toString("utf8"),
|
|
130
|
-
}),
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
response.on("error", reject);
|
|
134
|
-
},
|
|
135
|
-
);
|
|
136
|
-
request.on("error", reject);
|
|
137
|
-
request.write(args.body);
|
|
138
|
-
request.end();
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
92
|
export async function executeBuilderCmsWrite(args: {
|
|
143
93
|
request: BuilderCmsWriteRequest;
|
|
144
94
|
fetchImpl?: FetchLike;
|
|
145
|
-
|
|
95
|
+
/** @deprecated Never used: retrying another transport after dispatch is unsafe. */
|
|
96
|
+
nodeRequestImpl?: unknown;
|
|
97
|
+
timeoutMs?: number;
|
|
146
98
|
}): Promise<BuilderCmsWriteResult> {
|
|
147
99
|
const privateKey = await readBuilderPrivateKey();
|
|
148
100
|
if (!privateKey) {
|
|
@@ -166,11 +118,15 @@ export async function executeBuilderCmsWrite(args: {
|
|
|
166
118
|
"content-type": "application/json",
|
|
167
119
|
};
|
|
168
120
|
|
|
121
|
+
const controller = new AbortController();
|
|
122
|
+
const timeoutMs = Math.max(1, args.timeoutMs ?? 15_000);
|
|
123
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
169
124
|
try {
|
|
170
125
|
const response = await (args.fetchImpl ?? fetch)(url, {
|
|
171
126
|
method: args.request.method,
|
|
172
127
|
headers,
|
|
173
128
|
body,
|
|
129
|
+
signal: controller.signal,
|
|
174
130
|
});
|
|
175
131
|
return buildWriteResult({
|
|
176
132
|
ok: response.ok,
|
|
@@ -178,31 +134,19 @@ export async function executeBuilderCmsWrite(args: {
|
|
|
178
134
|
responseText: await response.text(),
|
|
179
135
|
});
|
|
180
136
|
} catch (error) {
|
|
181
|
-
|
|
182
|
-
args.request.method === "PATCH" &&
|
|
183
|
-
(args.nodeRequestImpl || !args.fetchImpl)
|
|
184
|
-
) {
|
|
185
|
-
try {
|
|
186
|
-
return await executeNodeRequest({
|
|
187
|
-
url,
|
|
188
|
-
method: args.request.method,
|
|
189
|
-
headers,
|
|
190
|
-
body,
|
|
191
|
-
requestImpl: args.nodeRequestImpl,
|
|
192
|
-
});
|
|
193
|
-
} catch {
|
|
194
|
-
// Return the original fetch failure below; it is usually more specific.
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
137
|
+
const timedOut = controller.signal.aborted;
|
|
198
138
|
return {
|
|
199
139
|
ok: false,
|
|
200
140
|
status: 0,
|
|
201
141
|
responseBody: null,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
142
|
+
ambiguity: timedOut ? "timeout" : "transport",
|
|
143
|
+
error: timedOut
|
|
144
|
+
? `Builder write timed out after ${timeoutMs}ms; remote outcome is unknown.`
|
|
145
|
+
: error instanceof Error
|
|
146
|
+
? `Builder write transport failed after dispatch; remote outcome is unknown: ${error.message}`
|
|
147
|
+
: "Builder write transport failed after dispatch; remote outcome is unknown.",
|
|
206
148
|
};
|
|
149
|
+
} finally {
|
|
150
|
+
clearTimeout(timeout);
|
|
207
151
|
}
|
|
208
152
|
}
|