@agent-native/core 0.101.7 → 0.101.11

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.
Files changed (219) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +28 -0
  3. package/corpus/core/docs/content/deployment.mdx +9 -7
  4. package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +9 -7
  5. package/corpus/core/docs/content/locales/de-DE/deployment.mdx +9 -7
  6. package/corpus/core/docs/content/locales/es-ES/deployment.mdx +9 -7
  7. package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +9 -7
  8. package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +9 -7
  9. package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +9 -7
  10. package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +9 -7
  11. package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +9 -7
  12. package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +9 -7
  13. package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +9 -7
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/a2a/client.ts +23 -1
  16. package/corpus/core/src/a2a/handlers.ts +65 -1
  17. package/corpus/core/src/a2a/server.ts +124 -0
  18. package/corpus/core/src/a2a/task-store.ts +253 -0
  19. package/corpus/core/src/a2a/types.ts +26 -0
  20. package/corpus/core/src/agent/production-agent.ts +5 -2
  21. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
  22. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
  23. package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
  24. package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
  25. package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
  26. package/corpus/core/src/mcp/builtin-tools.ts +25 -1
  27. package/corpus/core/src/scripts/call-agent.ts +22 -3
  28. package/corpus/core/src/server/agent-chat-plugin.ts +86 -1
  29. package/corpus/core/src/server/auth.ts +25 -1
  30. package/corpus/core/src/server/builder-browser.ts +353 -1
  31. package/corpus/core/src/server/core-routes-plugin.ts +601 -223
  32. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  33. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
  34. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
  35. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
  36. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
  37. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
  38. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
  39. package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
  40. package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
  41. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +23 -0
  42. package/corpus/templates/content/AGENTS.md +3 -2
  43. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
  44. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
  45. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  46. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
  47. package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
  48. package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
  49. package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
  50. package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
  51. package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
  52. package/corpus/templates/content/actions/_database-source-utils.ts +1041 -154
  53. package/corpus/templates/content/actions/_database-utils.ts +89 -12
  54. package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
  55. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
  56. package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
  57. package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
  58. package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
  59. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
  60. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
  61. package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
  62. package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
  63. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
  64. package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +27 -15
  66. package/corpus/templates/content/app/components/editor/MathRenderer.tsx +43 -0
  67. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +260 -4
  68. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
  69. package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
  70. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2353 -576
  71. package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
  72. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
  73. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +78 -0
  74. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
  75. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
  76. package/corpus/templates/content/app/global.css +71 -1
  77. package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
  79. package/corpus/templates/content/app/i18n/zh-TW.ts +38 -0
  80. package/corpus/templates/content/app/i18n-data.ts +450 -0
  81. package/corpus/templates/content/app/root.tsx +2 -0
  82. package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
  83. package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
  93. package/corpus/templates/content/changelog/2026-07-14-latex-equations-render-in-documents-public-pages-and-exports.md +6 -0
  94. package/corpus/templates/content/package.json +1 -0
  95. package/corpus/templates/content/parity/matrix.md +27 -25
  96. package/corpus/templates/content/parity/matrix.ts +50 -2
  97. package/corpus/templates/content/server/db/schema.ts +47 -0
  98. package/corpus/templates/content/server/plugins/auth.ts +1 -0
  99. package/corpus/templates/content/server/plugins/db.ts +53 -0
  100. package/corpus/templates/content/shared/api.ts +70 -1
  101. package/corpus/templates/content/shared/builder-mdx.ts +705 -15
  102. package/corpus/templates/content/shared/document-export.ts +296 -25
  103. package/corpus/templates/content/shared/inline-math.ts +128 -0
  104. package/corpus/templates/content/shared/math-rendering.ts +50 -0
  105. package/corpus/templates/content/shared/nfm.ts +20 -17
  106. package/corpus/templates/content/vite.config.ts +0 -1
  107. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
  108. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
  109. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
  110. package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
  111. package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
  112. package/corpus/templates/design/app/i18n/en-US.ts +1 -0
  113. package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
  114. package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
  115. package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
  116. package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
  117. package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
  118. package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
  119. package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
  120. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  121. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
  122. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
  123. package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
  124. package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
  125. package/corpus/templates/design/shared/review-anchor.ts +48 -0
  126. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +17 -0
  127. package/corpus/templates/mail/AGENTS.md +21 -19
  128. package/corpus/templates/mail/actions/create-attachment-upload.ts +49 -0
  129. package/corpus/templates/mail/changelog/2026-07-14-connected-agents-can-now-securely-upload-local-files-for-mai.md +6 -0
  130. package/corpus/templates/mail/server/handlers/media.ts +84 -61
  131. package/corpus/templates/mail/server/lib/attachment-upload-ticket.ts +234 -0
  132. package/corpus/templates/mail/server/lib/mail-connector-catalog.ts +8 -4
  133. package/corpus/templates/mail/server/lib/media-upload.ts +114 -0
  134. package/corpus/templates/mail/server/plugins/auth.ts +9 -1
  135. package/corpus/templates/mail/server/routes/api/media/attachment-upload/[uploadId].put.ts +1 -0
  136. package/corpus/templates/plan/server/plan-mdx.ts +53 -7
  137. package/dist/a2a/client.d.ts +5 -1
  138. package/dist/a2a/client.d.ts.map +1 -1
  139. package/dist/a2a/client.js +18 -1
  140. package/dist/a2a/client.js.map +1 -1
  141. package/dist/a2a/handlers.d.ts.map +1 -1
  142. package/dist/a2a/handlers.js +57 -8
  143. package/dist/a2a/handlers.js.map +1 -1
  144. package/dist/a2a/server.d.ts.map +1 -1
  145. package/dist/a2a/server.js +87 -1
  146. package/dist/a2a/server.js.map +1 -1
  147. package/dist/a2a/task-store.d.ts +27 -0
  148. package/dist/a2a/task-store.d.ts.map +1 -1
  149. package/dist/a2a/task-store.js +216 -0
  150. package/dist/a2a/task-store.js.map +1 -1
  151. package/dist/a2a/types.d.ts +24 -0
  152. package/dist/a2a/types.d.ts.map +1 -1
  153. package/dist/a2a/types.js.map +1 -1
  154. package/dist/agent/production-agent.d.ts +1 -0
  155. package/dist/agent/production-agent.d.ts.map +1 -1
  156. package/dist/agent/production-agent.js +5 -2
  157. package/dist/agent/production-agent.js.map +1 -1
  158. package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
  159. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  160. package/dist/client/review/ReviewCommentComposer.js +3 -3
  161. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  162. package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
  163. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  164. package/dist/client/review/ReviewThreadPanel.js +6 -4
  165. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  166. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  167. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  168. package/dist/client/settings/SettingsPanel.js +11 -4
  169. package/dist/client/settings/SettingsPanel.js.map +1 -1
  170. package/dist/client/settings/useBuilderStatus.d.ts +8 -4
  171. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  172. package/dist/client/settings/useBuilderStatus.js +19 -6
  173. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  174. package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
  175. package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
  176. package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
  177. package/dist/collab/awareness.d.ts +2 -2
  178. package/dist/collab/awareness.d.ts.map +1 -1
  179. package/dist/collab/struct-routes.d.ts +1 -1
  180. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  181. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  182. package/dist/mcp/builtin-tools.js +22 -3
  183. package/dist/mcp/builtin-tools.js.map +1 -1
  184. package/dist/notifications/routes.d.ts +3 -3
  185. package/dist/resources/handlers.d.ts +2 -2
  186. package/dist/scripts/call-agent.d.ts +1 -1
  187. package/dist/scripts/call-agent.d.ts.map +1 -1
  188. package/dist/scripts/call-agent.js +19 -1
  189. package/dist/scripts/call-agent.js.map +1 -1
  190. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  191. package/dist/server/agent-chat-plugin.js +71 -2
  192. package/dist/server/agent-chat-plugin.js.map +1 -1
  193. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  194. package/dist/server/auth.d.ts +1 -0
  195. package/dist/server/auth.d.ts.map +1 -1
  196. package/dist/server/auth.js +19 -2
  197. package/dist/server/auth.js.map +1 -1
  198. package/dist/server/builder-browser.d.ts +78 -0
  199. package/dist/server/builder-browser.d.ts.map +1 -1
  200. package/dist/server/builder-browser.js +246 -1
  201. package/dist/server/builder-browser.js.map +1 -1
  202. package/dist/server/core-routes-plugin.d.ts +41 -0
  203. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  204. package/dist/server/core-routes-plugin.js +260 -14
  205. package/dist/server/core-routes-plugin.js.map +1 -1
  206. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
  207. package/docs/content/deployment.mdx +9 -7
  208. package/docs/content/locales/ar-SA/deployment.mdx +9 -7
  209. package/docs/content/locales/de-DE/deployment.mdx +9 -7
  210. package/docs/content/locales/es-ES/deployment.mdx +9 -7
  211. package/docs/content/locales/fr-FR/deployment.mdx +9 -7
  212. package/docs/content/locales/hi-IN/deployment.mdx +9 -7
  213. package/docs/content/locales/ja-JP/deployment.mdx +9 -7
  214. package/docs/content/locales/ko-KR/deployment.mdx +9 -7
  215. package/docs/content/locales/pt-BR/deployment.mdx +9 -7
  216. package/docs/content/locales/zh-CN/deployment.mdx +9 -7
  217. package/docs/content/locales/zh-TW/deployment.mdx +9 -7
  218. package/package.json +2 -2
  219. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +25 -0
@@ -36,6 +36,32 @@ type DatabaseMembershipRow = {
36
36
  bodyHydrationQueueId?: string | null;
37
37
  };
38
38
 
39
+ type DocumentListRow = Omit<typeof schema.documents.$inferSelect, "content">;
40
+
41
+ // Database grids render row metadata and properties. Fetching the document body
42
+ // here would transfer it only for serializeDocument to replace it with an empty
43
+ // string below; opened documents use their dedicated document read path instead.
44
+ export const contentDatabaseListDocumentSelection = {
45
+ id: schema.documents.id,
46
+ parentId: schema.documents.parentId,
47
+ title: schema.documents.title,
48
+ description: schema.documents.description,
49
+ icon: schema.documents.icon,
50
+ position: schema.documents.position,
51
+ isFavorite: schema.documents.isFavorite,
52
+ hideFromSearch: schema.documents.hideFromSearch,
53
+ sourceMode: schema.documents.sourceMode,
54
+ sourceKind: schema.documents.sourceKind,
55
+ sourcePath: schema.documents.sourcePath,
56
+ sourceRootPath: schema.documents.sourceRootPath,
57
+ sourceUpdatedAt: schema.documents.sourceUpdatedAt,
58
+ visibility: schema.documents.visibility,
59
+ ownerEmail: schema.documents.ownerEmail,
60
+ orgId: schema.documents.orgId,
61
+ createdAt: schema.documents.createdAt,
62
+ updatedAt: schema.documents.updatedAt,
63
+ };
64
+
39
65
  export function serializeBodyHydration(
40
66
  item: typeof schema.contentDatabaseItems.$inferSelect,
41
67
  options: { queued?: boolean } = {},
@@ -46,6 +72,7 @@ export function serializeBodyHydration(
46
72
  status === "pending" ||
47
73
  status === "hydrating" ||
48
74
  status === "hydrated" ||
75
+ status === "unavailable" ||
49
76
  status === "error"
50
77
  ? status
51
78
  : options.queued
@@ -123,16 +150,61 @@ export function normalizeContentDatabasePageOptions(options: {
123
150
  return { limit, offset };
124
151
  }
125
152
 
153
+ export function filterContentDatabaseSourceRowsForPage<
154
+ TRow extends { documentId: string; databaseItemId: string },
155
+ TChangeSet extends {
156
+ documentId: string | null;
157
+ databaseItemId: string | null;
158
+ direction: string;
159
+ state: string;
160
+ executions: Array<{ state: string }>;
161
+ },
162
+ >(args: {
163
+ rows: TRow[];
164
+ changeSets: TChangeSet[];
165
+ visibleDocumentIds: ReadonlySet<string>;
166
+ }) {
167
+ const actionableChangeSets = args.changeSets.filter(
168
+ (changeSet) =>
169
+ changeSet.direction === "outbound" &&
170
+ !changeSet.executions.some(
171
+ (execution) => execution.state === "succeeded",
172
+ ) &&
173
+ (changeSet.state === "pending_push" ||
174
+ changeSet.state === "staged_revision" ||
175
+ changeSet.state === "approved"),
176
+ );
177
+ const actionableDocumentIds = new Set(
178
+ actionableChangeSets.flatMap((changeSet) =>
179
+ changeSet.documentId ? [changeSet.documentId] : [],
180
+ ),
181
+ );
182
+ const actionableItemIds = new Set(
183
+ actionableChangeSets.flatMap((changeSet) =>
184
+ changeSet.databaseItemId ? [changeSet.databaseItemId] : [],
185
+ ),
186
+ );
187
+
188
+ return args.rows.filter(
189
+ (row) =>
190
+ !row.documentId ||
191
+ args.visibleDocumentIds.has(row.documentId) ||
192
+ actionableDocumentIds.has(row.documentId) ||
193
+ actionableItemIds.has(row.databaseItemId),
194
+ );
195
+ }
196
+
126
197
  function serializeDocument(
127
- doc: typeof schema.documents.$inferSelect,
198
+ doc: DocumentListRow,
128
199
  membership?: DatabaseMembershipRow,
129
- options: { includeContent?: boolean } = {},
130
200
  ) {
131
201
  return {
132
202
  id: doc.id,
133
203
  parentId: doc.parentId,
134
204
  title: doc.title,
135
- content: options.includeContent === true ? doc.content : "",
205
+ // List reads deliberately project no `documents.content`; opened documents
206
+ // use their dedicated read path.
207
+ content: "",
136
208
  description: doc.description,
137
209
  icon: doc.icon,
138
210
  position: doc.position,
@@ -196,7 +268,7 @@ export async function getContentDatabaseResponse(
196
268
  const documents =
197
269
  items.length > 0
198
270
  ? await db
199
- .select()
271
+ .select(contentDatabaseListDocumentSelection)
200
272
  .from(schema.documents)
201
273
  .where(
202
274
  and(
@@ -211,7 +283,9 @@ export async function getContentDatabaseResponse(
211
283
  const documentById = new Map(documents.map((doc) => [doc.id, doc]));
212
284
  const propertiesByDocumentId = await listPropertiesForDatabaseDocuments(
213
285
  databaseId,
214
- documents,
286
+ // Property serialization uses metadata only; this list projection carries
287
+ // every document field it consumes except the deliberately omitted body.
288
+ documents as Array<typeof schema.documents.$inferSelect>,
215
289
  );
216
290
  const queuedBodyHydrationItemIds =
217
291
  items.length > 0
@@ -259,17 +333,20 @@ export async function getContentDatabaseResponse(
259
333
  serializedItems.map((item) => item.document.id),
260
334
  );
261
335
  // When paginating, scope every DOCUMENT-BACKED source's rows to the visible
262
- // page that's the primary AND any row-union secondary (each row maps to a
263
- // real document). Federated join rows carry no document (empty documentId),
264
- // so they're kept intact only matched ones overlay anyway.
336
+ // page, plus the small set referenced by actionable reviews. The dialog gets
337
+ // change sets independently of the item page and needs those rows to retain
338
+ // the linked provider target instead of misclassifying an off-page update as
339
+ // a create. Federated join rows carry no document (empty documentId), so
340
+ // they're kept intact — only matched ones overlay anyway.
265
341
  const pagedSources =
266
342
  limit !== null
267
343
  ? sources.map((source) => ({
268
344
  ...source,
269
- rows: source.rows.filter(
270
- (row) =>
271
- !row.documentId || serializedDocumentIds.has(row.documentId),
272
- ),
345
+ rows: filterContentDatabaseSourceRowsForPage({
346
+ rows: source.rows,
347
+ changeSets: source.changeSets,
348
+ visibleDocumentIds: serializedDocumentIds,
349
+ }),
273
350
  }))
274
351
  : sources;
275
352
  const pagedPrimary = pagedSources[0] ?? null;
@@ -0,0 +1,31 @@
1
+ import { and, eq } from "drizzle-orm";
2
+
3
+ import { getDb, schema } from "../server/db/index.js";
4
+
5
+ export async function readPreviewDocumentDraft(
6
+ ownerEmail: string,
7
+ orgId: string,
8
+ documentId: string,
9
+ ) {
10
+ const [draft] = await getDb()
11
+ .select({
12
+ documentId: schema.documentPreviewDrafts.documentId,
13
+ title: schema.documentPreviewDrafts.title,
14
+ content: schema.documentPreviewDrafts.content,
15
+ baseDocumentUpdatedAt: schema.documentPreviewDrafts.baseDocumentUpdatedAt,
16
+ loadedContentWasEmpty: schema.documentPreviewDrafts.loadedContentWasEmpty,
17
+ deferredReason: schema.documentPreviewDrafts.deferredReason,
18
+ version: schema.documentPreviewDrafts.version,
19
+ updatedAt: schema.documentPreviewDrafts.updatedAt,
20
+ })
21
+ .from(schema.documentPreviewDrafts)
22
+ .where(
23
+ and(
24
+ eq(schema.documentPreviewDrafts.ownerEmail, ownerEmail),
25
+ eq(schema.documentPreviewDrafts.orgId, orgId),
26
+ eq(schema.documentPreviewDrafts.documentId, documentId),
27
+ ),
28
+ )
29
+ .limit(1);
30
+ return draft ?? null;
31
+ }
@@ -0,0 +1,309 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { getRequestUserEmail } from "@agent-native/core/server/request-context";
3
+ import { assertAccess } from "@agent-native/core/sharing";
4
+ import { and, eq, inArray } from "drizzle-orm";
5
+ import { z } from "zod";
6
+
7
+ import { getDb, schema } from "../server/db/index.js";
8
+ import type {
9
+ CancelPreparedBuilderSourceUpdateRequest,
10
+ CancelPreparedBuilderSourceUpdateResponse,
11
+ } from "../shared/api.js";
12
+ import {
13
+ CANCELLED_BUILDER_EXECUTION_SUMMARY,
14
+ CANCELLED_BUILDER_REVIEW_NOTE_PREFIX,
15
+ resolveDatabaseForSourceMutation,
16
+ } from "./_database-source-utils.js";
17
+ import { getContentDatabaseResponse } from "./_database-utils.js";
18
+
19
+ const PRE_DISPATCH_STATES = new Set(["ready", "write_disabled", "blocked"]);
20
+
21
+ function parseExecutionPayload(payloadJson: string) {
22
+ let payload: unknown;
23
+ try {
24
+ payload = JSON.parse(payloadJson);
25
+ } catch {
26
+ throw new Error(
27
+ "Cannot cancel this Builder update because its execution evidence is unreadable.",
28
+ );
29
+ }
30
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
31
+ throw new Error(
32
+ "Cannot cancel this Builder update because its execution evidence is ambiguous.",
33
+ );
34
+ }
35
+ return payload as Record<string, unknown>;
36
+ }
37
+
38
+ function isLocalBlockedDryRun(payload: Record<string, unknown>) {
39
+ const dryRun = payload.dryRun;
40
+ return (
41
+ !!dryRun &&
42
+ typeof dryRun === "object" &&
43
+ !Array.isArray(dryRun) &&
44
+ (dryRun as Record<string, unknown>).status === "blocked"
45
+ );
46
+ }
47
+
48
+ function assertProvablyPreDispatch(execution: {
49
+ state: string;
50
+ summary: string;
51
+ payloadJson: string;
52
+ attemptToken: string | null;
53
+ }) {
54
+ if (!PRE_DISPATCH_STATES.has(execution.state)) {
55
+ throw new Error(
56
+ `Cannot cancel a Builder execution in state ${execution.state}. Its outcome must be preserved.`,
57
+ );
58
+ }
59
+ if (execution.attemptToken) {
60
+ throw new Error(
61
+ "Cannot cancel this Builder update because a dispatch attempt was recorded.",
62
+ );
63
+ }
64
+
65
+ const payload = parseExecutionPayload(execution.payloadJson);
66
+ if (
67
+ Object.prototype.hasOwnProperty.call(payload, "response") ||
68
+ Object.prototype.hasOwnProperty.call(payload, "dispatch")
69
+ ) {
70
+ throw new Error(
71
+ "Cannot cancel this Builder update because dispatch or response evidence was recorded.",
72
+ );
73
+ }
74
+ if (
75
+ execution.state === "blocked" &&
76
+ execution.summary !== CANCELLED_BUILDER_EXECUTION_SUMMARY &&
77
+ !isLocalBlockedDryRun(payload)
78
+ ) {
79
+ throw new Error(
80
+ "Cannot cancel this blocked Builder update because it is not provably a local pre-dispatch block.",
81
+ );
82
+ }
83
+ }
84
+
85
+ function assertCanonicalClaims(
86
+ executions: Array<{ id: string; idempotencyKey: string }>,
87
+ claims: Array<{ idempotencyKey: string; executionId: string }>,
88
+ ) {
89
+ const idempotencyKeys = executions.map((row) => row.idempotencyKey);
90
+ if (new Set(idempotencyKeys).size !== executions.length) {
91
+ throw new Error(
92
+ "Cannot cancel this Builder update because duplicate execution gates share an idempotency key.",
93
+ );
94
+ }
95
+ if (claims.length !== executions.length) {
96
+ throw new Error(
97
+ "Cannot cancel this Builder update because its canonical execution claim is missing or foreign.",
98
+ );
99
+ }
100
+ const claimByKey = new Map(
101
+ claims.map((claim) => [claim.idempotencyKey, claim.executionId]),
102
+ );
103
+ for (const execution of executions) {
104
+ if (claimByKey.get(execution.idempotencyKey) !== execution.id) {
105
+ throw new Error(
106
+ "Cannot cancel this Builder update because an execution claim points to a different gate.",
107
+ );
108
+ }
109
+ }
110
+ }
111
+
112
+ export default defineAction({
113
+ description:
114
+ "Cancel one exact prepared Builder update only when every execution is provably pre-dispatch. This never calls Builder or deletes audit history.",
115
+ schema: z
116
+ .object({
117
+ databaseId: z.string().optional().describe("Database ID"),
118
+ documentId: z.string().optional().describe("Database document/page ID"),
119
+ sourceId: z.string().describe("Exact Builder source ID"),
120
+ changeSetId: z.string().describe("Exact prepared Builder change-set ID"),
121
+ note: z
122
+ .string()
123
+ .max(500)
124
+ .optional()
125
+ .describe("Optional cancellation note"),
126
+ })
127
+ .refine((value) => value.databaseId || value.documentId, {
128
+ message: "Provide databaseId or documentId.",
129
+ }),
130
+ run: async (
131
+ args: CancelPreparedBuilderSourceUpdateRequest,
132
+ ): Promise<CancelPreparedBuilderSourceUpdateResponse> => {
133
+ const database = await resolveDatabaseForSourceMutation(args);
134
+ if (!database) throw new Error("Database not found.");
135
+ await assertAccess("document", database.documentId, "editor");
136
+
137
+ const db = getDb();
138
+ const actor = getRequestUserEmail() ?? "agent-runtime@agent-native.local";
139
+ const now = new Date().toISOString();
140
+ let executionIds: string[] = [];
141
+ let status: "cancelled" | "already_cancelled" = "cancelled";
142
+
143
+ await db.transaction(async (tx) => {
144
+ const [source] = await tx
145
+ .select()
146
+ .from(schema.contentDatabaseSources)
147
+ .where(
148
+ and(
149
+ eq(schema.contentDatabaseSources.id, args.sourceId),
150
+ eq(schema.contentDatabaseSources.databaseId, database.id),
151
+ ),
152
+ )
153
+ .limit(1);
154
+ if (!source || source.sourceType !== "builder-cms") {
155
+ throw new Error("The exact Builder source was not found.");
156
+ }
157
+
158
+ const [changeSet] = await tx
159
+ .select()
160
+ .from(schema.contentDatabaseSourceChangeSets)
161
+ .where(
162
+ and(
163
+ eq(schema.contentDatabaseSourceChangeSets.id, args.changeSetId),
164
+ eq(schema.contentDatabaseSourceChangeSets.sourceId, source.id),
165
+ ),
166
+ )
167
+ .limit(1);
168
+ if (!changeSet) throw new Error("Source change-set not found.");
169
+
170
+ const executions = await tx
171
+ .select()
172
+ .from(schema.contentDatabaseSourceExecutions)
173
+ .where(
174
+ and(
175
+ eq(schema.contentDatabaseSourceExecutions.sourceId, source.id),
176
+ eq(
177
+ schema.contentDatabaseSourceExecutions.changeSetId,
178
+ changeSet.id,
179
+ ),
180
+ ),
181
+ );
182
+ executionIds = executions.map((execution) => execution.id);
183
+
184
+ if (executions.length === 0) {
185
+ throw new Error("No prepared Builder execution was found to cancel.");
186
+ }
187
+ for (const execution of executions) assertProvablyPreDispatch(execution);
188
+
189
+ const idempotencyKeys = [
190
+ ...new Set(executions.map((row) => row.idempotencyKey)),
191
+ ];
192
+ const claims = await tx
193
+ .select({
194
+ idempotencyKey:
195
+ schema.contentDatabaseSourceExecutionClaims.idempotencyKey,
196
+ executionId: schema.contentDatabaseSourceExecutionClaims.executionId,
197
+ })
198
+ .from(schema.contentDatabaseSourceExecutionClaims)
199
+ .where(
200
+ and(
201
+ eq(
202
+ schema.contentDatabaseSourceExecutionClaims.ownerEmail,
203
+ database.ownerEmail,
204
+ ),
205
+ eq(schema.contentDatabaseSourceExecutionClaims.sourceId, source.id),
206
+ inArray(
207
+ schema.contentDatabaseSourceExecutionClaims.idempotencyKey,
208
+ idempotencyKeys,
209
+ ),
210
+ ),
211
+ );
212
+ assertCanonicalClaims(executions, claims);
213
+
214
+ if (changeSet.state === "rejected") {
215
+ const cancellationReviews = await tx
216
+ .select()
217
+ .from(schema.contentDatabaseSourceChangeReviews)
218
+ .where(
219
+ and(
220
+ eq(schema.contentDatabaseSourceChangeReviews.sourceId, source.id),
221
+ eq(
222
+ schema.contentDatabaseSourceChangeReviews.changeSetId,
223
+ changeSet.id,
224
+ ),
225
+ eq(
226
+ schema.contentDatabaseSourceChangeReviews.decision,
227
+ "rejected",
228
+ ),
229
+ ),
230
+ );
231
+ const wasCancelled =
232
+ executions.length > 0 &&
233
+ executions.every(
234
+ (execution) =>
235
+ execution.state === "blocked" &&
236
+ execution.summary === CANCELLED_BUILDER_EXECUTION_SUMMARY,
237
+ ) &&
238
+ cancellationReviews.some((review) =>
239
+ review.note?.startsWith(CANCELLED_BUILDER_REVIEW_NOTE_PREFIX),
240
+ );
241
+ if (!wasCancelled) {
242
+ throw new Error(
243
+ "This Builder change-set was rejected through another review path, not cancelled as a prepared update.",
244
+ );
245
+ }
246
+ status = "already_cancelled";
247
+ return;
248
+ }
249
+
250
+ if (changeSet.state !== "approved") {
251
+ throw new Error(
252
+ `Only an approved, prepared Builder change-set can be cancelled; current state is ${changeSet.state}.`,
253
+ );
254
+ }
255
+
256
+ await tx
257
+ .update(schema.contentDatabaseSourceExecutions)
258
+ .set({
259
+ state: "blocked",
260
+ summary: CANCELLED_BUILDER_EXECUTION_SUMMARY,
261
+ lastError: null,
262
+ updatedAt: now,
263
+ })
264
+ .where(
265
+ and(
266
+ eq(schema.contentDatabaseSourceExecutions.sourceId, source.id),
267
+ eq(
268
+ schema.contentDatabaseSourceExecutions.changeSetId,
269
+ changeSet.id,
270
+ ),
271
+ ),
272
+ );
273
+
274
+ const noteSuffix = args.note?.trim() ? ` Note: ${args.note.trim()}` : "";
275
+ await tx.insert(schema.contentDatabaseSourceChangeReviews).values({
276
+ id: crypto.randomUUID(),
277
+ ownerEmail: database.ownerEmail,
278
+ sourceId: source.id,
279
+ changeSetId: changeSet.id,
280
+ reviewerEmail: actor,
281
+ decision: "rejected",
282
+ stateFrom: changeSet.state,
283
+ stateTo: "rejected",
284
+ note: `${CANCELLED_BUILDER_REVIEW_NOTE_PREFIX} by ${actor} at ${now}.${noteSuffix}`,
285
+ createdAt: now,
286
+ });
287
+ await tx
288
+ .update(schema.contentDatabaseSourceChangeSets)
289
+ .set({ state: "rejected", updatedAt: now })
290
+ .where(eq(schema.contentDatabaseSourceChangeSets.id, changeSet.id));
291
+ await tx
292
+ .update(schema.contentDatabaseSources)
293
+ .set({ updatedAt: now })
294
+ .where(eq(schema.contentDatabaseSources.id, source.id));
295
+ });
296
+
297
+ return {
298
+ ...(await getContentDatabaseResponse(database.id)),
299
+ cancellation: {
300
+ sourceId: args.sourceId,
301
+ changeSetId: args.changeSetId,
302
+ executionIds,
303
+ status,
304
+ cancelledAt: now,
305
+ cancelledBy: actor,
306
+ },
307
+ };
308
+ },
309
+ });