@agent-native/core 0.107.0 → 0.107.2

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 (156) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +16 -0
  3. package/corpus/core/docs/content/integrations.mdx +27 -27
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/a2a/artifact-response.ts +32 -10
  6. package/corpus/core/src/agent/production-agent.ts +19 -0
  7. package/corpus/core/src/agent/run-store.ts +98 -2
  8. package/corpus/core/src/client/AssistantChat.tsx +13 -0
  9. package/corpus/core/src/client/CommandMenu.tsx +5 -0
  10. package/corpus/core/src/client/active-run-state.ts +34 -0
  11. package/corpus/core/src/client/agent-chat-adapter.ts +4 -0
  12. package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +41 -11
  13. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +27 -9
  14. package/corpus/core/src/client/resources/mcp-integration-catalog.ts +45 -0
  15. package/corpus/core/src/client/sharing/ShareButton.tsx +30 -9
  16. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +2 -2
  17. package/corpus/core/src/integrations/a2a-continuation-processor.ts +107 -3
  18. package/corpus/core/src/integrations/pending-tasks-store.ts +98 -1
  19. package/corpus/core/src/integrations/plugin.ts +129 -4
  20. package/corpus/core/src/integrations/webhook-handler.ts +339 -36
  21. package/corpus/core/src/localization/default-messages.ts +4 -1
  22. package/corpus/core/src/secrets/register-framework-secrets.ts +28 -0
  23. package/corpus/core/src/server/agent-chat-plugin.ts +122 -9
  24. package/corpus/core/src/vite/client.ts +24 -0
  25. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +29 -21
  26. package/corpus/templates/analytics/AGENTS.md +8 -0
  27. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  28. package/corpus/templates/analytics/actions/update-dashboard.ts +6 -2
  29. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +35 -7
  30. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -3
  31. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -1
  32. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/extension-slot.ts +11 -0
  33. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +27 -0
  34. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +6 -0
  35. package/corpus/templates/analytics/changelog/2026-07-17-dashboard-extension-boxes.md +6 -0
  36. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +8 -0
  37. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
  38. package/corpus/templates/assets/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  39. package/corpus/templates/brain/app/hooks/use-navigation-state.ts +6 -1
  40. package/corpus/templates/brain/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  41. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -1
  42. package/corpus/templates/calendar/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  43. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  44. package/corpus/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  45. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  46. package/corpus/templates/clips/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  47. package/corpus/templates/content/actions/_content-space-access.ts +45 -2
  48. package/corpus/templates/content/actions/create-content-database.ts +41 -7
  49. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  50. package/corpus/templates/content/app/root.tsx +1 -10
  51. package/corpus/templates/content/changelog/2026-07-17-creating-a-database-now-completes-reliably-in-hosted-workspa.md +6 -0
  52. package/corpus/templates/content/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  53. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +4 -1
  54. package/corpus/templates/dispatch/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  55. package/corpus/templates/forms/app/components/layout/Layout.tsx +2 -4
  56. package/corpus/templates/forms/app/components/ui/popover.tsx +25 -1
  57. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +6 -1
  58. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +13 -10
  59. package/corpus/templates/forms/app/root.tsx +6 -1
  60. package/corpus/templates/forms/changelog/2026-07-17-form-builder-popovers-stay-within-the-viewport.md +6 -0
  61. package/corpus/templates/forms/changelog/2026-07-17-form-editor-loading-matches-current-sidebar-free-layout.md +6 -0
  62. package/corpus/templates/forms/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  63. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
  64. package/corpus/templates/mail/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  65. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +14 -3
  66. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +8 -1
  67. package/corpus/templates/plan/app/root.tsx +0 -2
  68. package/corpus/templates/plan/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  69. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  70. package/corpus/templates/slides/app/context/DeckContext.tsx +23 -23
  71. package/corpus/templates/slides/changelog/2026-07-17-duplicated-slides-now-stay-in-the-deck.md +6 -0
  72. package/corpus/templates/slides/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  73. package/corpus/templates/tasks/app/components/layout/Layout.tsx +2 -13
  74. package/corpus/templates/tasks/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it.md +6 -0
  75. package/dist/a2a/artifact-response.d.ts +5 -1
  76. package/dist/a2a/artifact-response.d.ts.map +1 -1
  77. package/dist/a2a/artifact-response.js +18 -12
  78. package/dist/a2a/artifact-response.js.map +1 -1
  79. package/dist/agent/production-agent.d.ts.map +1 -1
  80. package/dist/agent/production-agent.js +13 -1
  81. package/dist/agent/production-agent.js.map +1 -1
  82. package/dist/agent/run-store.d.ts +17 -0
  83. package/dist/agent/run-store.d.ts.map +1 -1
  84. package/dist/agent/run-store.js +84 -2
  85. package/dist/agent/run-store.js.map +1 -1
  86. package/dist/client/AssistantChat.d.ts.map +1 -1
  87. package/dist/client/AssistantChat.js +10 -1
  88. package/dist/client/AssistantChat.js.map +1 -1
  89. package/dist/client/CommandMenu.d.ts.map +1 -1
  90. package/dist/client/CommandMenu.js +4 -0
  91. package/dist/client/CommandMenu.js.map +1 -1
  92. package/dist/client/active-run-state.d.ts +7 -0
  93. package/dist/client/active-run-state.d.ts.map +1 -1
  94. package/dist/client/active-run-state.js +28 -0
  95. package/dist/client/active-run-state.js.map +1 -1
  96. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  97. package/dist/client/agent-chat-adapter.js +4 -1
  98. package/dist/client/agent-chat-adapter.js.map +1 -1
  99. package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
  100. package/dist/client/resources/McpConnectionSuggestion.js +27 -8
  101. package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
  102. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  103. package/dist/client/resources/McpIntegrationDialog.js +4 -2
  104. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  105. package/dist/client/resources/mcp-integration-catalog.d.ts +6 -0
  106. package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
  107. package/dist/client/resources/mcp-integration-catalog.js +33 -0
  108. package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
  109. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  110. package/dist/client/sharing/ShareButton.js +15 -12
  111. package/dist/client/sharing/ShareButton.js.map +1 -1
  112. package/dist/client/use-agent-chat-home-handoff.d.ts +1 -1
  113. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  114. package/dist/client/use-agent-chat-home-handoff.js +1 -1
  115. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  116. package/dist/collab/awareness.d.ts +2 -2
  117. package/dist/collab/awareness.d.ts.map +1 -1
  118. package/dist/collab/routes.d.ts +1 -1
  119. package/dist/integrations/a2a-continuation-processor.js +62 -5
  120. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  121. package/dist/integrations/pending-tasks-store.d.ts +3 -0
  122. package/dist/integrations/pending-tasks-store.d.ts.map +1 -1
  123. package/dist/integrations/pending-tasks-store.js +75 -1
  124. package/dist/integrations/pending-tasks-store.js.map +1 -1
  125. package/dist/integrations/plugin.d.ts.map +1 -1
  126. package/dist/integrations/plugin.js +81 -5
  127. package/dist/integrations/plugin.js.map +1 -1
  128. package/dist/integrations/webhook-handler.d.ts +23 -2
  129. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  130. package/dist/integrations/webhook-handler.js +257 -35
  131. package/dist/integrations/webhook-handler.js.map +1 -1
  132. package/dist/localization/default-messages.d.ts +2 -0
  133. package/dist/localization/default-messages.d.ts.map +1 -1
  134. package/dist/localization/default-messages.js +3 -1
  135. package/dist/localization/default-messages.js.map +1 -1
  136. package/dist/notifications/routes.d.ts +2 -2
  137. package/dist/observability/routes.d.ts +5 -5
  138. package/dist/resources/handlers.d.ts +1 -1
  139. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  140. package/dist/secrets/register-framework-secrets.js +25 -0
  141. package/dist/secrets/register-framework-secrets.js.map +1 -1
  142. package/dist/secrets/routes.d.ts +9 -9
  143. package/dist/server/agent-chat-plugin.d.ts +6 -0
  144. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  145. package/dist/server/agent-chat-plugin.js +94 -7
  146. package/dist/server/agent-chat-plugin.js.map +1 -1
  147. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  148. package/dist/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  149. package/dist/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
  150. package/dist/vite/client.d.ts.map +1 -1
  151. package/dist/vite/client.js +20 -0
  152. package/dist/vite/client.js.map +1 -1
  153. package/docs/content/integrations.mdx +27 -27
  154. package/package.json +1 -1
  155. package/src/templates/chat/app/hooks/use-navigation-state.ts +4 -1
  156. package/src/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +6 -0
@@ -4,6 +4,7 @@ import {
4
4
  appendA2AArtifactLinks,
5
5
  buildA2AVerifiedMutationReceipt,
6
6
  extractA2AArtifactIdentities,
7
+ type A2AArtifactIdentity,
7
8
  type A2AToolResultSummary,
8
9
  } from "../a2a/artifact-response.js";
9
10
  import { collectFinalResponseTextFromAgentEvents } from "../a2a/response-text.js";
@@ -43,7 +44,7 @@ import { attachToolSearch } from "../agent/tool-search.js";
43
44
  import { createThread, getThread } from "../chat-threads/store.js";
44
45
  import { updateThreadData } from "../chat-threads/store.js";
45
46
  import { isLocalDatabase } from "../db/client.js";
46
- import { resolveOrgIdForEmail } from "../org/context.js";
47
+ import { getOrgA2ASecret, resolveOrgIdForEmail } from "../org/context.js";
47
48
  import { withConfiguredAppBasePath } from "../server/app-base-path.js";
48
49
  import { FRAMEWORK_ROUTE_PREFIX } from "../server/core-routes-plugin.js";
49
50
  import {
@@ -62,6 +63,7 @@ import { signInternalToken } from "./internal-token.js";
62
63
  import {
63
64
  insertPendingTask,
64
65
  isDuplicateEventError,
66
+ stageTaskDeliveryPayload,
65
67
  type PendingTask,
66
68
  } from "./pending-tasks-store.js";
67
69
  import { integrationScopeSubjectKey } from "./scope-store.js";
@@ -69,6 +71,7 @@ import { getThreadMapping, saveThreadMapping } from "./thread-mapping-store.js";
69
71
  import type {
70
72
  PlatformAdapter,
71
73
  IncomingMessage,
74
+ OutgoingMessage,
72
75
  PlatformDeliveryReceipt,
73
76
  } from "./types.js";
74
77
  import {
@@ -92,6 +95,27 @@ type ToolDoneEvent = {
92
95
  completedSideEffect?: boolean;
93
96
  };
94
97
 
98
+ export type IntegrationResponseDeliveryTaskPayload = {
99
+ kind: "response-delivery";
100
+ incoming: IncomingMessage;
101
+ message: OutgoingMessage;
102
+ placeholderRef?: string;
103
+ internalThreadId?: string;
104
+ userMessageId?: string;
105
+ assistantMessageId?: string;
106
+ deliveryReceipt?: PlatformDeliveryReceipt;
107
+ deliveredAt?: string;
108
+ artifacts?: A2AArtifactIdentity[];
109
+ };
110
+
111
+ export type ProcessIntegrationTaskResult =
112
+ | { status: "completed" }
113
+ | {
114
+ status: "delivery-pending";
115
+ payload: IntegrationResponseDeliveryTaskPayload;
116
+ errorMessage: string;
117
+ };
118
+
95
119
  /**
96
120
  * Build a stable per-event dedup key from the incoming message. The same
97
121
  * key is computed for every retry of the same event from the platform —
@@ -124,6 +148,17 @@ function buildEventDedupKey(incoming: IncomingMessage): string {
124
148
  return `${incoming.platform}:${incoming.externalThreadId}:${eventReference}`;
125
149
  }
126
150
 
151
+ function buildDeliveryHistoryMessageIds(incoming: IncomingMessage): {
152
+ userMessageId: string;
153
+ assistantMessageId: string;
154
+ } {
155
+ const eventKey = buildEventDedupKey(incoming);
156
+ return {
157
+ userMessageId: `integration-${eventKey}-user`,
158
+ assistantMessageId: `integration-${eventKey}-assistant`,
159
+ };
160
+ }
161
+
127
162
  export interface WebhookHandlerOptions {
128
163
  adapter: PlatformAdapter;
129
164
  /** Resolved system prompt string */
@@ -546,7 +581,7 @@ export function resolveBaseUrl(event: H3Event): string {
546
581
  export async function processIntegrationTask(
547
582
  task: PendingTask,
548
583
  options: WebhookHandlerOptions,
549
- ): Promise<void> {
584
+ ): Promise<ProcessIntegrationTaskResult> {
550
585
  const parsed = JSON.parse(task.payload) as {
551
586
  incoming: IncomingMessage;
552
587
  placeholderRef?: string;
@@ -555,7 +590,7 @@ export async function processIntegrationTask(
555
590
 
556
591
  await recordInboundIntegrationAudit(task, parsed.incoming);
557
592
 
558
- await processIncomingMessage(parsed.incoming, options, {
593
+ return processIncomingMessage(parsed.incoming, options, {
559
594
  taskId: task.id,
560
595
  attempts: task.attempts,
561
596
  placeholderRef: parsed.placeholderRef,
@@ -615,7 +650,7 @@ async function processIncomingMessage(
615
650
  orgId?: string;
616
651
  principalType?: "user" | "service";
617
652
  } = {},
618
- ): Promise<void> {
653
+ ): Promise<ProcessIntegrationTaskResult> {
619
654
  const {
620
655
  adapter,
621
656
  systemPrompt,
@@ -698,10 +733,49 @@ async function processIncomingMessage(
698
733
  const outgoing = adapter.formatAgentResponse(
699
734
  "This channel or requester has reached its configured AI usage budget. An admin can review the budget in Messaging settings.",
700
735
  );
701
- await adapter.sendResponse(outgoing, incoming, {
702
- placeholderRef: opts.placeholderRef,
703
- });
704
- return;
736
+ let deliveryPayload: IntegrationResponseDeliveryTaskPayload = {
737
+ kind: "response-delivery",
738
+ incoming,
739
+ message: outgoing,
740
+ ...(opts.placeholderRef ? { placeholderRef: opts.placeholderRef } : {}),
741
+ };
742
+ try {
743
+ if (opts.taskId) {
744
+ await stageTaskDeliveryPayload(
745
+ opts.taskId,
746
+ JSON.stringify(deliveryPayload),
747
+ );
748
+ }
749
+ const receipt = await adapter.sendResponse(outgoing, incoming, {
750
+ placeholderRef: opts.placeholderRef,
751
+ });
752
+ if (receipt?.status !== "delivered") {
753
+ throw new Error(
754
+ `${incoming.platform} response completed without delivery proof`,
755
+ );
756
+ }
757
+ deliveryPayload = {
758
+ ...deliveryPayload,
759
+ deliveryReceipt: receipt,
760
+ deliveredAt: new Date().toISOString(),
761
+ };
762
+ if (opts.taskId) {
763
+ await stageTaskDeliveryPayload(
764
+ opts.taskId,
765
+ JSON.stringify(deliveryPayload),
766
+ );
767
+ }
768
+ return { status: "completed" };
769
+ } catch (error) {
770
+ return {
771
+ status: "delivery-pending",
772
+ payload: deliveryPayload,
773
+ errorMessage:
774
+ error instanceof Error
775
+ ? error.message.slice(0, 1000)
776
+ : `${incoming.platform} response delivery failed`,
777
+ };
778
+ }
705
779
  }
706
780
 
707
781
  let threadId: string;
@@ -785,11 +859,13 @@ async function processIncomingMessage(
785
859
  // A2A delegation. Without this, getRequestOrgId() returns undefined and
786
860
  // call-agent can't look up the org's a2a_secret or org_domain.
787
861
  let orgId: string | null | undefined;
862
+ let artifactSecrets: string[];
788
863
  let runnableActions: Record<string, ActionEntry>;
789
864
  let tools: ReturnType<typeof actionsToEngineTools>;
790
865
  let availableTools: ReturnType<typeof actionsToEngineTools>;
791
866
  try {
792
867
  orgId = opts.orgId ?? (await resolveOrgIdForEmail(ownerEmail));
868
+ artifactSecrets = await resolveIntegrationArtifactSecrets(orgId);
793
869
  // Attach tool-search on a shallow copy so framework additions merged in
794
870
  // by `createIntegrationsPlugin` (integration memory, `call-agent`) can be
795
871
  // deferred behind it without mutating the plugin's long-lived registry.
@@ -811,7 +887,7 @@ async function processIncomingMessage(
811
887
 
812
888
  // Wait for the run to complete inside this fresh function execution.
813
889
  // We use a Promise so the processor endpoint can await the full lifecycle.
814
- await new Promise<void>((resolve) => {
890
+ return new Promise<ProcessIntegrationTaskResult>((resolve) => {
815
891
  startRun(
816
892
  runId,
817
893
  threadId,
@@ -921,6 +997,14 @@ async function processIncomingMessage(
921
997
  async (completedRun: ActiveRun) => {
922
998
  let keepSlackInputWindow = false;
923
999
  let queuedA2AContinuation = false;
1000
+ let outgoingForDelivery: OutgoingMessage | undefined;
1001
+ let stagedDeliveryPayload:
1002
+ | IntegrationResponseDeliveryTaskPayload
1003
+ | undefined;
1004
+ let threadCheckpoint:
1005
+ | { userMessageId: string; assistantMessageId?: string }
1006
+ | undefined;
1007
+ let outcome: ProcessIntegrationTaskResult = { status: "completed" };
924
1008
  try {
925
1009
  queuedA2AContinuation = hasQueuedA2AContinuation(completedRun);
926
1010
  const slackInputRequest =
@@ -1029,6 +1113,26 @@ async function processIncomingMessage(
1029
1113
  const outgoing = adapter.formatAgentResponse(responseText, {
1030
1114
  threadDeepLinkUrl,
1031
1115
  });
1116
+ outgoingForDelivery = outgoing;
1117
+ stagedDeliveryPayload = {
1118
+ kind: "response-delivery",
1119
+ incoming,
1120
+ message: outgoing,
1121
+ ...(opts.placeholderRef
1122
+ ? { placeholderRef: opts.placeholderRef }
1123
+ : {}),
1124
+ internalThreadId: threadId,
1125
+ ...buildDeliveryHistoryMessageIds(incoming),
1126
+ artifacts: extractA2AArtifactIdentities(toolResults, {
1127
+ persistedArtifactSecrets: artifactSecrets,
1128
+ }),
1129
+ };
1130
+ if (opts.taskId) {
1131
+ await stageTaskDeliveryPayload(
1132
+ opts.taskId,
1133
+ JSON.stringify(stagedDeliveryPayload),
1134
+ );
1135
+ }
1032
1136
  let deliveryReceipt: void | PlatformDeliveryReceipt;
1033
1137
  if (queuedA2AContinuation && progress?.ref) {
1034
1138
  // Post substantive parent results as a normal thread reply while
@@ -1054,24 +1158,33 @@ async function processIncomingMessage(
1054
1158
  placeholderRef: opts.placeholderRef,
1055
1159
  });
1056
1160
  }
1057
- const delivered = deliveryReceipt?.status === "delivered";
1058
- if (!delivered) {
1161
+ if (deliveryReceipt?.status !== "delivered") {
1059
1162
  throw new Error(
1060
1163
  `${incoming.platform} response completed without delivery proof`,
1061
1164
  );
1062
1165
  }
1063
- if (delivered) {
1064
- deliveredResponse = {
1065
- platform: incoming.platform,
1066
- status: "delivered",
1067
- text: outgoing.text,
1068
- deliveredAt: new Date().toISOString(),
1069
- ...(deliveryReceipt?.messageRefs?.length
1070
- ? { messageRefs: deliveryReceipt.messageRefs }
1071
- : {}),
1072
- };
1166
+ const deliveredAt = new Date().toISOString();
1167
+ stagedDeliveryPayload = {
1168
+ ...stagedDeliveryPayload,
1169
+ deliveryReceipt,
1170
+ deliveredAt,
1171
+ };
1172
+ if (opts.taskId) {
1173
+ await stageTaskDeliveryPayload(
1174
+ opts.taskId,
1175
+ JSON.stringify(stagedDeliveryPayload),
1176
+ );
1073
1177
  }
1074
- if (slackInputRequest && delivered && incoming.senderId) {
1178
+ deliveredResponse = {
1179
+ platform: incoming.platform,
1180
+ status: "delivered",
1181
+ text: outgoing.text,
1182
+ deliveredAt,
1183
+ ...(deliveryReceipt.messageRefs?.length
1184
+ ? { messageRefs: deliveryReceipt.messageRefs }
1185
+ : {}),
1186
+ };
1187
+ if (slackInputRequest && incoming.senderId) {
1075
1188
  await setIntegrationAwaitingInput({
1076
1189
  platform: "slack",
1077
1190
  externalThreadId: incoming.externalThreadId,
@@ -1113,14 +1226,34 @@ async function processIncomingMessage(
1113
1226
  }
1114
1227
 
1115
1228
  // Persist thread data
1116
- await persistThreadData(
1229
+ threadCheckpoint = await persistThreadData(
1117
1230
  threadId,
1118
1231
  incoming.text,
1119
1232
  completedRun,
1120
1233
  thread,
1121
1234
  deliveredResponse,
1122
1235
  toolResults,
1236
+ stagedDeliveryPayload,
1237
+ artifactSecrets,
1123
1238
  );
1239
+ if (outgoingForDelivery && stagedDeliveryPayload) {
1240
+ if (!threadCheckpoint) {
1241
+ throw new Error("Integration response history checkpoint failed");
1242
+ }
1243
+ stagedDeliveryPayload = {
1244
+ ...stagedDeliveryPayload,
1245
+ userMessageId: threadCheckpoint.userMessageId,
1246
+ ...(threadCheckpoint.assistantMessageId
1247
+ ? { assistantMessageId: threadCheckpoint.assistantMessageId }
1248
+ : {}),
1249
+ };
1250
+ if (opts.taskId) {
1251
+ await stageTaskDeliveryPayload(
1252
+ opts.taskId,
1253
+ JSON.stringify(stagedDeliveryPayload),
1254
+ );
1255
+ }
1256
+ }
1124
1257
  await recordIntegrationUsage({
1125
1258
  usage,
1126
1259
  ownerEmail,
@@ -1141,9 +1274,64 @@ async function processIncomingMessage(
1141
1274
  `[integrations] Error sending response to ${incoming.platform}:`,
1142
1275
  err,
1143
1276
  );
1277
+ if (outgoingForDelivery) {
1278
+ const errorMessage =
1279
+ err instanceof Error
1280
+ ? err.message.slice(0, 1000)
1281
+ : `${incoming.platform} response delivery failed`;
1282
+ if (!stagedDeliveryPayload?.deliveryReceipt) {
1283
+ threadCheckpoint = await persistThreadData(
1284
+ threadId,
1285
+ incoming.text,
1286
+ completedRun,
1287
+ thread,
1288
+ undefined,
1289
+ collectToolResultSummaries(completedRun),
1290
+ stagedDeliveryPayload,
1291
+ artifactSecrets,
1292
+ );
1293
+ }
1294
+ if (usage) {
1295
+ await recordIntegrationUsage({
1296
+ usage,
1297
+ ownerEmail,
1298
+ appId: options.appId,
1299
+ runId,
1300
+ threadId,
1301
+ taskId: opts.taskId,
1302
+ orgId: orgId ?? undefined,
1303
+ incoming,
1304
+ }).catch(() => {});
1305
+ try {
1306
+ await settleApplicableIntegrationBudgets(
1307
+ budgetReservations.reservations,
1308
+ usage,
1309
+ );
1310
+ budgetsSettled = true;
1311
+ } catch {}
1312
+ }
1313
+ outcome = {
1314
+ status: "delivery-pending",
1315
+ payload: {
1316
+ ...(stagedDeliveryPayload ?? {
1317
+ kind: "response-delivery",
1318
+ incoming,
1319
+ message: outgoingForDelivery,
1320
+ internalThreadId: threadId,
1321
+ }),
1322
+ ...(threadCheckpoint?.userMessageId
1323
+ ? { userMessageId: threadCheckpoint.userMessageId }
1324
+ : {}),
1325
+ ...(threadCheckpoint?.assistantMessageId
1326
+ ? { assistantMessageId: threadCheckpoint.assistantMessageId }
1327
+ : {}),
1328
+ },
1329
+ errorMessage,
1330
+ };
1331
+ return;
1332
+ }
1144
1333
  // A queued continuation owns the final platform response. Later
1145
- // bookkeeping failures (for example, persisting this parent run)
1146
- // must not close its resumable native stream with a false failure.
1334
+ // bookkeeping failures must not close its stream with a false failure.
1147
1335
  if (queuedA2AContinuation) return;
1148
1336
  // Last-ditch: try to post a brief apology so the thread isn't silent.
1149
1337
  try {
@@ -1170,7 +1358,7 @@ async function processIncomingMessage(
1170
1358
  budgetReservations.reservations,
1171
1359
  );
1172
1360
  }
1173
- resolve();
1361
+ resolve(outcome);
1174
1362
  }
1175
1363
  },
1176
1364
  // Integration workers are ordinary self-dispatched serverless requests,
@@ -1583,7 +1771,12 @@ async function persistThreadData(
1583
1771
  messageRefs?: string[];
1584
1772
  },
1585
1773
  toolResults: A2AToolResultSummary[] = [],
1586
- ): Promise<void> {
1774
+ messageIds?: Pick<
1775
+ IntegrationResponseDeliveryTaskPayload,
1776
+ "userMessageId" | "assistantMessageId"
1777
+ >,
1778
+ artifactSecrets: readonly string[] = [],
1779
+ ): Promise<{ userMessageId: string; assistantMessageId?: string } | undefined> {
1587
1780
  try {
1588
1781
  let repo: any;
1589
1782
  try {
@@ -1595,31 +1788,44 @@ async function persistThreadData(
1595
1788
 
1596
1789
  // Add user message
1597
1790
  const userMsg = {
1598
- id: `msg-${Date.now()}-user`,
1791
+ id: messageIds?.userMessageId ?? `msg-${Date.now()}-user`,
1599
1792
  role: "user",
1600
1793
  content: [{ type: "text", text: userText }],
1601
1794
  createdAt: new Date().toISOString(),
1602
1795
  };
1603
1796
 
1604
1797
  // Build assistant message from run events
1605
- const assistantMsg = buildAssistantMessage(
1798
+ const builtAssistantMsg = buildAssistantMessage(
1606
1799
  completedRun.events ?? [],
1607
1800
  completedRun.runId,
1608
1801
  );
1802
+ if (builtAssistantMsg && messageIds?.assistantMessageId) {
1803
+ builtAssistantMsg.id = messageIds.assistantMessageId;
1804
+ }
1805
+ const existingAssistantMsg = builtAssistantMsg
1806
+ ? repo.messages.find(
1807
+ (message: any) => message?.id === builtAssistantMsg.id,
1808
+ )
1809
+ : undefined;
1810
+ const assistantMsg = existingAssistantMsg ?? builtAssistantMsg;
1609
1811
  if (assistantMsg) {
1610
1812
  assistantMsg.metadata.integrationDeliveryAttempted = true;
1813
+ const artifactIdentities = extractA2AArtifactIdentities(toolResults, {
1814
+ persistedArtifactSecrets: artifactSecrets,
1815
+ });
1816
+ if (artifactIdentities.length > 0) {
1817
+ assistantMsg.metadata.integrationArtifacts = artifactIdentities;
1818
+ }
1611
1819
  if (deliveredResponse) {
1612
1820
  assistantMsg.metadata.integrationDelivery = deliveredResponse;
1613
- const artifactIdentities = extractA2AArtifactIdentities(toolResults);
1614
- if (artifactIdentities.length > 0) {
1615
- assistantMsg.metadata.integrationArtifacts = artifactIdentities;
1616
- }
1617
1821
  }
1618
1822
  }
1619
1823
 
1620
- repo.messages.push(userMsg);
1621
- if (assistantMsg) {
1622
- repo.messages.push(assistantMsg);
1824
+ if (!repo.messages.some((message: any) => message?.id === userMsg.id)) {
1825
+ repo.messages.push(userMsg);
1826
+ }
1827
+ if (builtAssistantMsg && !existingAssistantMsg) {
1828
+ repo.messages.push(builtAssistantMsg);
1623
1829
  }
1624
1830
 
1625
1831
  const meta = extractThreadMeta(repo);
@@ -1630,7 +1836,104 @@ async function persistThreadData(
1630
1836
  meta.preview || thread?.preview || "",
1631
1837
  repo.messages.length,
1632
1838
  );
1839
+ return {
1840
+ userMessageId: userMsg.id,
1841
+ ...(assistantMsg?.id ? { assistantMessageId: assistantMsg.id } : {}),
1842
+ };
1633
1843
  } catch {
1634
1844
  // Best-effort persistence
1845
+ return undefined;
1846
+ }
1847
+ }
1848
+
1849
+ async function resolveIntegrationArtifactSecrets(
1850
+ orgId: string | null | undefined,
1851
+ ): Promise<string[]> {
1852
+ const secrets: string[] = [];
1853
+ const add = (secret: string | null | undefined) => {
1854
+ const value = secret?.trim();
1855
+ if (value && !secrets.includes(value)) secrets.push(value);
1856
+ };
1857
+ add(process.env.A2A_SECRET);
1858
+ if (orgId) {
1859
+ try {
1860
+ add(await getOrgA2ASecret(orgId));
1861
+ } catch {}
1635
1862
  }
1863
+ return secrets;
1864
+ }
1865
+
1866
+ export async function recordIntegrationResponseDelivery(
1867
+ payload: IntegrationResponseDeliveryTaskPayload,
1868
+ receipt: PlatformDeliveryReceipt,
1869
+ ): Promise<void> {
1870
+ if (!payload.internalThreadId) return;
1871
+ const thread = await getThread(payload.internalThreadId);
1872
+ if (!thread) throw new Error("Integration delivery thread was not found");
1873
+
1874
+ let repo: any;
1875
+ try {
1876
+ repo = JSON.parse(thread.threadData || "{}");
1877
+ } catch {
1878
+ throw new Error("Integration delivery thread data is invalid");
1879
+ }
1880
+ if (!Array.isArray(repo.messages)) {
1881
+ repo.messages = [];
1882
+ }
1883
+ const stableMessageIds = buildDeliveryHistoryMessageIds(payload.incoming);
1884
+ const userMessageId = payload.userMessageId ?? stableMessageIds.userMessageId;
1885
+ const assistantMessageId =
1886
+ payload.assistantMessageId ?? stableMessageIds.assistantMessageId;
1887
+ const userMsg = userMessageId
1888
+ ? repo.messages.find((message: any) => message?.id === userMessageId)
1889
+ : undefined;
1890
+ let assistantMsg = assistantMessageId
1891
+ ? repo.messages.find((message: any) => message?.id === assistantMessageId)
1892
+ : undefined;
1893
+ const createdAt = payload.deliveredAt ?? new Date().toISOString();
1894
+ if (!userMsg) {
1895
+ repo.messages.push({
1896
+ id: userMessageId,
1897
+ role: "user",
1898
+ content: [{ type: "text", text: payload.incoming.text }],
1899
+ createdAt,
1900
+ });
1901
+ }
1902
+ if (!assistantMsg) {
1903
+ assistantMsg = {
1904
+ id: assistantMessageId,
1905
+ role: "assistant",
1906
+ content: [{ type: "text", text: payload.message.text }],
1907
+ createdAt,
1908
+ metadata: {
1909
+ integrationDeliveryAttempted: true,
1910
+ ...(payload.artifacts?.length
1911
+ ? { integrationArtifacts: payload.artifacts }
1912
+ : {}),
1913
+ },
1914
+ };
1915
+ repo.messages.push(assistantMsg);
1916
+ }
1917
+ if (!assistantMsg.metadata || typeof assistantMsg.metadata !== "object") {
1918
+ assistantMsg.metadata = {};
1919
+ }
1920
+ assistantMsg.metadata.integrationDeliveryAttempted = true;
1921
+ assistantMsg.metadata.integrationDelivery = {
1922
+ platform: payload.incoming.platform,
1923
+ status: "delivered",
1924
+ text: payload.message.text,
1925
+ deliveredAt: payload.deliveredAt ?? new Date().toISOString(),
1926
+ ...(receipt.messageRefs?.length
1927
+ ? { messageRefs: receipt.messageRefs }
1928
+ : {}),
1929
+ };
1930
+
1931
+ const meta = extractThreadMeta(repo);
1932
+ await updateThreadData(
1933
+ payload.internalThreadId,
1934
+ JSON.stringify(repo),
1935
+ meta.title || thread.title || "Integration Chat",
1936
+ meta.preview || thread.preview || "",
1937
+ repo.messages.length,
1938
+ );
1636
1939
  }
@@ -697,7 +697,10 @@ const messages = {
697
697
  configure: "Configure",
698
698
  connect: "Connect",
699
699
  connectWithOAuth: "Connect with OAuth",
700
+ useApiToken: "Use API token",
700
701
  connectSuggestion: "Connect {{name}} to use this link in chat",
702
+ connectSuggestionWithApiToken:
703
+ "Connect {{name}} with an API token to use this link in chat",
701
704
  dismissSuggestion: "Dismiss integration suggestion",
702
705
  backToIntegrations: "Back to integrations",
703
706
  customTitle: "Add custom MCP server",
@@ -792,7 +795,7 @@ const messages = {
792
795
  "Bring Figma design context and canvas actions into an agent.",
793
796
  useCase: "Design files, components, variables, design systems, canvas",
794
797
  setupNote:
795
- "Figma only allows clients listed in its MCP Catalog. Agent Native needs vendor approval before this remote endpoint can complete a connection; use the existing Figma OAuth connector for app-owned workflows in the meantime.",
798
+ "Figma MCP only allows clients listed in Figma's MCP Catalog, so this remote endpoint cannot connect from Agent Native yet. Use the Figma REST API fallback with a personal access token for reading file and node context; MCP canvas actions remain unavailable until Figma approves Agent Native.",
796
799
  },
797
800
  canva: {
798
801
  description: "Search, create, and update Canva designs and assets.",
@@ -197,4 +197,32 @@ export function registerFrameworkSecrets(): void {
197
197
  required: false,
198
198
  });
199
199
  }
200
+
201
+ if (!getRequiredSecret("FIGMA_ACCESS_TOKEN")) {
202
+ registerRequiredSecret({
203
+ key: "FIGMA_ACCESS_TOKEN",
204
+ label: "Figma access token",
205
+ description:
206
+ "Optional fallback for reading Figma file and node context when the hosted Figma MCP server is unavailable. Generate a personal access token with current_user:read and file_content:read.",
207
+ docsUrl:
208
+ "https://developers.figma.com/docs/rest-api/personal-access-tokens/",
209
+ scope: "user",
210
+ kind: "api-key",
211
+ required: false,
212
+ validator: async (value) => {
213
+ const response = await fetch("https://api.figma.com/v1/me", {
214
+ headers: {
215
+ "X-Figma-Token": value,
216
+ "User-Agent": "AgentNative/1.0",
217
+ },
218
+ });
219
+ return response.ok
220
+ ? { ok: true }
221
+ : {
222
+ ok: false,
223
+ error: `Figma rejected the token (HTTP ${response.status}).`,
224
+ };
225
+ },
226
+ });
227
+ }
200
228
  }