@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
@@ -43,6 +43,11 @@ import {
43
43
  partitionAllDayEvents,
44
44
  } from "@/lib/all-day-layout";
45
45
  import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
46
+ import {
47
+ getFirstVisibleOutOfOfficeDayIndex,
48
+ getOutOfOfficeSegment,
49
+ isOutOfOfficeEvent,
50
+ } from "@/lib/out-of-office";
46
51
  import {
47
52
  shouldSuppressAfterPopoverClose,
48
53
  shouldSuppressCreatePointerDown,
@@ -56,6 +61,7 @@ import {
56
61
  } from "@/lib/working-location";
57
62
 
58
63
  import { EventDetailPopover } from "./EventDetailPopover";
64
+ import { OutOfOfficeEvent } from "./OutOfOfficeEvent";
59
65
  import { shouldRenderWeekDragSegment } from "./week-drag-segment";
60
66
 
61
67
  interface WeekViewProps {
@@ -285,6 +291,7 @@ interface WeekEventCardProps {
285
291
  onDraftUpdate?: WeekViewProps["onDraftUpdate"];
286
292
  onDraftCreate?: WeekViewProps["onDraftCreate"];
287
293
  onDraftDiscard?: WeekViewProps["onDraftDiscard"];
294
+ onPopoverOpenChange: (event: CalendarEvent, open: boolean) => void;
288
295
  }
289
296
 
290
297
  /**
@@ -320,6 +327,7 @@ const WeekEventCard = memo(function WeekEventCard({
320
327
  onDraftUpdate,
321
328
  onDraftCreate,
322
329
  onDraftDiscard,
330
+ onPopoverOpenChange,
323
331
  }: WeekEventCardProps) {
324
332
  const t = useT();
325
333
  const li = layout.get(event.id) ?? {
@@ -558,6 +566,7 @@ const WeekEventCard = memo(function WeekEventCard({
558
566
  onDraftUpdate={onDraftUpdate}
559
567
  onDraftCreate={onDraftCreate}
560
568
  onDraftDiscard={onDraftDiscard}
569
+ onOpenChange={(open) => onPopoverOpenChange(event, open)}
561
570
  >
562
571
  {eventButton}
563
572
  </EventDetailPopover>
@@ -618,6 +627,7 @@ export const WeekView = memo(function WeekView({
618
627
  const GUTTER_WIDTH = isMobile ? MOBILE_GUTTER_WIDTH : DESKTOP_GUTTER_WIDTH;
619
628
  const [now, setNow] = useState(new Date());
620
629
  const [focusedEventId, setFocusedEventId] = useState<string | null>(null);
630
+ const focusedEventIdRef = useRef<string | null>(null);
621
631
  const currentTimeRef = useRef<HTMLDivElement>(null);
622
632
  const scrollContainerRef = useRef<HTMLDivElement>(null);
623
633
  const allDayContainerRef = useRef<HTMLDivElement>(null);
@@ -628,6 +638,7 @@ export const WeekView = memo(function WeekView({
628
638
  useEffect(() => {
629
639
  function handleKey(e: KeyboardEvent) {
630
640
  if (e.key === "Escape") {
641
+ focusedEventIdRef.current = null;
631
642
  setFocusedEventId(null);
632
643
  setFocusedEvent(null);
633
644
  }
@@ -675,7 +686,15 @@ export const WeekView = memo(function WeekView({
675
686
  // Separate all-day and timed events
676
687
  const allDayEvents = useMemo(() => events.filter((e) => e.allDay), [events]);
677
688
 
678
- const timedEvents = useMemo(() => events.filter((e) => !e.allDay), [events]);
689
+ const outOfOfficeEvents = useMemo(
690
+ () => events.filter((event) => !event.allDay && isOutOfOfficeEvent(event)),
691
+ [events],
692
+ );
693
+
694
+ const timedEvents = useMemo(
695
+ () => events.filter((event) => !event.allDay && !isOutOfOfficeEvent(event)),
696
+ [events],
697
+ );
679
698
 
680
699
  const { workingLocations, regularEvents } = useMemo(
681
700
  () => partitionAllDayEvents(allDayEvents),
@@ -849,6 +868,22 @@ export const WeekView = memo(function WeekView({
849
868
 
850
869
  const canDrag = !!onEventTimeChange;
851
870
 
871
+ const handleEventPopoverOpenChange = useCallback(
872
+ (event: CalendarEvent, open: boolean) => {
873
+ if (open) {
874
+ focusedEventIdRef.current = event.id;
875
+ setFocusedEventId(event.id);
876
+ setFocusedEvent(event);
877
+ return;
878
+ }
879
+ if (focusedEventIdRef.current !== event.id) return;
880
+ focusedEventIdRef.current = null;
881
+ setFocusedEventId(null);
882
+ setFocusedEvent(null);
883
+ },
884
+ [setFocusedEvent],
885
+ );
886
+
852
887
  const handleEventPointerDown = useCallback(
853
888
  (
854
889
  e: React.PointerEvent,
@@ -856,6 +891,7 @@ export const WeekView = memo(function WeekView({
856
891
  isStart: boolean,
857
892
  dayIndex: number,
858
893
  ) => {
894
+ focusedEventIdRef.current = event.id;
859
895
  setFocusedEventId(event.id);
860
896
  setFocusedEvent(event);
861
897
  if (
@@ -1262,6 +1298,16 @@ export const WeekView = memo(function WeekView({
1262
1298
  }
1263
1299
  }
1264
1300
 
1301
+ const visibleOutOfOfficeEvents = outOfOfficeEvents.filter(
1302
+ (event) => {
1303
+ const overrides = getDragOverrides(event.id);
1304
+ return (
1305
+ getOutOfOfficeSegment(event, day) !== null ||
1306
+ (dragEventId === event.id && overrides?.dayIndex === dayIndex)
1307
+ );
1308
+ },
1309
+ );
1310
+
1265
1311
  return (
1266
1312
  <div
1267
1313
  key={day.toISOString()}
@@ -1338,6 +1384,73 @@ export const WeekView = memo(function WeekView({
1338
1384
  </div>
1339
1385
  )}
1340
1386
 
1387
+ {/* Native Google out-of-office context sits behind meetings. */}
1388
+ {!isLoading &&
1389
+ visibleOutOfOfficeEvents.map((event, markerIndex) => {
1390
+ const isBeingDragged = dragEventId === event.id;
1391
+ const overrides = getDragOverrides(event.id);
1392
+ const canonicalDayIndex =
1393
+ getFirstVisibleOutOfOfficeDayIndex(event, days);
1394
+ return (
1395
+ <OutOfOfficeEvent
1396
+ key={`${event._tempId ?? event.id}:${day.toISOString()}`}
1397
+ event={event}
1398
+ day={day}
1399
+ hourHeight={HOUR_HEIGHT}
1400
+ color={
1401
+ getEventDisplayColor(event, prefs) ??
1402
+ "hsl(var(--primary))"
1403
+ }
1404
+ label={t("eventForm.outOfOffice")}
1405
+ markerIndex={markerIndex}
1406
+ compactMarker
1407
+ canDrag={canDrag}
1408
+ isBeingDragged={isBeingDragged}
1409
+ isDragging={isDragging}
1410
+ isDragTargetDay={overrides?.dayIndex === dayIndex}
1411
+ overrideTop={overrides?.top ?? null}
1412
+ overrideHeight={overrides?.height ?? null}
1413
+ onMovePointerDown={(pointerEvent, startsOnDay) =>
1414
+ handleEventPointerDown(
1415
+ pointerEvent,
1416
+ event,
1417
+ startsOnDay,
1418
+ dayIndex,
1419
+ )
1420
+ }
1421
+ onResizeTopPointerDown={(pointerEvent) =>
1422
+ handleResizeTopPointerDown(
1423
+ pointerEvent,
1424
+ event.id,
1425
+ dayIndex,
1426
+ )
1427
+ }
1428
+ onResizeBottomPointerDown={(pointerEvent) =>
1429
+ handleResizeBottomPointerDown(
1430
+ pointerEvent,
1431
+ event.id,
1432
+ dayIndex,
1433
+ )
1434
+ }
1435
+ shouldSuppressClick={shouldSuppressClick}
1436
+ onDelete={onDeleteEvent}
1437
+ isDraft={draftEventIds.includes(event.id)}
1438
+ defaultOpen={
1439
+ quickEditEventId === event.id &&
1440
+ dayIndex === canonicalDayIndex
1441
+ }
1442
+ onTitleSave={onQuickEditSave}
1443
+ onDismissNew={onQuickEditCancel}
1444
+ onDraftUpdate={onDraftUpdate}
1445
+ onDraftCreate={onDraftCreate}
1446
+ onDraftDiscard={onDraftDiscard}
1447
+ onOpenChange={(open) =>
1448
+ handleEventPopoverOpenChange(event, open)
1449
+ }
1450
+ />
1451
+ );
1452
+ })}
1453
+
1341
1454
  {/* Skeleton events when loading */}
1342
1455
  {isLoading &&
1343
1456
  WEEK_SKELETONS[dayIndex]?.map(
@@ -1402,6 +1515,7 @@ export const WeekView = memo(function WeekView({
1402
1515
  onDraftUpdate={onDraftUpdate}
1403
1516
  onDraftCreate={onDraftCreate}
1404
1517
  onDraftDiscard={onDraftDiscard}
1518
+ onPopoverOpenChange={handleEventPopoverOpenChange}
1405
1519
  />
1406
1520
  );
1407
1521
  })}
@@ -0,0 +1,45 @@
1
+ import type { CalendarEvent } from "@shared/api";
2
+ import { addDays, differenceInMinutes, parseISO, startOfDay } from "date-fns";
3
+
4
+ export interface OutOfOfficeSegment {
5
+ topMinutes: number;
6
+ durationMinutes: number;
7
+ startsOnDay: boolean;
8
+ endsOnDay: boolean;
9
+ }
10
+
11
+ export function isOutOfOfficeEvent(
12
+ event: Pick<CalendarEvent, "eventType">,
13
+ ): boolean {
14
+ return event.eventType === "outOfOffice";
15
+ }
16
+
17
+ /** Return the portion of a timed out-of-office event visible on one day. */
18
+ export function getOutOfOfficeSegment(
19
+ event: Pick<CalendarEvent, "start" | "end">,
20
+ day: Date,
21
+ ): OutOfOfficeSegment | null {
22
+ const eventStart = parseISO(event.start);
23
+ const eventEnd = parseISO(event.end);
24
+ const dayStart = startOfDay(day);
25
+ const dayEnd = addDays(dayStart, 1);
26
+
27
+ if (eventStart >= dayEnd || eventEnd <= dayStart) return null;
28
+
29
+ const segmentStart = eventStart > dayStart ? eventStart : dayStart;
30
+ const segmentEnd = eventEnd < dayEnd ? eventEnd : dayEnd;
31
+
32
+ return {
33
+ topMinutes: Math.max(0, differenceInMinutes(segmentStart, dayStart)),
34
+ durationMinutes: Math.max(1, differenceInMinutes(segmentEnd, segmentStart)),
35
+ startsOnDay: eventStart >= dayStart && eventStart < dayEnd,
36
+ endsOnDay: eventEnd > dayStart && eventEnd <= dayEnd,
37
+ };
38
+ }
39
+
40
+ export function getFirstVisibleOutOfOfficeDayIndex(
41
+ event: Pick<CalendarEvent, "start" | "end">,
42
+ days: Date[],
43
+ ): number {
44
+ return days.findIndex((day) => getOutOfOfficeSegment(event, day) !== null);
45
+ }
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-07-14
4
+ ---
5
+
6
+ Out-of-office blocks now sit behind meetings, and event stacking resets after closing details.
@@ -101,6 +101,37 @@ inferences for confirmation, then submit once. When required information is
101
101
  missing, keep the clarification in the originating thread and retain earlier
102
102
  answers as context.
103
103
 
104
+ ### Slack Follow-ups And Corrections
105
+
106
+ A follow-up in an existing Slack thread is not automatically a new intake. Read
107
+ the thread context and inspect the prior Content artifact identity first,
108
+ including any returned document ID or `/page/<id>` path and the canonical
109
+ database row when available. Then choose the operation that matches the user's
110
+ intent:
111
+
112
+ - **Update** the same document for corrections, refinements, status changes, or
113
+ renames that still describe the same request. A rename changes the title, not
114
+ the artifact identity: preserve the stable Content document ID and page path.
115
+ - **Add** new details to the same document when the follow-up extends the
116
+ original request without replacing it.
117
+ - **Supersede** only when the user intends a replacement artifact or distinct
118
+ successor and the workspace's schema or instructions define how that
119
+ relationship is recorded. Preserve a concrete link to the prior artifact.
120
+ - **Create** only when the follow-up is genuinely a separate request or the user
121
+ explicitly asks for a new artifact. Do not blindly submit another row merely
122
+ because a new Slack message arrived.
123
+
124
+ Apply people fields from verified identity and intent, not from convenient
125
+ guesswork:
126
+
127
+ - When the database has a `Requester` field, default it to the verified Slack
128
+ sender unless the user explicitly identifies a different requester.
129
+ - A named doer such as "for Apoorva" maps to `Assignee` when that field exists.
130
+ Naming an assignee never changes or replaces `Requester`.
131
+ - Resolve named people to the database's accepted person identity before
132
+ writing. If a named person cannot be resolved unambiguously, clarify in the
133
+ originating Slack thread; never omit, downgrade, or silently drop the person.
134
+
104
135
  ## Local File Mode
105
136
 
106
137
  Install into an existing repo with:
@@ -7,7 +7,10 @@ description: >-
7
7
 
8
8
  # Document Editing
9
9
 
10
- Documents are stored in the SQL database via Drizzle ORM. Each document has a title, markdown content, optional parent (for nesting), and a position for ordering.
10
+ Documents are stored in the SQL database via Drizzle ORM. Each document has a
11
+ title, stable description, markdown content, optional parent (for nesting), and
12
+ a position for ordering. The description explains why the page exists and what
13
+ belongs there; it is not a changing synopsis of the current body.
11
14
 
12
15
  ## Scripts
13
16
 
@@ -48,6 +51,7 @@ Create a new document.
48
51
  pnpm action create-document --title "Meeting Notes" --content "# Meeting Notes\n\nAttendees: ..."
49
52
  pnpm action create-document --title "Sub Page" --parentId parent123
50
53
  pnpm action create-document --title "My Page" --icon "📝"
54
+ pnpm action create-document --title "Research" --description "Evidence and source notes that support the current project"
51
55
  ```
52
56
 
53
57
  ### edit-document
@@ -73,6 +77,7 @@ Update an existing document. Use for **full rewrites or new content**, not for s
73
77
  pnpm action update-document --id abc123 --title "New Title"
74
78
  pnpm action update-document --id abc123 --content "# Updated Content\n\nNew text here"
75
79
  pnpm action update-document --id abc123 --title "New Title" --content "New content"
80
+ pnpm action update-document --id abc123 --description "Stable guidance for what belongs on this page"
76
81
  ```
77
82
 
78
83
  ### delete-document
@@ -123,17 +128,18 @@ Always run this after any document modification to update the sidebar.
123
128
 
124
129
  ## Document Schema
125
130
 
126
- | Column | Type | Description |
127
- | ------------ | ------- | --------------------------------------- |
128
- | `id` | text | Primary key (12-char hex string) |
129
- | `parent_id` | text | Parent document ID (null for root) |
130
- | `title` | text | Document title (default: "Untitled") |
131
- | `content` | text | Markdown content |
132
- | `icon` | text | Emoji icon (optional) |
133
- | `position` | integer | Sort order within parent (0-based) |
134
- | `is_favorite`| integer | Whether document is favorited (0 or 1) |
135
- | `created_at` | text | ISO timestamp |
136
- | `updated_at` | text | ISO timestamp |
131
+ | Column | Type | Description |
132
+ | ------------- | ------- | -------------------------------------- |
133
+ | `id` | text | Primary key (12-char hex string) |
134
+ | `parent_id` | text | Parent document ID (null for root) |
135
+ | `title` | text | Document title (default: "Untitled") |
136
+ | `description` | text | Stable semantic guidance for the page |
137
+ | `content` | text | Markdown content |
138
+ | `icon` | text | Emoji icon (optional) |
139
+ | `position` | integer | Sort order within parent (0-based) |
140
+ | `is_favorite` | integer | Whether document is favorited (0 or 1) |
141
+ | `created_at` | text | ISO timestamp |
142
+ | `updated_at` | text | ISO timestamp |
137
143
 
138
144
  ## Content Format
139
145
 
@@ -142,21 +148,29 @@ Documents use **markdown** for content. The editor renders markdown in real time
142
148
  ## Parent-Child Hierarchy
143
149
 
144
150
  Documents form a tree via `parent_id`:
151
+
145
152
  - Root documents have `parent_id = null`
146
153
  - Child documents reference their parent's `id`
147
154
  - Deleting a parent recursively deletes all children
148
155
  - Position determines ordering within the same parent
149
156
 
157
+ Descriptions are owned; context is inherited. `get-document` and `view-screen`
158
+ return the focused page's own description plus a computed root-to-parent
159
+ `contextPath`. Use that path to understand where the page lives, but never copy
160
+ ancestor descriptions into the child. Database, property, and option
161
+ descriptions narrow the guidance further when working with structured values.
162
+
150
163
  ## Common Tasks
151
164
 
152
- | User says | What to do |
153
- | ----------------------------- | ---------------------------------------------------------------- |
154
- | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
155
- | "Find my meeting notes" | `search-documents --query "meeting notes"` |
156
- | "Fix a typo / edit a line" | `view-screen` to get ID, then `edit-document --id ... --find "old" --replace "new"` |
157
- | "Rewrite this document" | `view-screen` to get ID, then `update-document --id ... --content ...` |
158
- | "Delete this page" | `view-screen` to get ID, then `delete-document --id ...` |
159
- | "Add a sub-page" | `create-document --title "Sub" --parentId <parentId>` |
160
- | "Show me the document tree" | `list-documents` |
165
+ | User says | What to do |
166
+ | ---------------------------- | ----------------------------------------------------------------------------------- |
167
+ | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
168
+ | "Describe what belongs here" | `update-document --id ... --description "..."` |
169
+ | "Find my meeting notes" | `search-documents --query "meeting notes"` |
170
+ | "Fix a typo / edit a line" | `view-screen` to get ID, then `edit-document --id ... --find "old" --replace "new"` |
171
+ | "Rewrite this document" | `view-screen` to get ID, then `update-document --id ... --content ...` |
172
+ | "Delete this page" | `view-screen` to get ID, then `delete-document --id ...` |
173
+ | "Add a sub-page" | `create-document --title "Sub" --parentId <parentId>` |
174
+ | "Show me the document tree" | `list-documents` |
161
175
 
162
176
  Always run `refresh-list` after any create, update, or delete operation.
@@ -102,19 +102,19 @@ cd templates/content && pnpm action <name> [args]
102
102
  | `push-builder-doc` | `--files <json> [--path <file.builder.mdx>] [--dryRun true\|false]` | Guarded Builder autosave PATCH for the safe Builder test model; never publishes |
103
103
  | `navigate` | `--path <path>` or `--documentId <id>` or `--databaseId <id>` | Open a route, document page, or database page in the UI |
104
104
  | `search-documents` | `--query <text> [--format json]` | Search by title/content and return snippets |
105
- | `get-document` | `--id <id> [--format json]` | Get a single document with content |
105
+ | `get-document` | `--id <id> [--format json]` | Get a document with content, stable description, properties, and computed ancestry context |
106
106
  | `pull-document` | `--id <id> [--format markdown\|text]` | Collab-aware "ingest the final" read |
107
- | `create-document` | `--title <text> [--content] [--parentId] [--icon]` | Create a new document |
107
+ | `create-document` | `--title <text> [--content] [--description] [--parentId] [--icon]` | Create a document with optional stable guidance about why it exists and what belongs there |
108
108
  | `edit-document` | `--id <id> --find <text> --replace <text>` | Surgical text edit (preferred for modifications) |
109
109
  | `edit-document` | `--id <id> --edits <json>` | Batch surgical text edits |
110
- | `update-document` | `--id <id> [--title] [--content] [--icon]` | Full rewrite of document fields |
110
+ | `update-document` | `--id <id> [--title] [--content] [--description] [--icon]` | Update document fields; descriptions are stable guidance, not summaries of current content |
111
111
  | `share-local-file-document` | `--id <local-file-document-id>` | Create or refresh a DB-backed shareable copy of a local file document |
112
112
  | `remove-local-file-source` | `[--sourceRootPath <path>]` | Unlink local-file sources from Content without deleting local Markdown/MDX files |
113
113
  | `list-local-component-files` | | List registered local MDX component source files |
114
114
  | `write-local-component-file` | `--workspaceId <id> --path <relative-component-path> --content <source>` | Create or update a file in a registered local `components/` folder |
115
- | `create-content-database` | `[--documentId <id>] [--parentId <id>] [--title <text>]` | Create a database page or convert an existing page into a database |
116
- | `create-inline-content-database` | `--hostDocumentId <id> [--title <text>]` | Create a database owned by an inline database block in the host document |
117
- | `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database table with property schema and item pages |
115
+ | `create-content-database` | `[--documentId <id>] [--parentId <id>] [--title <text>] [--description <text>]` | Create a described database page or convert an existing page into a database |
116
+ | `create-inline-content-database` | `--hostDocumentId <id> [--title <text>] [--description <text>]` | Create a described database owned by an inline database block in the host document |
117
+ | `get-content-database` | `--databaseId <id>` or `--documentId <id>` | Get a database with its description, property/option schema guidance, item pages, and computed ancestry context |
118
118
  | `list-trashed-content-databases` | | List soft-deleted databases visible in the sidebar Trash surface |
119
119
  | `restore-content-database` | `--databaseId <id>` | Restore a soft-deleted database from the sidebar Trash surface |
120
120
  | `get-content-database-source` | `--databaseId <id>` or `--documentId <id>` | Inspect local/no-source or source-backed status, mappings, row identity, freshness, and change sets |
@@ -123,13 +123,14 @@ cd templates/content && pnpm action <name> [args]
123
123
  | `change-content-database-source-role` | `--databaseId <id>` or `--documentId <id> --sourceId <id> --relationshipMode items\|details [--join <json>]` | Change an attached source between adding rows and adding matched detail columns without removing the source |
124
124
  | `refresh-content-database-source` | `--databaseId <id>` or `--documentId <id>` | Refresh the read-only source status envelope; large Builder CMS sources may return a partial fetch, and another refresh continues loading remaining rows |
125
125
  | `process-builder-body-hydration` | `--sourceId <id> [--documentId <id>] [--limit <n>]` | Hydrate queued Builder article bodies into readable Content markdown with preserved source-component markers |
126
- | `set-content-database-source-write-mode` | `--databaseId <id>` or `--documentId <id> --liveWritesEnabled true\|false [--allowedWriteModes <json>]` | Enable/disable per-source Builder live writes; enabling is allowed only for `agent-native-blog-article-test` with explicit modes |
126
+ | `set-content-database-source-write-mode` | `--databaseId <id>` or `--documentId <id> [--sourceId <id>] --writeMode read_only\|stage_only\|publish_updates [--allowPublicationTransitions true\|false]` | Set the per-source Builder write tier; requires page admin access and remains read-only by default |
127
127
  | `stage-builder-revision` | `--databaseId <id>` or `--documentId <id>` | Stage pending local Builder CMS changes as a local-only save-revision record; never calls Builder |
128
128
  | `review-content-database-source-change-set` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> --decision approve\|reject [--note]` | Approve or reject a local source change-set review record without provider writes |
129
129
  | `stage-builder-source-bulk-update` | `--databaseId <id>` or `--documentId <id> --itemIds <json-array>` or `--documentIds <json-array> --field <json> [--sourceId <id>] [--dryRun true\|false]` | Preview or stage one bounded field update across selected source-backed Builder rows; apply still goes through Builder review/execution |
130
130
  | `prepare-builder-source-execution` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> [--pushModeConfirmation autosave\|draft\|publish]` | Prepare a dry-run Builder execution gate for approved field/body changes with request semantics/idempotency key; never calls Builder |
131
131
  | `validate-builder-source-execution` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> [--idempotencyKey <key>]` | Validate/replay a prepared Builder execution gate locally as a dry run; never calls Builder |
132
- | `execute-builder-source-execution` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> [--idempotencyKey <key>] [--pushModeConfirmation autosave\|draft\|publish]` | Execute a guarded live Builder write only when approved, validated, enabled, idempotent, and targeting `agent-native-blog-article-test` |
132
+ | `execute-builder-source-execution` | `--databaseId <id>` or `--documentId <id> --changeSetId <id> [--idempotencyKey <key>] [--pushModeConfirmation autosave\|draft\|publish]` | Execute a guarded live Builder write only when approved, validated, enabled for that source, and idempotent |
133
+ | `cancel-prepared-builder-source-update` | `--databaseId <id>` or `--documentId <id> --sourceId <id> --changeSetId <id> [--note <text>]` | Cancel an exact prepared Builder update only when every associated execution is provably pre-dispatch; preserves review and execution history and never calls Builder |
133
134
  | `add-database-item` | `--databaseId <id> [--title <text>] [--propertyValues <json>]` | Add a page row to a database, optionally seeding property values |
134
135
  | `submit-content-database-form` | `--databaseId <id> [--viewId <form-view-id>] [--title <text>] [--propertyValues <json>]` | Submit one form response atomically; validates required questions, resolves option labels/IDs, writes Blocks correctly, verifies persistence, and returns the exact row page link |
135
136
  | `duplicate-database-item` | `--itemId <id>` or `--documentId <id> [--title <text>]` | Duplicate exactly one database row page and its stored property values; for two or more rows, use `duplicate-database-items` once |
@@ -138,7 +139,7 @@ cd templates/content && pnpm action <name> [args]
138
139
  | `move-database-item` | `--itemId <id>` or `--documentId <id> --position <number>` | Move a database row page to a new zero-based table position |
139
140
  | `update-content-database-view` | `--databaseId <id> --viewConfig <json>` | Persist database views, sorts, filters, hidden properties, and view settings |
140
141
  | `list-document-properties` | `--documentId <id> [--format json]` | List Notion-style property definitions and values for a document |
141
- | `configure-document-property` | `--documentId <id> [--id <propertyId>] --name <name> --type <type> [--visibility always_show\|hide_when_empty\|always_hide]` | Create or update a property definition |
142
+ | `configure-document-property` | `--documentId <id> [--id <propertyId>] --name <name> --type <type> [--description <text>] [--visibility always_show\|hide_when_empty\|always_hide] [--options <json>]` | Create or update a property definition and its option-level guidance |
142
143
  | `duplicate-document-property` | `--documentId <id> --propertyId <propertyId>` | Duplicate a property definition and its stored values |
143
144
  | `delete-document-property` | `--documentId <id> --propertyId <propertyId>` | Delete a property definition and its stored values |
144
145
  | `set-document-property` | `--documentId <id> --propertyId <propertyId> --value <json>` | Set a document property value (for a `blocks` field, the value is its markdown content) |
@@ -151,6 +152,27 @@ Database views follow Notion-style tab labels. When creating or duplicating
151
152
  views in `viewConfig`, use unique default names (`Table 2`, `SEO copy 2`, etc.)
152
153
  instead of appending several tabs with the same label.
153
154
 
155
+ ### Self-Documenting Descriptions
156
+
157
+ Descriptions are stable semantic guidance, not generated summaries of current
158
+ content. Preserve this distinction when reading or writing them:
159
+
160
+ - A page description explains why the page exists and what belongs there.
161
+ - A database description explains the collection's purpose and inclusion
162
+ boundary. Inline and full-page views of one database share the same
163
+ description.
164
+ - A property description explains what the field means and what value belongs
165
+ there.
166
+ - A select, status, or multi-select option description explains when to choose
167
+ that option.
168
+
169
+ Descriptions are owned; context is inherited. Never copy an ancestor's prose
170
+ into a child description. Focused reads expose a root-to-parent `contextPath`
171
+ so the agent can use ancestor guidance without creating stale duplicates. Read
172
+ the returned descriptions before placing content or setting property values.
173
+ Update a description only when the object's meaning changes, not whenever its
174
+ current content changes.
175
+
154
176
  **`pull-document` is the collab-aware "ingest the final" read** — prefer it over
155
177
  `get-document` for external ingest (another app, an external coding agent over
156
178
  MCP/A2A, an A2A peer). `get-document` returns whatever is in the
@@ -388,6 +410,7 @@ Public documents are reachable at `/p/<id>` once visibility is `public`. Anyone
388
410
  | ------------------------------ | ------------------------------------------------------------------------------ |
389
411
  | "What am I looking at?" | `view-screen` |
390
412
  | "Create a page about X" | `create-document --title "X" --content "# X\n\n..."` |
413
+ | "Explain what belongs here" | `view-screen` to get ID, `update-document --id ... --description "..."` |
391
414
  | "Find my meeting notes" | `search-documents --query "meeting notes"` |
392
415
  | "Update the title of this doc" | `view-screen` to get ID, `update-document --id ... --title "New"` |
393
416
  | "Fix a typo / small edit" | `view-screen` to get ID, `edit-document --id ... --find "old" --replace "new"` |
@@ -0,0 +1,87 @@
1
+ import { isDeepStrictEqual } from "node:util";
2
+
3
+ import { BUILDER_CMS_SAFE_WRITE_MODEL } from "../shared/api.js";
4
+ import { readBuilderCmsContentEntries } from "./_builder-cms-read-client.js";
5
+ import type { BuilderCmsSourceEntry } from "./_builder-cms-source-adapter.js";
6
+ import { BUILDER_CMS_EXECUTION_MARKER_FIELD } from "./_builder-cms-write-adapter.js";
7
+
8
+ export interface BuilderCmsIntentMatch {
9
+ id: string;
10
+ title: string;
11
+ lastUpdated: string | null;
12
+ published: string | null;
13
+ }
14
+
15
+ export function matchBuilderCmsSafeModelIntentEntries(
16
+ entries: BuilderCmsSourceEntry[],
17
+ args: {
18
+ marker?: string;
19
+ exactTitle?: string;
20
+ intendedFields?: Record<string, unknown>;
21
+ },
22
+ ) {
23
+ const identityMatches = entries.filter((entry) => {
24
+ const data = rawRecord(entry.rawEntry?.data);
25
+ if (args.marker) {
26
+ return data?.[BUILDER_CMS_EXECUTION_MARKER_FIELD] === args.marker;
27
+ }
28
+ const title = data?.title;
29
+ return (
30
+ typeof title === "string" && title.trim() === args.exactTitle?.trim()
31
+ );
32
+ });
33
+ const matchingIntent = identityMatches.filter((entry) => {
34
+ const data = rawRecord(entry.rawEntry?.data);
35
+ return Object.entries(args.intendedFields ?? {}).every(([key, value]) =>
36
+ isDeepStrictEqual(data?.[key], value),
37
+ );
38
+ });
39
+ return {
40
+ count: identityMatches.length,
41
+ matchingIntentCount: matchingIntent.length,
42
+ matches: identityMatches.map(compactMatch),
43
+ };
44
+ }
45
+
46
+ function rawRecord(value: unknown): Record<string, unknown> | null {
47
+ return value && typeof value === "object" && !Array.isArray(value)
48
+ ? (value as Record<string, unknown>)
49
+ : null;
50
+ }
51
+
52
+ function compactMatch(entry: BuilderCmsSourceEntry): BuilderCmsIntentMatch {
53
+ const raw = rawRecord(entry.rawEntry);
54
+ return {
55
+ id: entry.id,
56
+ title: entry.title,
57
+ lastUpdated:
58
+ typeof raw?.lastUpdated === "string" ||
59
+ typeof raw?.lastUpdated === "number"
60
+ ? String(raw.lastUpdated)
61
+ : entry.updatedAt || null,
62
+ published: typeof raw?.published === "string" ? raw.published : null,
63
+ };
64
+ }
65
+
66
+ export async function lookupBuilderCmsSafeModelIntent(args: {
67
+ marker?: string;
68
+ exactTitle?: string;
69
+ intendedFields?: Record<string, unknown>;
70
+ }) {
71
+ if (!args.marker && !args.exactTitle) {
72
+ throw new Error("Provide an exact execution marker or exact title.");
73
+ }
74
+ const result = await readBuilderCmsContentEntries({
75
+ model: BUILDER_CMS_SAFE_WRITE_MODEL,
76
+ rawData: true,
77
+ requirePrivateKey: true,
78
+ limit: 10_000,
79
+ });
80
+ if (result.state !== "live" || result.progress.partial) {
81
+ throw new Error(
82
+ result.message ??
83
+ "Builder safe-model lookup was incomplete; reconciliation cannot proceed.",
84
+ );
85
+ }
86
+ return matchBuilderCmsSafeModelIntentEntries(result.entries, args);
87
+ }