@agent-native/core-corpus 0.216.0 → 0.218.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/session-replay/SKILL.md +61 -1
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +3 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
- package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
- package/corpus/templates/analytics/actions/view-screen.ts +29 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
- package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
- package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +67 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
- package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
- package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
- package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
- package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
- package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
- package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
- package/corpus/templates/analytics/server/plugins/db.ts +46 -0
- package/corpus/templates/analytics/shared/output-use.ts +205 -0
- package/corpus/templates/analytics/shared/session-events.ts +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +12 -0
- package/corpus/templates/calendar/actions/create-event.ts +2 -4
- package/corpus/templates/calendar/actions/update-event.ts +35 -6
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
- package/corpus/templates/content/CHANGELOG.md +10 -0
- package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
- package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
- package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
- package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
- package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
- package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
- package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
- package/corpus/templates/content/actions/_database-utils.ts +93 -7
- package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
- package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
- package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
- package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
- package/corpus/templates/content/actions/delete-document.ts +41 -3
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
- package/corpus/templates/content/actions/get-document.ts +2 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
- package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
- package/corpus/templates/content/actions/update-document.ts +139 -20
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +119 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +56 -13
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
- package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
- package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
- package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +3 -1
- package/corpus/templates/content/server/db/schema.ts +20 -0
- package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
- package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
- package/corpus/templates/content/server/live-copy/binding.ts +31 -0
- package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
- package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
- package/corpus/templates/content/server/live-copy/holds.ts +59 -0
- package/corpus/templates/content/server/live-copy/persist.ts +656 -0
- package/corpus/templates/content/server/live-copy/switch.ts +136 -0
- package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
- package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
- package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
- package/corpus/templates/content/server/live-copy/write.ts +85 -0
- package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
- package/corpus/templates/content/server/plugins/collab.ts +3 -0
- package/corpus/templates/content/server/plugins/db.ts +28 -0
- package/corpus/templates/content/shared/api.ts +30 -1
- package/corpus/templates/content/shared/database-item-layout.ts +54 -22
- package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
- package/corpus/templates/content/shared/og-description.ts +258 -36
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
- package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
- package/corpus/templates/design/CHANGELOG.md +5 -0
- package/corpus/templates/design/actions/get-getting-started.ts +53 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
- package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
- package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
- package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
- package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
- package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
- package/corpus/templates/design/app/global.css +6 -0
- package/corpus/templates/design/app/i18n/en-US.ts +17 -0
- package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
- package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
- package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +28 -67
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
- package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
- package/corpus/templates/design/server/lib/getting-started.ts +267 -0
- package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
- package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
- package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
- package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
- package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
- package/corpus/templates/design/shared/design-template-presets.ts +8 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +7 -0
- package/corpus/templates/slides/actions/delete-deck.ts +7 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
- package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
|
@@ -7,6 +7,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
7
7
|
|
|
8
8
|
### Improved
|
|
9
9
|
|
|
10
|
+
- A collection linked to a big GitHub folder fills faster, and pushes to other linked collections aren't held up while it does
|
|
10
11
|
- Accept remaining and Reject remaining show the result for every suggested edit at once
|
|
11
12
|
- Accepting a group of suggested edits saves faster and adds one page version
|
|
12
13
|
|
|
@@ -18,6 +19,10 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
18
19
|
|
|
19
20
|
## 2026-10-21
|
|
20
21
|
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- List rows can show the first lines of each page under its title: pick **Page text** for Line 2
|
|
25
|
+
|
|
21
26
|
### Improved
|
|
22
27
|
|
|
23
28
|
- Suggested edits show an agent's whole reason: open a group to read past the first two lines, and lone edits show the reason above the change
|
|
@@ -37,6 +42,8 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
37
42
|
|
|
38
43
|
### Fixed
|
|
39
44
|
|
|
45
|
+
- Suggested edits that add, remove, split, or merge paragraphs, line breaks, and list items now show where the break goes and save as one clear suggestion
|
|
46
|
+
- Builder content sync keeps read and write status accurate when API responses fail.
|
|
40
47
|
- HTML table cells preserve a trailing backslash when opening Markdown.
|
|
41
48
|
- Preserve empty paragraph colors and refuse unsupported inline image and oversized math formatting before content is stored.
|
|
42
49
|
|
|
@@ -67,6 +74,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
67
74
|
|
|
68
75
|
### Added
|
|
69
76
|
|
|
77
|
+
- Fill a collection from a GitHub folder through its Sources settings, which an empty collection links to from under its title and which say up front when GitHub can't link and what to do instead. A short preview comes before linking, the collection shows where its pages come from and when they last updated, with a link back to its folder in Sources, and unlinking asks first
|
|
70
78
|
- Connected AI assistants can make a collection from a GitHub folder, or link an empty one: each Markdown file becomes a view-only page, and a sync brings in the latest pushes
|
|
71
79
|
- Make a collection from a GitHub folder in the sidebar's New menu, or fill an empty one from its Sources settings, which say up front when GitHub can't link and what to do instead. A short preview comes before linking, the collection shows where its pages come from and when they last updated, and unlinking asks first
|
|
72
80
|
- Connected AI assistants can link a new collection to a GitHub folder: each Markdown file becomes a view-only page, and a sync brings in the latest pushes
|
|
@@ -91,6 +99,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
91
99
|
|
|
92
100
|
### Fixed
|
|
93
101
|
|
|
102
|
+
- Builder Content avoids retrying a write when Builder accepted it but the response could not be saved.
|
|
94
103
|
- Agents reading a page you have open no longer wait or fail when your edits are already saved
|
|
95
104
|
|
|
96
105
|
## 2026-10-16
|
|
@@ -101,6 +110,7 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
101
110
|
|
|
102
111
|
### Fixed
|
|
103
112
|
|
|
113
|
+
- Accepting a suggestion on a Page that changed since it was suggested no longer shows "Something went wrong"
|
|
104
114
|
- A page open in a tab that shows it's in the trash reopens within seconds when someone restores it.
|
|
105
115
|
- A page restored from Trash returns to its place in its collection, and edits to a page in Trash are refused
|
|
106
116
|
- Agent-written Markdown keeps nested lists indented with spaces, underscore bold and italics, and escaped punctuation
|
|
@@ -4,13 +4,15 @@ import { ActionContractError } from "@agent-native/core";
|
|
|
4
4
|
import { and, eq } from "drizzle-orm";
|
|
5
5
|
|
|
6
6
|
import { getDb, schema } from "../server/db/index.js";
|
|
7
|
+
import type { LiveCopyBodySave } from "../shared/api.js";
|
|
7
8
|
|
|
8
9
|
export interface BrowserSaveAttemptConfirmation {
|
|
9
10
|
attemptId: string;
|
|
10
|
-
result: "applied" | "replayed";
|
|
11
|
+
result: "applied" | "replayed" | "live_copy";
|
|
11
12
|
revision: string;
|
|
12
13
|
updatedAt: string;
|
|
13
14
|
softDeletedDatabaseIds?: string[];
|
|
15
|
+
bodySave?: LiveCopyBodySave;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export interface BrowserSaveAttemptPreservation {
|
|
@@ -72,6 +74,7 @@ export function readBrowserSaveAttempt(
|
|
|
72
74
|
reason?: unknown;
|
|
73
75
|
checkpointId?: unknown;
|
|
74
76
|
softDeletedDatabaseIds?: unknown;
|
|
77
|
+
bodySave?: unknown;
|
|
75
78
|
};
|
|
76
79
|
if (
|
|
77
80
|
typeof result.revision !== "string" ||
|
|
@@ -119,6 +122,12 @@ export function readBrowserSaveAttempt(
|
|
|
119
122
|
{ errorCode: "BROWSER_SAVE_RECEIPT_INVALID", statusCode: 500 },
|
|
120
123
|
);
|
|
121
124
|
}
|
|
125
|
+
if (result.bodySave !== undefined && !isLiveCopyBodySave(result.bodySave)) {
|
|
126
|
+
throw new ActionContractError(
|
|
127
|
+
"The stored browser save receipt has an invalid live-copy body save.",
|
|
128
|
+
{ errorCode: "BROWSER_SAVE_RECEIPT_INVALID", statusCode: 500 },
|
|
129
|
+
);
|
|
130
|
+
}
|
|
122
131
|
return {
|
|
123
132
|
attemptId: stored.attemptId,
|
|
124
133
|
result: "replayed",
|
|
@@ -127,5 +136,18 @@ export function readBrowserSaveAttempt(
|
|
|
127
136
|
softDeletedDatabaseIds: result.softDeletedDatabaseIds as
|
|
128
137
|
| string[]
|
|
129
138
|
| undefined,
|
|
139
|
+
...(result.bodySave ? { bodySave: result.bodySave } : {}),
|
|
130
140
|
};
|
|
131
141
|
}
|
|
142
|
+
|
|
143
|
+
function isLiveCopyBodySave(value: unknown): value is LiveCopyBodySave {
|
|
144
|
+
const save = value as Partial<LiveCopyBodySave> | null;
|
|
145
|
+
return (
|
|
146
|
+
typeof save === "object" &&
|
|
147
|
+
save !== null &&
|
|
148
|
+
save.status === "live_copy" &&
|
|
149
|
+
typeof save.matchesLiveCopy === "boolean" &&
|
|
150
|
+
Number.isSafeInteger(save.bodyRevision) &&
|
|
151
|
+
Number.isSafeInteger(save.collabVersion)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { ActionContractError } from "@agent-native/core";
|
|
4
|
+
import {
|
|
5
|
+
deletePrivateBlob,
|
|
6
|
+
putPrivateBlob,
|
|
7
|
+
readPrivateBlob,
|
|
8
|
+
type PrivateBlobHandle,
|
|
9
|
+
} from "@agent-native/core/private-blob";
|
|
10
|
+
import { asc, eq, lte } from "drizzle-orm";
|
|
11
|
+
|
|
12
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
13
|
+
|
|
14
|
+
const textEncoder = new TextEncoder();
|
|
15
|
+
const textDecoder = new TextDecoder();
|
|
16
|
+
const BUILDER_PRIVATE_PAYLOAD_CLEANUP_BATCH_SIZE = 50;
|
|
17
|
+
const BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_BASE_MS = 30_000;
|
|
18
|
+
const BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_MAX_MS = 6 * 60 * 60 * 1000;
|
|
19
|
+
|
|
20
|
+
export const BUILDER_EXECUTION_PAYLOAD_BLOB_KEY =
|
|
21
|
+
"__builder.execution.privatePayload";
|
|
22
|
+
|
|
23
|
+
type BuilderBlobBinding = Record<string, string>;
|
|
24
|
+
|
|
25
|
+
interface BuilderBlobReference {
|
|
26
|
+
kind: "agent-native.builder-private-payload";
|
|
27
|
+
version: 1;
|
|
28
|
+
binding: BuilderBlobBinding;
|
|
29
|
+
sha256: string;
|
|
30
|
+
handle: PrivateBlobHandle;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface BuilderBlobEnvelope<T> {
|
|
34
|
+
kind: "agent-native.builder-private-payload";
|
|
35
|
+
version: 1;
|
|
36
|
+
binding: BuilderBlobBinding;
|
|
37
|
+
payload: T;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stableJson(value: unknown): string {
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
43
|
+
}
|
|
44
|
+
if (value && typeof value === "object") {
|
|
45
|
+
const record = value as Record<string, unknown>;
|
|
46
|
+
return `{${Object.keys(record)
|
|
47
|
+
.sort()
|
|
48
|
+
.map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`)
|
|
49
|
+
.join(",")}}`;
|
|
50
|
+
}
|
|
51
|
+
return JSON.stringify(value) ?? "null";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function digest(bytes: Uint8Array) {
|
|
55
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function builderPrivatePayloadUnavailable(
|
|
59
|
+
message: string,
|
|
60
|
+
_cause?: unknown,
|
|
61
|
+
): never {
|
|
62
|
+
throw new ActionContractError(message, {
|
|
63
|
+
errorCode: "BUILDER_PRIVATE_PAYLOAD_UNAVAILABLE",
|
|
64
|
+
statusCode: 409,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function logBuilderPrivatePayloadCleanupFailure(
|
|
69
|
+
label: string,
|
|
70
|
+
handle: PrivateBlobHandle,
|
|
71
|
+
reason: string,
|
|
72
|
+
) {
|
|
73
|
+
console.error(`[builder-private-payload] ${label} cleanup failed`, {
|
|
74
|
+
provider: handle.provider,
|
|
75
|
+
handleId: handle.id,
|
|
76
|
+
reason,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseReference(value: unknown): BuilderBlobReference | null {
|
|
81
|
+
if (typeof value !== "string" || !value.trim()) return null;
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(value) as BuilderBlobReference;
|
|
84
|
+
if (
|
|
85
|
+
parsed?.kind !== "agent-native.builder-private-payload" ||
|
|
86
|
+
parsed.version !== 1 ||
|
|
87
|
+
!parsed.binding ||
|
|
88
|
+
typeof parsed.binding !== "object" ||
|
|
89
|
+
typeof parsed.sha256 !== "string" ||
|
|
90
|
+
!parsed.handle ||
|
|
91
|
+
typeof parsed.handle.id !== "string" ||
|
|
92
|
+
typeof parsed.handle.provider !== "string" ||
|
|
93
|
+
parsed.handle.opaque !== true ||
|
|
94
|
+
typeof parsed.handle.encrypted !== "boolean"
|
|
95
|
+
) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return parsed;
|
|
99
|
+
// coercion-ok: null is the explicit invalid-reference marker; reads reject it and cleanup never authorizes deletion from it.
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function sameBinding(actual: BuilderBlobBinding, expected: BuilderBlobBinding) {
|
|
106
|
+
return stableJson(actual) === stableJson(expected);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function readBuilderPrivatePayload<T>(args: {
|
|
110
|
+
reference: unknown;
|
|
111
|
+
binding: BuilderBlobBinding;
|
|
112
|
+
label: string;
|
|
113
|
+
}): Promise<T> {
|
|
114
|
+
const reference = parseReference(args.reference);
|
|
115
|
+
if (!reference)
|
|
116
|
+
builderPrivatePayloadUnavailable(
|
|
117
|
+
`${args.label} private blob reference is missing or malformed.`,
|
|
118
|
+
);
|
|
119
|
+
if (!sameBinding(reference.binding, args.binding)) {
|
|
120
|
+
builderPrivatePayloadUnavailable(
|
|
121
|
+
`${args.label} private blob reference belongs to a different Builder operation.`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
let result;
|
|
125
|
+
try {
|
|
126
|
+
result = await readPrivateBlob(reference.handle);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
builderPrivatePayloadUnavailable(
|
|
129
|
+
`${args.label} private blob could not be read.`,
|
|
130
|
+
error,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (digest(result.data) !== reference.sha256) {
|
|
134
|
+
builderPrivatePayloadUnavailable(
|
|
135
|
+
`${args.label} private blob failed integrity verification.`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const envelope = JSON.parse(
|
|
140
|
+
textDecoder.decode(result.data),
|
|
141
|
+
) as BuilderBlobEnvelope<T>;
|
|
142
|
+
if (
|
|
143
|
+
envelope?.kind !== "agent-native.builder-private-payload" ||
|
|
144
|
+
envelope.version !== 1 ||
|
|
145
|
+
!sameBinding(envelope.binding, args.binding)
|
|
146
|
+
) {
|
|
147
|
+
builderPrivatePayloadUnavailable(
|
|
148
|
+
`${args.label} private blob identity is invalid.`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return envelope.payload;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
if (error instanceof ActionContractError) throw error;
|
|
154
|
+
builderPrivatePayloadUnavailable(
|
|
155
|
+
`${args.label} private blob payload is malformed.`,
|
|
156
|
+
error,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function putBuilderPrivatePayload<T>(args: {
|
|
162
|
+
payload: T;
|
|
163
|
+
binding: BuilderBlobBinding;
|
|
164
|
+
label: string;
|
|
165
|
+
ownerEmail?: string;
|
|
166
|
+
}): Promise<string> {
|
|
167
|
+
const bytes = textEncoder.encode(
|
|
168
|
+
JSON.stringify({
|
|
169
|
+
kind: "agent-native.builder-private-payload",
|
|
170
|
+
version: 1,
|
|
171
|
+
binding: args.binding,
|
|
172
|
+
payload: args.payload,
|
|
173
|
+
} satisfies BuilderBlobEnvelope<T>),
|
|
174
|
+
);
|
|
175
|
+
let handle: PrivateBlobHandle | null;
|
|
176
|
+
try {
|
|
177
|
+
handle = await putPrivateBlob({
|
|
178
|
+
data: bytes,
|
|
179
|
+
key: `content/builder/${createHash("sha256").update(stableJson(args.binding)).digest("hex")}/${randomUUID()}.json`,
|
|
180
|
+
filename: "builder-private-payload.json",
|
|
181
|
+
mimeType: "application/json",
|
|
182
|
+
ownerEmail: args.ownerEmail,
|
|
183
|
+
metadata: { kind: "builder-private-payload", version: 1 },
|
|
184
|
+
});
|
|
185
|
+
} catch (error) {
|
|
186
|
+
builderPrivatePayloadUnavailable(
|
|
187
|
+
`${args.label} could not be stored in private blob custody.`,
|
|
188
|
+
error,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (!handle)
|
|
192
|
+
builderPrivatePayloadUnavailable(
|
|
193
|
+
`${args.label} requires configured private blob custody.`,
|
|
194
|
+
);
|
|
195
|
+
const reference = JSON.stringify({
|
|
196
|
+
kind: "agent-native.builder-private-payload",
|
|
197
|
+
version: 1,
|
|
198
|
+
binding: args.binding,
|
|
199
|
+
sha256: digest(bytes),
|
|
200
|
+
handle,
|
|
201
|
+
} satisfies BuilderBlobReference);
|
|
202
|
+
try {
|
|
203
|
+
await readBuilderPrivatePayload({
|
|
204
|
+
reference,
|
|
205
|
+
binding: args.binding,
|
|
206
|
+
label: args.label,
|
|
207
|
+
});
|
|
208
|
+
} catch (error) {
|
|
209
|
+
let cleanupReason: string | null = null;
|
|
210
|
+
try {
|
|
211
|
+
const cleanup = await deletePrivateBlob(handle);
|
|
212
|
+
if (!cleanup.deleted) {
|
|
213
|
+
cleanupReason = cleanup.reason ?? "Deletion was declined";
|
|
214
|
+
}
|
|
215
|
+
} catch (cleanupError) {
|
|
216
|
+
cleanupReason =
|
|
217
|
+
cleanupError instanceof Error ? cleanupError.name : "Unknown error";
|
|
218
|
+
}
|
|
219
|
+
if (cleanupReason) {
|
|
220
|
+
try {
|
|
221
|
+
await enqueueBuilderPrivatePayloadCleanup(getDb(), reference);
|
|
222
|
+
} catch {
|
|
223
|
+
// Keep the provider failure visible if durable tracking is unavailable.
|
|
224
|
+
}
|
|
225
|
+
logBuilderPrivatePayloadCleanupFailure(args.label, handle, cleanupReason);
|
|
226
|
+
}
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
return reference;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export async function deleteBuilderPrivatePayload(reference: unknown) {
|
|
233
|
+
const parsed = parseReference(reference);
|
|
234
|
+
if (!parsed) return false;
|
|
235
|
+
const result = await deletePrivateBlob(parsed.handle);
|
|
236
|
+
return result.deleted;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function enqueueBuilderPrivatePayloadCleanup(
|
|
240
|
+
db: ReturnType<typeof getDb>,
|
|
241
|
+
reference: unknown,
|
|
242
|
+
) {
|
|
243
|
+
if (!isBuilderPrivatePayloadReference(reference)) return;
|
|
244
|
+
await db
|
|
245
|
+
.insert(schema.contentDatabaseBuilderBlobCleanup)
|
|
246
|
+
.values({ blobReference: reference as string })
|
|
247
|
+
.onConflictDoNothing();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function drainBuilderPrivatePayloadCleanupQueue(args: {
|
|
251
|
+
db: ReturnType<typeof getDb>;
|
|
252
|
+
label: string;
|
|
253
|
+
deadlineAt?: number;
|
|
254
|
+
signal?: AbortSignal;
|
|
255
|
+
}) {
|
|
256
|
+
const cleanupQueue = schema.contentDatabaseBuilderBlobCleanup;
|
|
257
|
+
|
|
258
|
+
let pending: Array<{ blobReference: string; attemptCount: number }>;
|
|
259
|
+
try {
|
|
260
|
+
pending = await args.db
|
|
261
|
+
.select({
|
|
262
|
+
blobReference: cleanupQueue.blobReference,
|
|
263
|
+
attemptCount: cleanupQueue.attemptCount,
|
|
264
|
+
})
|
|
265
|
+
.from(cleanupQueue)
|
|
266
|
+
.where(lte(cleanupQueue.nextAttemptAt, new Date().toISOString()))
|
|
267
|
+
.orderBy(asc(cleanupQueue.nextAttemptAt), asc(cleanupQueue.createdAt))
|
|
268
|
+
.limit(BUILDER_PRIVATE_PAYLOAD_CLEANUP_BATCH_SIZE);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(
|
|
271
|
+
`[builder-private-payload] ${args.label} cleanup queue could not be read`,
|
|
272
|
+
error,
|
|
273
|
+
);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const queued of pending) {
|
|
278
|
+
if (
|
|
279
|
+
args.signal?.aborted ||
|
|
280
|
+
(args.deadlineAt !== undefined && Date.now() >= args.deadlineAt)
|
|
281
|
+
) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
const deleted = await deleteBuilderPrivatePayload(queued.blobReference);
|
|
286
|
+
if (!deleted) {
|
|
287
|
+
console.error(
|
|
288
|
+
`[builder-private-payload] ${args.label} cleanup was declined; retry retained`,
|
|
289
|
+
);
|
|
290
|
+
await scheduleBuilderPrivatePayloadCleanupRetry(args, queued);
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
await args.db
|
|
294
|
+
.delete(cleanupQueue)
|
|
295
|
+
.where(eq(cleanupQueue.blobReference, queued.blobReference));
|
|
296
|
+
} catch (error) {
|
|
297
|
+
console.error(
|
|
298
|
+
`[builder-private-payload] ${args.label} cleanup remains queued`,
|
|
299
|
+
error,
|
|
300
|
+
);
|
|
301
|
+
await scheduleBuilderPrivatePayloadCleanupRetry(args, queued);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function scheduleBuilderPrivatePayloadCleanupRetry(
|
|
307
|
+
args: {
|
|
308
|
+
db: ReturnType<typeof getDb>;
|
|
309
|
+
label: string;
|
|
310
|
+
},
|
|
311
|
+
queued: { blobReference: string; attemptCount: number },
|
|
312
|
+
) {
|
|
313
|
+
const attemptCount = queued.attemptCount + 1;
|
|
314
|
+
const delayMs = Math.min(
|
|
315
|
+
BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_BASE_MS *
|
|
316
|
+
2 ** Math.min(attemptCount - 1, 10),
|
|
317
|
+
BUILDER_PRIVATE_PAYLOAD_CLEANUP_RETRY_MAX_MS,
|
|
318
|
+
);
|
|
319
|
+
try {
|
|
320
|
+
await args.db
|
|
321
|
+
.update(schema.contentDatabaseBuilderBlobCleanup)
|
|
322
|
+
.set({
|
|
323
|
+
attemptCount,
|
|
324
|
+
nextAttemptAt: new Date(Date.now() + delayMs).toISOString(),
|
|
325
|
+
})
|
|
326
|
+
.where(
|
|
327
|
+
eq(
|
|
328
|
+
schema.contentDatabaseBuilderBlobCleanup.blobReference,
|
|
329
|
+
queued.blobReference,
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error(
|
|
334
|
+
`[builder-private-payload] ${args.label} cleanup retry could not be scheduled`,
|
|
335
|
+
error,
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export async function drainBuilderPrivatePayloadCleanup(
|
|
341
|
+
args: {
|
|
342
|
+
deadlineAt?: number;
|
|
343
|
+
signal?: AbortSignal;
|
|
344
|
+
} = {},
|
|
345
|
+
) {
|
|
346
|
+
await drainBuilderPrivatePayloadCleanupQueue({
|
|
347
|
+
db: getDb(),
|
|
348
|
+
label: "scheduled",
|
|
349
|
+
deadlineAt: args.deadlineAt,
|
|
350
|
+
signal: args.signal,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export async function cleanupBuilderPrivatePayload(
|
|
355
|
+
reference: unknown,
|
|
356
|
+
_label: string,
|
|
357
|
+
) {
|
|
358
|
+
if (!isBuilderPrivatePayloadReference(reference)) return;
|
|
359
|
+
const blobReference = reference as string;
|
|
360
|
+
await enqueueBuilderPrivatePayloadCleanup(getDb(), blobReference);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function isBuilderPrivatePayloadReference(value: unknown) {
|
|
364
|
+
return parseReference(value) !== null;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export function isBuilderPrivatePayloadBoundToSource(
|
|
368
|
+
value: unknown,
|
|
369
|
+
ownerEmail: string,
|
|
370
|
+
sourceId: string,
|
|
371
|
+
) {
|
|
372
|
+
const reference = parseReference(value);
|
|
373
|
+
return (
|
|
374
|
+
reference?.binding.ownerEmail === ownerEmail &&
|
|
375
|
+
reference.binding.sourceId === sourceId
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function builderExecutionPayloadReference(
|
|
380
|
+
payloadJson: string,
|
|
381
|
+
): string | null {
|
|
382
|
+
try {
|
|
383
|
+
const payload = JSON.parse(payloadJson) as Record<string, unknown>;
|
|
384
|
+
const reference = payload?.[BUILDER_EXECUTION_PAYLOAD_BLOB_KEY];
|
|
385
|
+
return isBuilderPrivatePayloadReference(reference)
|
|
386
|
+
? (reference as string)
|
|
387
|
+
: null;
|
|
388
|
+
// coercion-ok: malformed compact SQL cannot authorize execution-blob deletion.
|
|
389
|
+
} catch {
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export interface BuilderExecutionPayloadBinding {
|
|
395
|
+
ownerEmail: string;
|
|
396
|
+
sourceId: string;
|
|
397
|
+
changeSetId: string;
|
|
398
|
+
executionId: string;
|
|
399
|
+
idempotencyKey: string;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function executionBinding(
|
|
403
|
+
value: BuilderExecutionPayloadBinding,
|
|
404
|
+
): BuilderBlobBinding {
|
|
405
|
+
return { ...value };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export async function storeBuilderExecutionPayload(args: {
|
|
409
|
+
payload: unknown;
|
|
410
|
+
binding: BuilderExecutionPayloadBinding;
|
|
411
|
+
}) {
|
|
412
|
+
if (
|
|
413
|
+
!args.payload ||
|
|
414
|
+
typeof args.payload !== "object" ||
|
|
415
|
+
Array.isArray(args.payload)
|
|
416
|
+
) {
|
|
417
|
+
builderPrivatePayloadUnavailable("Builder execution payload is malformed.");
|
|
418
|
+
}
|
|
419
|
+
const payload = args.payload as Record<string, unknown>;
|
|
420
|
+
const reference = await putBuilderPrivatePayload({
|
|
421
|
+
payload,
|
|
422
|
+
binding: executionBinding(args.binding),
|
|
423
|
+
label: "Builder prepared request and receipt",
|
|
424
|
+
ownerEmail: args.binding.ownerEmail,
|
|
425
|
+
});
|
|
426
|
+
const response =
|
|
427
|
+
payload.response && typeof payload.response === "object"
|
|
428
|
+
? { stored: true }
|
|
429
|
+
: undefined;
|
|
430
|
+
return JSON.stringify({
|
|
431
|
+
[BUILDER_EXECUTION_PAYLOAD_BLOB_KEY]: reference,
|
|
432
|
+
...(response ? { response } : {}),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
export async function readBuilderExecutionPayload(args: {
|
|
437
|
+
payloadJson: string;
|
|
438
|
+
binding: BuilderExecutionPayloadBinding;
|
|
439
|
+
}): Promise<Record<string, unknown>> {
|
|
440
|
+
let stored: Record<string, unknown>;
|
|
441
|
+
try {
|
|
442
|
+
const parsed = JSON.parse(args.payloadJson) as unknown;
|
|
443
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
444
|
+
builderPrivatePayloadUnavailable(
|
|
445
|
+
"Builder execution payload JSON is malformed.",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
stored = parsed as Record<string, unknown>;
|
|
449
|
+
} catch {
|
|
450
|
+
builderPrivatePayloadUnavailable(
|
|
451
|
+
"Builder execution payload JSON is malformed.",
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
const reference = stored[BUILDER_EXECUTION_PAYLOAD_BLOB_KEY];
|
|
455
|
+
if (reference === undefined) return stored;
|
|
456
|
+
const payload = await readBuilderPrivatePayload<unknown>({
|
|
457
|
+
reference,
|
|
458
|
+
binding: executionBinding(args.binding),
|
|
459
|
+
label: "Builder prepared request and receipt",
|
|
460
|
+
});
|
|
461
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
462
|
+
builderPrivatePayloadUnavailable(
|
|
463
|
+
"Builder execution private blob payload is malformed.",
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
return payload as Record<string, unknown>;
|
|
467
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const BUILDER_EXECUTION_CLAIM_TIMEOUT_MS = 10 * 60 * 1000;
|
|
2
|
+
|
|
3
|
+
export function builderExecutionStaleBefore(now: string) {
|
|
4
|
+
const timestamp = Date.parse(now);
|
|
5
|
+
return new Date(
|
|
6
|
+
(Number.isFinite(timestamp) ? timestamp : Date.now()) -
|
|
7
|
+
BUILDER_EXECUTION_CLAIM_TIMEOUT_MS,
|
|
8
|
+
).toISOString();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isBuilderExecutionClaimStale(updatedAt: string, now: string) {
|
|
12
|
+
const updatedTimestamp = Date.parse(updatedAt);
|
|
13
|
+
return (
|
|
14
|
+
Number.isFinite(updatedTimestamp) &&
|
|
15
|
+
updatedTimestamp < Date.parse(builderExecutionStaleBefore(now))
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -67,6 +67,8 @@ export async function lookupBuilderCmsSafeModelIntent(args: {
|
|
|
67
67
|
marker?: string;
|
|
68
68
|
exactTitle?: string;
|
|
69
69
|
intendedFields?: Record<string, unknown>;
|
|
70
|
+
expectedSourceSpace?: string | null;
|
|
71
|
+
expectedSourceConnectionId?: string | null;
|
|
70
72
|
}) {
|
|
71
73
|
if (!args.marker && !args.exactTitle) {
|
|
72
74
|
throw new Error("Provide an exact execution marker or exact title.");
|
|
@@ -75,7 +77,10 @@ export async function lookupBuilderCmsSafeModelIntent(args: {
|
|
|
75
77
|
model: BUILDER_CMS_SAFE_WRITE_MODEL,
|
|
76
78
|
rawData: true,
|
|
77
79
|
requirePrivateKey: true,
|
|
80
|
+
requireBoundOAuth: true,
|
|
78
81
|
limit: 10_000,
|
|
82
|
+
expectedSourceSpace: args.expectedSourceSpace,
|
|
83
|
+
expectedSourceConnectionId: args.expectedSourceConnectionId,
|
|
79
84
|
});
|
|
80
85
|
if (result.state !== "live" || result.progress.partial) {
|
|
81
86
|
throw new Error(
|