@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.
Files changed (237) 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/artifact-response.ts +255 -12
  16. package/corpus/core/src/agent/thread-data-builder.ts +84 -11
  17. package/corpus/core/src/client/review/ReviewCommentComposer.tsx +9 -1
  18. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +17 -2
  19. package/corpus/core/src/client/settings/SettingsPanel.tsx +41 -21
  20. package/corpus/core/src/client/settings/useBuilderStatus.ts +28 -9
  21. package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +2 -7
  22. package/corpus/core/src/integrations/a2a-continuation-processor.ts +99 -7
  23. package/corpus/core/src/integrations/adapters/discord.ts +3 -1
  24. package/corpus/core/src/integrations/adapters/email.ts +4 -1
  25. package/corpus/core/src/integrations/adapters/google-docs.ts +4 -1
  26. package/corpus/core/src/integrations/adapters/microsoft-teams.ts +3 -1
  27. package/corpus/core/src/integrations/adapters/slack.ts +38 -9
  28. package/corpus/core/src/integrations/adapters/telegram.ts +32 -10
  29. package/corpus/core/src/integrations/adapters/whatsapp.ts +5 -1
  30. package/corpus/core/src/integrations/index.ts +1 -0
  31. package/corpus/core/src/integrations/types.ts +9 -2
  32. package/corpus/core/src/integrations/webhook-handler.ts +69 -45
  33. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +1 -0
  34. package/corpus/core/src/server/auth.ts +25 -1
  35. package/corpus/core/src/server/builder-browser.ts +353 -1
  36. package/corpus/core/src/server/core-routes-plugin.ts +601 -223
  37. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +80 -1
  38. package/corpus/templates/calendar/app/components/calendar/EventCard.tsx +28 -3
  39. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +5 -1
  40. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +30 -3
  41. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +196 -0
  42. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +115 -1
  43. package/corpus/templates/calendar/app/lib/out-of-office.ts +45 -0
  44. package/corpus/templates/calendar/changelog/2026-07-14-out-of-office-blocks-now-sit-behind-meetings-and-event-stack.md +6 -0
  45. package/corpus/templates/content/.agents/skills/content/SKILL.md +31 -0
  46. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +35 -21
  47. package/corpus/templates/content/AGENTS.md +32 -9
  48. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +87 -0
  49. package/corpus/templates/content/actions/_builder-cms-read-client.ts +173 -25
  50. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  51. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +427 -18
  52. package/corpus/templates/content/actions/_builder-cms-write-client.ts +17 -73
  53. package/corpus/templates/content/actions/_builder-cms-write-settings.ts +27 -7
  54. package/corpus/templates/content/actions/_builder-source-execution-claim.ts +124 -0
  55. package/corpus/templates/content/actions/_builder-source-execution-preservation.ts +21 -0
  56. package/corpus/templates/content/actions/_builder-source-timings.ts +57 -0
  57. package/corpus/templates/content/actions/_database-source-utils.ts +1071 -170
  58. package/corpus/templates/content/actions/_database-utils.ts +104 -13
  59. package/corpus/templates/content/actions/_local-file-documents.ts +53 -1
  60. package/corpus/templates/content/actions/_preview-document-draft.ts +31 -0
  61. package/corpus/templates/content/actions/_property-utils.ts +7 -1
  62. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +309 -0
  63. package/corpus/templates/content/actions/configure-document-property.ts +11 -0
  64. package/corpus/templates/content/actions/create-content-database.ts +11 -0
  65. package/corpus/templates/content/actions/create-document.ts +9 -0
  66. package/corpus/templates/content/actions/create-inline-content-database.ts +3 -0
  67. package/corpus/templates/content/actions/execute-builder-source-batch.ts +120 -49
  68. package/corpus/templates/content/actions/execute-builder-source-execution.ts +608 -264
  69. package/corpus/templates/content/actions/export-content-source.ts +1 -0
  70. package/corpus/templates/content/actions/get-content-database.ts +13 -1
  71. package/corpus/templates/content/actions/get-document.ts +12 -2
  72. package/corpus/templates/content/actions/get-preview-document-draft.ts +26 -0
  73. package/corpus/templates/content/actions/import-content-source.ts +6 -0
  74. package/corpus/templates/content/actions/list-documents.ts +3 -0
  75. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +550 -0
  76. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +112 -65
  77. package/corpus/templates/content/actions/prepare-builder-source-review.ts +463 -170
  78. package/corpus/templates/content/actions/preview-builder-source-review.ts +99 -0
  79. package/corpus/templates/content/actions/pull-document.ts +2 -0
  80. package/corpus/templates/content/actions/search-documents.ts +5 -1
  81. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +2 -2
  82. package/corpus/templates/content/actions/update-document.ts +16 -1
  83. package/corpus/templates/content/actions/update-preview-document-draft.ts +153 -0
  84. package/corpus/templates/content/actions/view-screen.ts +15 -2
  85. package/corpus/templates/content/app/components/editor/DescriptionField.tsx +121 -0
  86. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +14 -0
  87. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +250 -49
  88. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +57 -5
  89. package/corpus/templates/content/app/components/editor/body-hydration.ts +27 -0
  90. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2414 -589
  91. package/corpus/templates/content/app/components/editor/database/settings.tsx +13 -10
  92. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +358 -75
  93. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +124 -29
  94. package/corpus/templates/content/app/components/editor/previewDocumentSaveRegistry.ts +2 -0
  95. package/corpus/templates/content/app/global.css +18 -1
  96. package/corpus/templates/content/app/hooks/use-content-database.ts +68 -0
  97. package/corpus/templates/content/app/hooks/use-documents.ts +53 -4
  98. package/corpus/templates/content/app/i18n/zh-TW.ts +30 -0
  99. package/corpus/templates/content/app/i18n-data.ts +339 -0
  100. package/corpus/templates/content/changelog/2026-07-10-builder-backed-preview-edits-are-preserved-when-article-bodi.md +6 -0
  101. package/corpus/templates/content/changelog/2026-07-10-builder-cms-sources-can-now-be-enabled-for-guarded-staged-or.md +6 -0
  102. package/corpus/templates/content/changelog/2026-07-13-builder-connection-setup-is-now-available-directly-from-conn.md +6 -0
  103. package/corpus/templates/content/changelog/2026-07-13-builder-reviews-now-hide-already-applied-writes-detect-nativ.md +6 -0
  104. package/corpus/templates/content/changelog/2026-07-13-required-builder-publishing-fields-can-now-be-added-directly.md +6 -0
  105. package/corpus/templates/content/changelog/2026-07-13-you-can-now-cancel-a-prepared-builder-update-safely-before-i.md +6 -0
  106. package/corpus/templates/content/changelog/2026-07-14-builder-account-connection-now-remains-available-when-branch.md +6 -0
  107. package/corpus/templates/content/changelog/2026-07-14-builder-backed-articles-now-preserve-fifth-and-sixth-level-h.md +6 -0
  108. package/corpus/templates/content/changelog/2026-07-14-builder-sources-now-review-and-publish-rich-article-updates-.md +6 -0
  109. package/corpus/templates/content/changelog/2026-07-14-builder-writes-now-stop-retries-when-reconciliation-is-needed.md +6 -0
  110. package/corpus/templates/content/changelog/2026-07-14-file-and-media-links-now-validate-before-saving.md +6 -0
  111. package/corpus/templates/content/changelog/2026-07-14-pages-and-databases-can-describe-themselves-with-guidance-fo.md +6 -0
  112. package/corpus/templates/content/changelog/2026-07-14-slack-follow-ups-now-retain-created-content-identity-and-per.md +6 -0
  113. package/corpus/templates/content/parity/matrix.md +27 -25
  114. package/corpus/templates/content/parity/matrix.ts +50 -2
  115. package/corpus/templates/content/server/db/schema.ts +51 -0
  116. package/corpus/templates/content/server/lib/document-context.ts +76 -0
  117. package/corpus/templates/content/server/lib/public-documents.ts +3 -0
  118. package/corpus/templates/content/server/plugins/auth.ts +1 -0
  119. package/corpus/templates/content/server/plugins/db.ts +59 -0
  120. package/corpus/templates/content/server/routes/api/document-agent-context.json.get.ts +5 -0
  121. package/corpus/templates/content/shared/api.ts +87 -1
  122. package/corpus/templates/content/shared/builder-mdx.ts +705 -15
  123. package/corpus/templates/content/shared/content-source.ts +7 -0
  124. package/corpus/templates/content/shared/nfm.ts +3 -3
  125. package/corpus/templates/content/shared/properties.ts +2 -0
  126. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +52 -7
  127. package/corpus/templates/design/app/components/design/ReviewCommentsPanel.tsx +36 -4
  128. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +4 -1
  129. package/corpus/templates/design/app/i18n/ar-SA.ts +1 -0
  130. package/corpus/templates/design/app/i18n/de-DE.ts +1 -0
  131. package/corpus/templates/design/app/i18n/en-US.ts +1 -0
  132. package/corpus/templates/design/app/i18n/es-ES.ts +1 -0
  133. package/corpus/templates/design/app/i18n/fr-FR.ts +1 -0
  134. package/corpus/templates/design/app/i18n/hi-IN.ts +1 -0
  135. package/corpus/templates/design/app/i18n/ja-JP.ts +1 -0
  136. package/corpus/templates/design/app/i18n/ko-KR.ts +1 -0
  137. package/corpus/templates/design/app/i18n/pt-BR.ts +1 -0
  138. package/corpus/templates/design/app/i18n/zh-CN.ts +1 -0
  139. package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
  140. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +8 -0
  141. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +11 -0
  142. package/corpus/templates/design/changelog/2026-07-14-reviewers-can-now-pin-comments-from-the-canvas-toolbar-targe.md +6 -0
  143. package/corpus/templates/design/changelog/2026-07-15-viewer-comment-controls-now-sit-inside-the-read-only-notice-.md +6 -0
  144. package/corpus/templates/design/shared/review-anchor.ts +48 -0
  145. package/corpus/templates/plan/server/plan-mdx.ts +53 -7
  146. package/dist/a2a/artifact-response.d.ts +15 -0
  147. package/dist/a2a/artifact-response.d.ts.map +1 -1
  148. package/dist/a2a/artifact-response.js +204 -5
  149. package/dist/a2a/artifact-response.js.map +1 -1
  150. package/dist/agent/thread-data-builder.d.ts +6 -0
  151. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  152. package/dist/agent/thread-data-builder.js +71 -8
  153. package/dist/agent/thread-data-builder.js.map +1 -1
  154. package/dist/client/review/ReviewCommentComposer.d.ts +2 -1
  155. package/dist/client/review/ReviewCommentComposer.d.ts.map +1 -1
  156. package/dist/client/review/ReviewCommentComposer.js +3 -3
  157. package/dist/client/review/ReviewCommentComposer.js.map +1 -1
  158. package/dist/client/review/ReviewThreadPanel.d.ts +9 -1
  159. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -1
  160. package/dist/client/review/ReviewThreadPanel.js +6 -4
  161. package/dist/client/review/ReviewThreadPanel.js.map +1 -1
  162. package/dist/client/settings/SettingsPanel.d.ts +3 -0
  163. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  164. package/dist/client/settings/SettingsPanel.js +11 -4
  165. package/dist/client/settings/SettingsPanel.js.map +1 -1
  166. package/dist/client/settings/useBuilderStatus.d.ts +8 -4
  167. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  168. package/dist/client/settings/useBuilderStatus.js +19 -6
  169. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  170. package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -1
  171. package/dist/client/setup-connections/BuilderConnectCard.js +2 -5
  172. package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -1
  173. package/dist/collab/awareness.d.ts +2 -2
  174. package/dist/collab/awareness.d.ts.map +1 -1
  175. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  176. package/dist/integrations/a2a-continuation-processor.js +72 -6
  177. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  178. package/dist/integrations/adapters/discord.d.ts.map +1 -1
  179. package/dist/integrations/adapters/discord.js +1 -0
  180. package/dist/integrations/adapters/discord.js.map +1 -1
  181. package/dist/integrations/adapters/email.d.ts.map +1 -1
  182. package/dist/integrations/adapters/email.js +2 -0
  183. package/dist/integrations/adapters/email.js.map +1 -1
  184. package/dist/integrations/adapters/google-docs.d.ts.map +1 -1
  185. package/dist/integrations/adapters/google-docs.js +2 -0
  186. package/dist/integrations/adapters/google-docs.js.map +1 -1
  187. package/dist/integrations/adapters/microsoft-teams.d.ts.map +1 -1
  188. package/dist/integrations/adapters/microsoft-teams.js +1 -0
  189. package/dist/integrations/adapters/microsoft-teams.js.map +1 -1
  190. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  191. package/dist/integrations/adapters/slack.js +21 -6
  192. package/dist/integrations/adapters/slack.js.map +1 -1
  193. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  194. package/dist/integrations/adapters/telegram.js +11 -2
  195. package/dist/integrations/adapters/telegram.js.map +1 -1
  196. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  197. package/dist/integrations/adapters/whatsapp.js +3 -0
  198. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  199. package/dist/integrations/index.d.ts +1 -1
  200. package/dist/integrations/index.d.ts.map +1 -1
  201. package/dist/integrations/index.js.map +1 -1
  202. package/dist/integrations/types.d.ts +8 -2
  203. package/dist/integrations/types.d.ts.map +1 -1
  204. package/dist/integrations/types.js.map +1 -1
  205. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  206. package/dist/integrations/webhook-handler.js +40 -43
  207. package/dist/integrations/webhook-handler.js.map +1 -1
  208. package/dist/observability/routes.d.ts +5 -5
  209. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  210. package/dist/secrets/routes.d.ts +9 -9
  211. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  212. package/dist/server/agent-chat/action-filters-a2a.js +1 -0
  213. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  214. package/dist/server/auth.d.ts +1 -0
  215. package/dist/server/auth.d.ts.map +1 -1
  216. package/dist/server/auth.js +19 -2
  217. package/dist/server/auth.js.map +1 -1
  218. package/dist/server/builder-browser.d.ts +78 -0
  219. package/dist/server/builder-browser.d.ts.map +1 -1
  220. package/dist/server/builder-browser.js +246 -1
  221. package/dist/server/builder-browser.js.map +1 -1
  222. package/dist/server/core-routes-plugin.d.ts +41 -0
  223. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  224. package/dist/server/core-routes-plugin.js +260 -14
  225. package/dist/server/core-routes-plugin.js.map +1 -1
  226. package/docs/content/deployment.mdx +9 -7
  227. package/docs/content/locales/ar-SA/deployment.mdx +9 -7
  228. package/docs/content/locales/de-DE/deployment.mdx +9 -7
  229. package/docs/content/locales/es-ES/deployment.mdx +9 -7
  230. package/docs/content/locales/fr-FR/deployment.mdx +9 -7
  231. package/docs/content/locales/hi-IN/deployment.mdx +9 -7
  232. package/docs/content/locales/ja-JP/deployment.mdx +9 -7
  233. package/docs/content/locales/ko-KR/deployment.mdx +9 -7
  234. package/docs/content/locales/pt-BR/deployment.mdx +9 -7
  235. package/docs/content/locales/zh-CN/deployment.mdx +9 -7
  236. package/docs/content/locales/zh-TW/deployment.mdx +9 -7
  237. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
2
2
 
3
3
  import { getDb, schema } from "../server/db/index.js";
4
+ import { getDocumentContextPath } from "../server/lib/document-context.js";
4
5
  import {
5
6
  parseDocumentFavorite,
6
7
  parseDocumentHideFromSearch,
@@ -20,6 +21,7 @@ import {
20
21
  listPropertiesForDatabase,
21
22
  serializeDatabase,
22
23
  } from "./_property-utils.js";
24
+ export { getDocumentContextPath };
23
25
 
24
26
  export const CONTENT_DATABASE_MAX_READ_LIMIT = 5_000;
25
27
 
@@ -34,6 +36,32 @@ type DatabaseMembershipRow = {
34
36
  bodyHydrationQueueId?: string | null;
35
37
  };
36
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
+
37
65
  export function serializeBodyHydration(
38
66
  item: typeof schema.contentDatabaseItems.$inferSelect,
39
67
  options: { queued?: boolean } = {},
@@ -44,6 +72,7 @@ export function serializeBodyHydration(
44
72
  status === "pending" ||
45
73
  status === "hydrating" ||
46
74
  status === "hydrated" ||
75
+ status === "unavailable" ||
47
76
  status === "error"
48
77
  ? status
49
78
  : options.queued
@@ -121,16 +150,62 @@ export function normalizeContentDatabasePageOptions(options: {
121
150
  return { limit, offset };
122
151
  }
123
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
+
124
197
  function serializeDocument(
125
- doc: typeof schema.documents.$inferSelect,
198
+ doc: DocumentListRow,
126
199
  membership?: DatabaseMembershipRow,
127
- options: { includeContent?: boolean } = {},
128
200
  ) {
129
201
  return {
130
202
  id: doc.id,
131
203
  parentId: doc.parentId,
132
204
  title: doc.title,
133
- content: options.includeContent === true ? doc.content : "",
205
+ // List reads deliberately project no `documents.content`; opened documents
206
+ // use their dedicated read path.
207
+ content: "",
208
+ description: doc.description,
134
209
  icon: doc.icon,
135
210
  position: doc.position,
136
211
  isFavorite: parseDocumentFavorite(doc.isFavorite),
@@ -160,6 +235,14 @@ export async function getContentDatabaseResponse(
160
235
  if (!database || database.deletedAt) {
161
236
  throw new Error(`Database "${databaseId}" not found`);
162
237
  }
238
+ const [databaseDocument] = await db
239
+ .select({
240
+ id: schema.documents.id,
241
+ parentId: schema.documents.parentId,
242
+ description: schema.documents.description,
243
+ })
244
+ .from(schema.documents)
245
+ .where(eq(schema.documents.id, database.documentId));
163
246
 
164
247
  // PURE read: the primary "Content" Blocks field is seeded at create time and
165
248
  // by the one-time startup repair — never here. Reading a database (including a
@@ -185,7 +268,7 @@ export async function getContentDatabaseResponse(
185
268
  const documents =
186
269
  items.length > 0
187
270
  ? await db
188
- .select()
271
+ .select(contentDatabaseListDocumentSelection)
189
272
  .from(schema.documents)
190
273
  .where(
191
274
  and(
@@ -200,7 +283,9 @@ export async function getContentDatabaseResponse(
200
283
  const documentById = new Map(documents.map((doc) => [doc.id, doc]));
201
284
  const propertiesByDocumentId = await listPropertiesForDatabaseDocuments(
202
285
  databaseId,
203
- 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>,
204
289
  );
205
290
  const queuedBodyHydrationItemIds =
206
291
  items.length > 0
@@ -248,17 +333,20 @@ export async function getContentDatabaseResponse(
248
333
  serializedItems.map((item) => item.document.id),
249
334
  );
250
335
  // When paginating, scope every DOCUMENT-BACKED source's rows to the visible
251
- // page that's the primary AND any row-union secondary (each row maps to a
252
- // real document). Federated join rows carry no document (empty documentId),
253
- // 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.
254
341
  const pagedSources =
255
342
  limit !== null
256
343
  ? sources.map((source) => ({
257
344
  ...source,
258
- rows: source.rows.filter(
259
- (row) =>
260
- !row.documentId || serializedDocumentIds.has(row.documentId),
261
- ),
345
+ rows: filterContentDatabaseSourceRowsForPage({
346
+ rows: source.rows,
347
+ changeSets: source.changeSets,
348
+ visibleDocumentIds: serializedDocumentIds,
349
+ }),
262
350
  }))
263
351
  : sources;
264
352
  const pagedPrimary = pagedSources[0] ?? null;
@@ -272,7 +360,10 @@ export async function getContentDatabaseResponse(
272
360
  const itemsWithOverlay = applyFederatedOverlayValues(federatedItems);
273
361
 
274
362
  return {
275
- database: serializeDatabase(database),
363
+ database: serializeDatabase(database, databaseDocument?.description ?? ""),
364
+ contextPath: databaseDocument
365
+ ? await getDocumentContextPath(databaseDocument)
366
+ : [],
276
367
  properties: await listPropertiesForDatabase(databaseId),
277
368
  items: itemsWithOverlay,
278
369
  source: pagedPrimary,
@@ -154,6 +154,7 @@ function documentFromFolder(folderPath: string, position: number): Document {
154
154
  parentId: folderParentId(folderPath),
155
155
  title: titleFromSegment(basename(folderPath)),
156
156
  content: "",
157
+ description: "",
157
158
  icon: null,
158
159
  position,
159
160
  isFavorite: false,
@@ -189,6 +190,7 @@ function documentFromLocalFile(
189
190
  parentId: parentFolderId(file.path),
190
191
  title: parsed.title,
191
192
  content: parsed.content,
193
+ description: parsed.description ?? "",
192
194
  icon: parsed.icon ?? null,
193
195
  position,
194
196
  isFavorite: parsed.isFavorite ?? false,
@@ -410,6 +412,33 @@ export async function getLocalFileDocument(id: string): Promise<Document> {
410
412
  return documentFromLocalFile(file, file.content, 0);
411
413
  }
412
414
 
415
+ export async function getLocalDocumentContextPath(id: string) {
416
+ const documents = await listLocalFileDocuments();
417
+ const byId = new Map(documents.map((document) => [document.id, document]));
418
+ const current = byId.get(id) ?? (await getLocalFileDocument(id));
419
+ const path: Array<{
420
+ id: string;
421
+ kind: "page" | "database";
422
+ title: string;
423
+ description: string;
424
+ }> = [];
425
+ const seen = new Set([current.id]);
426
+ let parentId = current.parentId;
427
+ while (parentId && !seen.has(parentId)) {
428
+ seen.add(parentId);
429
+ const parent = byId.get(parentId);
430
+ if (!parent) break;
431
+ path.unshift({
432
+ id: parent.id,
433
+ kind: "page",
434
+ title: parent.title,
435
+ description: parent.description ?? "",
436
+ });
437
+ parentId = parent.parentId;
438
+ }
439
+ return path;
440
+ }
441
+
413
442
  function splitFrontmatter(source: string) {
414
443
  const match = source.match(FRONTMATTER_RE);
415
444
  if (!match) return { frontmatter: "", body: source };
@@ -517,12 +546,16 @@ function updateFrontmatterFields(
517
546
  titleChanged: boolean,
518
547
  iconChanged: boolean,
519
548
  favoriteChanged: boolean,
549
+ descriptionChanged: boolean,
520
550
  ) {
521
551
  if (usesDocsNoBookkeepingProfile(file.profile)) {
522
552
  return {
523
553
  ...(titleChanged ? { title: nextTitle || "Untitled" } : {}),
524
554
  ...(iconChanged ? { icon: args.icon ?? null } : {}),
525
555
  ...(favoriteChanged ? { isFavorite: args.isFavorite ?? false } : {}),
556
+ ...(descriptionChanged
557
+ ? { description: args.description?.trim() ?? "" }
558
+ : {}),
526
559
  };
527
560
  }
528
561
 
@@ -531,6 +564,10 @@ function updateFrontmatterFields(
531
564
  icon: args.icon !== undefined ? args.icon : current.icon,
532
565
  isFavorite:
533
566
  args.isFavorite !== undefined ? args.isFavorite : current.isFavorite,
567
+ description:
568
+ args.description !== undefined
569
+ ? args.description.trim()
570
+ : current.description,
534
571
  updatedAt: new Date().toISOString(),
535
572
  };
536
573
  }
@@ -544,6 +581,9 @@ function createFrontmatterFields(
544
581
  return {
545
582
  title,
546
583
  ...(args.icon !== undefined ? { icon: args.icon || null } : {}),
584
+ ...(args.description !== undefined
585
+ ? { description: args.description.trim() }
586
+ : {}),
547
587
  };
548
588
  }
549
589
 
@@ -551,6 +591,7 @@ function createFrontmatterFields(
551
591
  title,
552
592
  icon: args.icon || null,
553
593
  isFavorite: false,
594
+ description: args.description?.trim() ?? "",
554
595
  updatedAt: new Date().toISOString(),
555
596
  };
556
597
  }
@@ -576,8 +617,17 @@ export async function updateLocalFileDocument(
576
617
  const iconChanged = args.icon !== undefined && args.icon !== current.icon;
577
618
  const favoriteChanged =
578
619
  args.isFavorite !== undefined && args.isFavorite !== current.isFavorite;
620
+ const descriptionChanged =
621
+ args.description !== undefined &&
622
+ args.description.trim() !== current.description;
579
623
 
580
- if (!titleChanged && !contentChanged && !iconChanged && !favoriteChanged) {
624
+ if (
625
+ !titleChanged &&
626
+ !contentChanged &&
627
+ !iconChanged &&
628
+ !favoriteChanged &&
629
+ !descriptionChanged
630
+ ) {
581
631
  return current;
582
632
  }
583
633
 
@@ -591,6 +641,7 @@ export async function updateLocalFileDocument(
591
641
  titleChanged,
592
642
  iconChanged,
593
643
  favoriteChanged,
644
+ descriptionChanged,
594
645
  ),
595
646
  nextContent,
596
647
  );
@@ -712,6 +763,7 @@ export async function localContentViewScreenSummary() {
712
763
  id: document.id,
713
764
  parentId: document.parentId,
714
765
  title: document.title,
766
+ description: document.description,
715
767
  source: document.source,
716
768
  })),
717
769
  };
@@ -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
+ }
@@ -151,11 +151,15 @@ export async function getDatabaseById(
151
151
  return database ?? null;
152
152
  }
153
153
 
154
- export function serializeDatabase(database: ContentDatabaseRow) {
154
+ export function serializeDatabase(
155
+ database: ContentDatabaseRow,
156
+ description = "",
157
+ ) {
155
158
  return {
156
159
  id: database.id,
157
160
  documentId: database.documentId,
158
161
  title: database.title,
162
+ description,
159
163
  viewConfig: parseDatabaseViewConfig(database.viewConfigJson),
160
164
  createdAt: database.createdAt,
161
165
  updatedAt: database.updatedAt,
@@ -500,6 +504,7 @@ export async function listPropertiesForDatabase(
500
504
  databaseId: definition.databaseId,
501
505
  name: definition.name,
502
506
  type,
507
+ description: definition.description,
503
508
  visibility: normalizePropertyVisibility(definition.visibility),
504
509
  options,
505
510
  position: definition.position,
@@ -568,6 +573,7 @@ function serializePropertyDefinition(
568
573
  databaseId: definition.databaseId,
569
574
  name: definition.name,
570
575
  type,
576
+ description: definition.description,
571
577
  visibility: normalizePropertyVisibility(definition.visibility),
572
578
  options: parsePropertyOptions(definition.optionsJson),
573
579
  position: definition.position,
@@ -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
+ });