@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
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
2
2
 
3
3
  import {
4
4
  appendA2AArtifactLinks,
5
+ extractA2AArtifactIdentities,
5
6
  type A2AToolResultSummary,
6
7
  } from "../a2a/artifact-response.js";
7
8
  import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
@@ -35,6 +36,7 @@ import {
35
36
  import {
36
37
  buildAssistantMessage,
37
38
  extractThreadMeta,
39
+ threadDataToEngineMessages,
38
40
  } from "../agent/thread-data-builder.js";
39
41
  import { attachToolSearch } from "../agent/tool-search.js";
40
42
  import { createThread, getThread } from "../chat-threads/store.js";
@@ -63,7 +65,11 @@ import {
63
65
  } from "./pending-tasks-store.js";
64
66
  import { integrationScopeSubjectKey } from "./scope-store.js";
65
67
  import { getThreadMapping, saveThreadMapping } from "./thread-mapping-store.js";
66
- import type { PlatformAdapter, IncomingMessage } from "./types.js";
68
+ import type {
69
+ PlatformAdapter,
70
+ IncomingMessage,
71
+ PlatformDeliveryReceipt,
72
+ } from "./types.js";
67
73
  import {
68
74
  listIntegrationUsageBudgets,
69
75
  releaseIntegrationUsageBudget,
@@ -710,34 +716,7 @@ async function processIncomingMessage(
710
716
  }
711
717
  const existingMessages: EngineMessage[] = [];
712
718
  if (thread?.threadData) {
713
- try {
714
- const data = JSON.parse(thread.threadData);
715
- if (Array.isArray(data.messages)) {
716
- for (const msg of data.messages) {
717
- const m = msg.message ?? msg;
718
- const textContent =
719
- typeof m.content === "string"
720
- ? m.content
721
- : Array.isArray(m.content)
722
- ? m.content
723
- .filter((c: any) => c.type === "text")
724
- .map((c: any) => c.text)
725
- .join("\n")
726
- : "";
727
- if (m.role === "user") {
728
- existingMessages.push({
729
- role: "user",
730
- content: [{ type: "text", text: textContent }],
731
- });
732
- } else if (m.role === "assistant") {
733
- existingMessages.push({
734
- role: "assistant",
735
- content: [{ type: "text", text: textContent }],
736
- });
737
- }
738
- }
739
- }
740
- } catch {}
719
+ existingMessages.push(...threadDataToEngineMessages(thread.threadData));
741
720
  }
742
721
 
743
722
  // Add the new user message. Include verified platform identity as lightweight
@@ -988,12 +967,11 @@ async function processIncomingMessage(
988
967
  // preview card.
989
968
  const baseUrl = process.env.APP_URL || process.env.URL || "";
990
969
  const appBaseUrl = baseUrl ? withConfiguredAppBasePath(baseUrl) : "";
970
+ const toolResults = collectToolResultSummaries(completedRun);
991
971
  if (!suppressPlatformReply) {
992
- responseText = appendA2AArtifactLinks(
993
- responseText,
994
- collectToolResultSummaries(completedRun),
995
- { baseUrl: appBaseUrl || undefined },
996
- );
972
+ responseText = appendA2AArtifactLinks(responseText, toolResults, {
973
+ baseUrl: appBaseUrl || undefined,
974
+ });
997
975
  }
998
976
  const threadDeepLinkUrl =
999
977
  appBaseUrl && threadId
@@ -1002,34 +980,60 @@ async function processIncomingMessage(
1002
980
 
1003
981
  // Format and send back to platform — update the "thinking…"
1004
982
  // placeholder in place if the adapter supplied one.
983
+ let deliveredResponse:
984
+ | {
985
+ platform: string;
986
+ status: "delivered";
987
+ text: string;
988
+ deliveredAt: string;
989
+ messageRefs?: string[];
990
+ }
991
+ | undefined;
1005
992
  if (!suppressPlatformReply) {
1006
993
  const outgoing = adapter.formatAgentResponse(responseText, {
1007
994
  threadDeepLinkUrl,
1008
995
  });
1009
- let delivered = false;
996
+ let deliveryReceipt: void | PlatformDeliveryReceipt;
1010
997
  if (queuedA2AContinuation && progress?.ref) {
1011
998
  // Post substantive parent results as a normal thread reply while
1012
999
  // the one continuation that claimed this resumable stream keeps
1013
1000
  // it open for its eventual terminal result.
1014
- await adapter.sendResponse(outgoing, incoming, {
1001
+ deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
1015
1002
  placeholderRef: opts.placeholderRef,
1016
1003
  });
1017
- delivered = true;
1018
1004
  } else if (progress) {
1019
1005
  try {
1020
- await progress.complete(outgoing);
1021
- delivered = true;
1006
+ deliveryReceipt = await progress.complete(outgoing);
1022
1007
  } catch {
1023
- await adapter.sendResponse(outgoing, incoming, {
1024
- placeholderRef: opts.placeholderRef,
1025
- });
1026
- delivered = true;
1008
+ deliveryReceipt = await adapter.sendResponse(
1009
+ outgoing,
1010
+ incoming,
1011
+ {
1012
+ placeholderRef: opts.placeholderRef,
1013
+ },
1014
+ );
1027
1015
  }
1028
1016
  } else {
1029
- await adapter.sendResponse(outgoing, incoming, {
1017
+ deliveryReceipt = await adapter.sendResponse(outgoing, incoming, {
1030
1018
  placeholderRef: opts.placeholderRef,
1031
1019
  });
1032
- delivered = true;
1020
+ }
1021
+ const delivered = deliveryReceipt?.status === "delivered";
1022
+ if (!delivered) {
1023
+ throw new Error(
1024
+ `${incoming.platform} response completed without delivery proof`,
1025
+ );
1026
+ }
1027
+ if (delivered) {
1028
+ deliveredResponse = {
1029
+ platform: incoming.platform,
1030
+ status: "delivered",
1031
+ text: outgoing.text,
1032
+ deliveredAt: new Date().toISOString(),
1033
+ ...(deliveryReceipt?.messageRefs?.length
1034
+ ? { messageRefs: deliveryReceipt.messageRefs }
1035
+ : {}),
1036
+ };
1033
1037
  }
1034
1038
  if (slackInputRequest && delivered && incoming.senderId) {
1035
1039
  await setIntegrationAwaitingInput({
@@ -1078,6 +1082,8 @@ async function processIncomingMessage(
1078
1082
  incoming.text,
1079
1083
  completedRun,
1080
1084
  thread,
1085
+ deliveredResponse,
1086
+ toolResults,
1081
1087
  );
1082
1088
  await recordIntegrationUsage({
1083
1089
  usage,
@@ -1533,6 +1539,14 @@ async function persistThreadData(
1533
1539
  userText: string,
1534
1540
  completedRun: ActiveRun,
1535
1541
  thread: any,
1542
+ deliveredResponse?: {
1543
+ platform: string;
1544
+ status: "delivered";
1545
+ text: string;
1546
+ deliveredAt: string;
1547
+ messageRefs?: string[];
1548
+ },
1549
+ toolResults: A2AToolResultSummary[] = [],
1536
1550
  ): Promise<void> {
1537
1551
  try {
1538
1552
  let repo: any;
@@ -1556,6 +1570,16 @@ async function persistThreadData(
1556
1570
  completedRun.events ?? [],
1557
1571
  completedRun.runId,
1558
1572
  );
1573
+ if (assistantMsg) {
1574
+ assistantMsg.metadata.integrationDeliveryAttempted = true;
1575
+ if (deliveredResponse) {
1576
+ assistantMsg.metadata.integrationDelivery = deliveredResponse;
1577
+ const artifactIdentities = extractA2AArtifactIdentities(toolResults);
1578
+ if (artifactIdentities.length > 0) {
1579
+ assistantMsg.metadata.integrationArtifacts = artifactIdentities;
1580
+ }
1581
+ }
1582
+ }
1559
1583
 
1560
1584
  repo.messages.push(userMsg);
1561
1585
  if (assistantMsg) {
@@ -110,6 +110,7 @@ export function assembleA2AFinalResponse(
110
110
  const finalText = appendA2AArtifactLinks(responseText, [...toolResults], {
111
111
  baseUrl: options.baseUrl ?? resolveArtifactBaseUrl(options.event),
112
112
  includeReferencedArtifacts: true,
113
+ includePersistedArtifactMarker: true,
113
114
  });
114
115
  if (terminalError && !finalText.trim()) {
115
116
  throw new Error(formatA2ATerminalError(terminalError));
@@ -104,9 +104,12 @@ import type { BetterAuthConfig } from "./better-auth-instance.js";
104
104
  import {
105
105
  BUILDER_CONNECT_OWNER_COOKIE,
106
106
  BUILDER_CONNECT_PARAM,
107
+ BUILDER_RELAY_PATH,
108
+ BUILDER_RELAY_STATE_PARAM,
107
109
  BUILDER_STATE_PARAM,
108
110
  verifyBuilderCallbackStateAndGetOwner,
109
111
  verifyBuilderConnectTokenAndGetOwner,
112
+ verifyBuilderPreviewRelayStateForCallback,
110
113
  } from "./builder-browser.js";
111
114
  import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
112
115
  import {
@@ -1522,7 +1525,15 @@ function verifiedBuilderConnectOwnerFromUrl(url: string): string | null {
1522
1525
  return verifyBuilderConnectTokenAndGetOwner(token);
1523
1526
  }
1524
1527
 
1525
- function shouldBypassAuthForBuilderConnect(event: H3Event, p: string): boolean {
1528
+ export function shouldBypassAuthForBuilderConnect(
1529
+ event: H3Event,
1530
+ p: string,
1531
+ ): boolean {
1532
+ // The preview-safe second hop is authenticated by its timestamped HMAC and
1533
+ // one-shot pending row. It cannot carry a browser session from the corporate
1534
+ // callback deployment, so let the route perform that stronger check itself.
1535
+ if (p === BUILDER_RELAY_PATH) return true;
1536
+
1526
1537
  if (p === "/_agent-native/builder/connect") {
1527
1538
  const url = event.node?.req?.url ?? event.path ?? "/";
1528
1539
  return Boolean(verifiedBuilderConnectOwnerFromUrl(url));
@@ -1537,6 +1548,19 @@ function shouldBypassAuthForBuilderConnect(event: H3Event, p: string): boolean {
1537
1548
  BUILDER_STATE_PARAM,
1538
1549
  )
1539
1550
  : null;
1551
+ const relayState =
1552
+ queryStart >= 0
1553
+ ? new URLSearchParams(url.slice(queryStart + 1)).get(
1554
+ BUILDER_RELAY_STATE_PARAM,
1555
+ )
1556
+ : null;
1557
+ if (relayState) {
1558
+ try {
1559
+ if (verifyBuilderPreviewRelayStateForCallback(relayState)) return true;
1560
+ } catch {
1561
+ // Dedicated relay secret missing: let the auth guard fail closed.
1562
+ }
1563
+ }
1540
1564
  // The signed `_an_state` authenticates this specific Builder callback
1541
1565
  // flow back to our app. A stale localhost session cookie can otherwise
1542
1566
  // make the global guard reject the callback before the handler gets to
@@ -1,4 +1,9 @@
1
- import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
1
+ import {
2
+ createHash,
3
+ createHmac,
4
+ randomBytes,
5
+ timingSafeEqual,
6
+ } from "node:crypto";
2
7
 
3
8
  import type { H3Event } from "h3";
4
9
  import { getHeader } from "h3";
@@ -15,6 +20,349 @@ const BUILDER_BROWSER_HOST = "agent-native-browser";
15
20
  const BUILDER_BROWSER_CLIENT_ID = "Agent Native Browser";
16
21
 
17
22
  export const BUILDER_CALLBACK_PATH = "/_agent-native/builder/callback";
23
+ export const BUILDER_RELAY_PATH = "/_agent-native/builder/relay";
24
+ export const BUILDER_RELAY_STATE_PARAM = "_an_relay";
25
+ export const BUILDER_RELAY_SECRET_ENV = "AGENT_NATIVE_BUILDER_RELAY_SECRET";
26
+ export const BUILDER_RELAY_TARGET_ORIGINS_ENV =
27
+ "AGENT_NATIVE_BUILDER_RELAY_TARGET_ORIGINS";
28
+ export const BUILDER_RELAY_TIMESTAMP_HEADER = "x-agent-native-relay-timestamp";
29
+ export const BUILDER_RELAY_FLOW_HEADER = "x-agent-native-relay-flow";
30
+ export const BUILDER_RELAY_SIGNATURE_HEADER = "x-agent-native-relay-signature";
31
+
32
+ const BUILDER_RELAY_PURPOSE = "builder-preview-callback-relay";
33
+ const BUILDER_RELAY_STATE_VERSION = 1;
34
+ const BUILDER_RELAY_TTL_MS = 10 * 60 * 1000;
35
+ const BUILDER_RELAY_REQUEST_SKEW_MS = 2 * 60 * 1000;
36
+
37
+ export interface BuilderPreviewRelayState {
38
+ v: 1;
39
+ purpose: typeof BUILDER_RELAY_PURPOSE;
40
+ flowId: string;
41
+ ownerEmail: string;
42
+ targetOrigin: string;
43
+ basePath: string;
44
+ iat: number;
45
+ exp: number;
46
+ }
47
+
48
+ export interface BuilderRelayCredentials {
49
+ privateKey: string;
50
+ publicKey: string;
51
+ userId: string | null;
52
+ orgName: string | null;
53
+ orgKind: string | null;
54
+ subscription: string | null;
55
+ subscriptionLevel: string | null;
56
+ subscriptionName: string | null;
57
+ isEnterprise: boolean | null;
58
+ isFreeAccount: boolean | null;
59
+ }
60
+
61
+ export interface BuilderRelayRequestBody {
62
+ relayState: string;
63
+ credentials: BuilderRelayCredentials;
64
+ }
65
+
66
+ function builderRelaySecret(): string {
67
+ const secret = process.env[BUILDER_RELAY_SECRET_ENV]?.trim();
68
+ if (!secret) {
69
+ throw new Error(
70
+ `${BUILDER_RELAY_SECRET_ENV} is required for Builder preview authorization relay.`,
71
+ );
72
+ }
73
+ if (secret.length < 32) {
74
+ throw new Error(
75
+ `${BUILDER_RELAY_SECRET_ENV} must be at least 32 characters long.`,
76
+ );
77
+ }
78
+ return secret;
79
+ }
80
+
81
+ function builderRelayMac(value: string): string {
82
+ return createHmac("sha256", builderRelaySecret())
83
+ .update(value)
84
+ .digest("base64url");
85
+ }
86
+
87
+ function safeEqualText(expected: string, actual: string): boolean {
88
+ const expectedBuffer = Buffer.from(expected);
89
+ const actualBuffer = Buffer.from(actual);
90
+ return (
91
+ expectedBuffer.length === actualBuffer.length &&
92
+ timingSafeEqual(expectedBuffer, actualBuffer)
93
+ );
94
+ }
95
+
96
+ function normalizeBuilderRelayBasePath(value: string): string | null {
97
+ if (!value) return "";
98
+ if (!value.startsWith("/") || value.includes("?") || value.includes("#")) {
99
+ return null;
100
+ }
101
+ const normalized = value.replace(/\/+$/, "");
102
+ if (normalized.split("/").some((part) => part === "." || part === "..")) {
103
+ return null;
104
+ }
105
+ return normalized;
106
+ }
107
+
108
+ export function isSafeBuilderRelayTargetOrigin(value: string): boolean {
109
+ try {
110
+ const url = new URL(value);
111
+ if (
112
+ url.origin !== value ||
113
+ url.username ||
114
+ url.password ||
115
+ url.hostname.includes("*")
116
+ )
117
+ return false;
118
+ const hostname = url.hostname.toLowerCase();
119
+ const loopback =
120
+ hostname === "localhost" ||
121
+ hostname === "127.0.0.1" ||
122
+ hostname === "::1" ||
123
+ hostname === "[::1]";
124
+ if (loopback)
125
+ return url.protocol === "http:" && process.env.NODE_ENV !== "production";
126
+ if (url.protocol !== "https:") return false;
127
+ return (
128
+ hostname.endsWith(".netlify.app") ||
129
+ hostname.endsWith(".vercel.app") ||
130
+ hostname === "agent-native.com" ||
131
+ hostname.endsWith(".agent-native.com") ||
132
+ hostname.endsWith(".builder.io") ||
133
+ hostname.endsWith(".builderio.xyz") ||
134
+ hostname.endsWith(".builderio.dev") ||
135
+ hostname.endsWith(".builder.codes")
136
+ );
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+
142
+ export function isTrustedBuilderRelayTargetOrigin(value: string): boolean {
143
+ if (!isSafeBuilderRelayTargetOrigin(value)) return false;
144
+ const hostname = new URL(value).hostname.toLowerCase();
145
+ if (
146
+ hostname.endsWith(".netlify.app") &&
147
+ !/^[a-f0-9]{24}--[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.netlify\.app$/.test(
148
+ hostname,
149
+ )
150
+ ) {
151
+ return false;
152
+ }
153
+ const configured = process.env[BUILDER_RELAY_TARGET_ORIGINS_ENV];
154
+ if (!configured) return false;
155
+ return configured
156
+ .split(",")
157
+ .map((origin) => origin.trim())
158
+ .filter(Boolean)
159
+ .some((origin) => origin === value && !origin.includes("*"));
160
+ }
161
+
162
+ export function signBuilderPreviewRelayState(input: {
163
+ ownerEmail: string;
164
+ targetOrigin: string;
165
+ basePath?: string;
166
+ flowId?: string;
167
+ now?: number;
168
+ }): { state: string; payload: BuilderPreviewRelayState } {
169
+ if (!isSafeBuilderRelayTargetOrigin(input.targetOrigin)) {
170
+ throw new Error(
171
+ "Builder relay target origin is not an approved preview origin.",
172
+ );
173
+ }
174
+ const basePath = normalizeBuilderRelayBasePath(input.basePath ?? "");
175
+ if (basePath === null) throw new Error("Builder relay base path is invalid.");
176
+ const now = input.now ?? Date.now();
177
+ const payload: BuilderPreviewRelayState = {
178
+ v: BUILDER_RELAY_STATE_VERSION,
179
+ purpose: BUILDER_RELAY_PURPOSE,
180
+ flowId: input.flowId ?? randomBytes(24).toString("base64url"),
181
+ ownerEmail: input.ownerEmail,
182
+ targetOrigin: input.targetOrigin,
183
+ basePath,
184
+ iat: now,
185
+ exp: now + BUILDER_RELAY_TTL_MS,
186
+ };
187
+ const encoded = Buffer.from(JSON.stringify(payload), "utf8").toString(
188
+ "base64url",
189
+ );
190
+ return { state: `${encoded}.${builderRelayMac(encoded)}`, payload };
191
+ }
192
+
193
+ export function verifyBuilderPreviewRelayState(
194
+ state: string | null | undefined,
195
+ options: { now?: number } = {},
196
+ ): BuilderPreviewRelayState | null {
197
+ if (!state) return null;
198
+ const parts = state.split(".");
199
+ if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
200
+ if (!safeEqualText(builderRelayMac(parts[0]), parts[1])) return null;
201
+ let value: unknown;
202
+ try {
203
+ value = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
204
+ } catch {
205
+ return null;
206
+ }
207
+ if (!value || typeof value !== "object") return null;
208
+ const payload = value as Partial<BuilderPreviewRelayState>;
209
+ const now = options.now ?? Date.now();
210
+ if (
211
+ payload.v !== BUILDER_RELAY_STATE_VERSION ||
212
+ payload.purpose !== BUILDER_RELAY_PURPOSE ||
213
+ typeof payload.flowId !== "string" ||
214
+ !/^[A-Za-z0-9_-]{24,128}$/.test(payload.flowId) ||
215
+ typeof payload.ownerEmail !== "string" ||
216
+ !payload.ownerEmail.includes("@") ||
217
+ typeof payload.targetOrigin !== "string" ||
218
+ !isSafeBuilderRelayTargetOrigin(payload.targetOrigin) ||
219
+ typeof payload.basePath !== "string" ||
220
+ normalizeBuilderRelayBasePath(payload.basePath) !== payload.basePath ||
221
+ typeof payload.iat !== "number" ||
222
+ typeof payload.exp !== "number" ||
223
+ payload.exp <= payload.iat ||
224
+ payload.exp - payload.iat > BUILDER_RELAY_TTL_MS ||
225
+ payload.iat > now + BUILDER_RELAY_REQUEST_SKEW_MS ||
226
+ payload.exp < now
227
+ ) {
228
+ return null;
229
+ }
230
+ return payload as BuilderPreviewRelayState;
231
+ }
232
+
233
+ /**
234
+ * Corporate callback trust check. Preview-side state verification and relay
235
+ * receipt deliberately do not require this callback-only allowlist.
236
+ */
237
+ export function verifyBuilderPreviewRelayStateForCallback(
238
+ state: string | null | undefined,
239
+ options: { now?: number } = {},
240
+ ): BuilderPreviewRelayState | null {
241
+ const payload = verifyBuilderPreviewRelayState(state, options);
242
+ return payload && isTrustedBuilderRelayTargetOrigin(payload.targetOrigin)
243
+ ? payload
244
+ : null;
245
+ }
246
+
247
+ export function getBuilderPreviewRelayUrl(
248
+ payload: BuilderPreviewRelayState,
249
+ ): string {
250
+ return `${payload.targetOrigin}${payload.basePath}${BUILDER_RELAY_PATH}`;
251
+ }
252
+
253
+ function builderRelayBodyDigest(body: string): string {
254
+ return createHash("sha256").update(body).digest("base64url");
255
+ }
256
+
257
+ function builderRelayRequestSignature(
258
+ timestamp: number,
259
+ flowId: string,
260
+ body: string,
261
+ ): string {
262
+ return builderRelayMac(
263
+ `v1.${timestamp}.${flowId}.${builderRelayBodyDigest(body)}`,
264
+ );
265
+ }
266
+
267
+ export function createBuilderRelayRequest(
268
+ relayState: string,
269
+ credentials: BuilderRelayCredentials,
270
+ options: { now?: number } = {},
271
+ ): { body: string; headers: Record<string, string>; url: string } {
272
+ const payload = verifyBuilderPreviewRelayState(relayState, options);
273
+ if (!payload) throw new Error("Builder relay state is invalid or expired.");
274
+ const body = JSON.stringify({
275
+ relayState,
276
+ credentials,
277
+ } satisfies BuilderRelayRequestBody);
278
+ const timestamp = options.now ?? Date.now();
279
+ return {
280
+ body,
281
+ url: getBuilderPreviewRelayUrl(payload),
282
+ headers: {
283
+ "content-type": "application/json",
284
+ [BUILDER_RELAY_TIMESTAMP_HEADER]: String(timestamp),
285
+ [BUILDER_RELAY_FLOW_HEADER]: payload.flowId,
286
+ [BUILDER_RELAY_SIGNATURE_HEADER]: builderRelayRequestSignature(
287
+ timestamp,
288
+ payload.flowId,
289
+ body,
290
+ ),
291
+ },
292
+ };
293
+ }
294
+
295
+ export function verifyBuilderRelayRequest(input: {
296
+ body: string;
297
+ timestamp: string | null | undefined;
298
+ flowId: string | null | undefined;
299
+ signature: string | null | undefined;
300
+ requestOrigin: string;
301
+ requestBasePath: string;
302
+ now?: number;
303
+ }): {
304
+ payload: BuilderPreviewRelayState;
305
+ body: BuilderRelayRequestBody;
306
+ } | null {
307
+ const timestamp = Number(input.timestamp);
308
+ const now = input.now ?? Date.now();
309
+ if (
310
+ !Number.isFinite(timestamp) ||
311
+ Math.abs(now - timestamp) > BUILDER_RELAY_REQUEST_SKEW_MS ||
312
+ !input.flowId ||
313
+ !input.signature ||
314
+ !safeEqualText(
315
+ builderRelayRequestSignature(timestamp, input.flowId, input.body),
316
+ input.signature,
317
+ )
318
+ ) {
319
+ return null;
320
+ }
321
+ let body: BuilderRelayRequestBody;
322
+ try {
323
+ body = JSON.parse(input.body) as BuilderRelayRequestBody;
324
+ } catch {
325
+ return null;
326
+ }
327
+ const payload = verifyBuilderPreviewRelayState(body.relayState, { now });
328
+ if (
329
+ !payload ||
330
+ payload.flowId !== input.flowId ||
331
+ payload.targetOrigin !== input.requestOrigin ||
332
+ payload.basePath !== input.requestBasePath ||
333
+ !body.credentials ||
334
+ typeof body.credentials.privateKey !== "string" ||
335
+ typeof body.credentials.publicKey !== "string" ||
336
+ !body.credentials.privateKey ||
337
+ !body.credentials.publicKey
338
+ ) {
339
+ return null;
340
+ }
341
+ const nullableString = (value: unknown): string | null =>
342
+ typeof value === "string" ? value : null;
343
+ const nullableBoolean = (value: unknown): boolean | null =>
344
+ typeof value === "boolean" ? value : null;
345
+ return {
346
+ payload,
347
+ body: {
348
+ relayState: body.relayState,
349
+ // Explicitly rebuild the credential payload. Extra fields such as an
350
+ // attacker-supplied ownerEmail/orgId never reach the credential writer.
351
+ credentials: {
352
+ privateKey: body.credentials.privateKey,
353
+ publicKey: body.credentials.publicKey,
354
+ userId: nullableString(body.credentials.userId),
355
+ orgName: nullableString(body.credentials.orgName),
356
+ orgKind: nullableString(body.credentials.orgKind),
357
+ subscription: nullableString(body.credentials.subscription),
358
+ subscriptionLevel: nullableString(body.credentials.subscriptionLevel),
359
+ subscriptionName: nullableString(body.credentials.subscriptionName),
360
+ isEnterprise: nullableBoolean(body.credentials.isEnterprise),
361
+ isFreeAccount: nullableBoolean(body.credentials.isFreeAccount),
362
+ },
363
+ },
364
+ };
365
+ }
18
366
 
19
367
  function escapeHtml(value: string): string {
20
368
  return value.replace(/[&<>"']/g, (ch) => {
@@ -516,6 +864,7 @@ export function buildBuilderCliAuthUrl(
516
864
  state: string | null = null,
517
865
  options: {
518
866
  previewOrigin?: string;
867
+ relayState?: string;
519
868
  tracking?: BuilderConnectTrackingParams;
520
869
  } = {},
521
870
  ): string {
@@ -536,6 +885,9 @@ export function buildBuilderCliAuthUrl(
536
885
  if (state) {
537
886
  callbackUrl.searchParams.set(BUILDER_STATE_PARAM, state);
538
887
  }
888
+ if (options.relayState) {
889
+ callbackUrl.searchParams.set(BUILDER_RELAY_STATE_PARAM, options.relayState);
890
+ }
539
891
  // When the cli-auth allow-list forces preview_url onto the gateway origin,
540
892
  // the callback would otherwise lose the real opener origin and post its
541
893
  // success message to the gateway instead of the preview tab. Embed the