@agent-native/core 0.122.2 → 0.122.4

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 (112) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/run-loop-with-resume.ts +12 -0
  5. package/corpus/core/src/cli/index.ts +2 -1
  6. package/corpus/core/src/cli/mcp.ts +13 -3
  7. package/corpus/core/src/cli/skills-content/rewind-skill.ts +36 -4
  8. package/corpus/core/src/cli/skills.ts +250 -37
  9. package/corpus/core/src/cli/telemetry-routing.ts +57 -0
  10. package/corpus/core/src/cli/telemetry.ts +3 -3
  11. package/corpus/core/src/integrations/a2a-continuation-processor.ts +182 -0
  12. package/corpus/core/src/integrations/a2a-continuations-store.ts +122 -12
  13. package/corpus/core/src/integrations/integration-campaign-recovery.ts +127 -0
  14. package/corpus/core/src/integrations/integration-campaigns-store.ts +1039 -0
  15. package/corpus/core/src/integrations/integration-durable-dispatch.ts +28 -3
  16. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +8 -0
  17. package/corpus/core/src/integrations/plugin.ts +453 -35
  18. package/corpus/core/src/integrations/webhook-handler.ts +469 -11
  19. package/corpus/core/src/mcp/screen-memory-stdio.ts +21 -2
  20. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +7 -3
  21. package/corpus/templates/content/app/components/editor/body-hydration.ts +10 -0
  22. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -0
  23. package/corpus/templates/content/app/root.tsx +12 -1
  24. package/corpus/templates/content/changelog/2026-07-23-new-pages-open-immediately-while-slow-network-work-continues.md +6 -0
  25. package/corpus/templates/content/changelog/2026-07-25-database-preview-actions-open-reliably.md +6 -0
  26. package/corpus/templates/content/e2e/playwright.config.ts +1 -1
  27. package/dist/agent/run-loop-with-resume.d.ts +7 -1
  28. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  29. package/dist/agent/run-loop-with-resume.js +7 -0
  30. package/dist/agent/run-loop-with-resume.js.map +1 -1
  31. package/dist/cli/index.js +3 -1
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/mcp.d.ts.map +1 -1
  34. package/dist/cli/mcp.js +12 -3
  35. package/dist/cli/mcp.js.map +1 -1
  36. package/dist/cli/skills-content/rewind-skill.d.ts +1 -1
  37. package/dist/cli/skills-content/rewind-skill.d.ts.map +1 -1
  38. package/dist/cli/skills-content/rewind-skill.js +36 -4
  39. package/dist/cli/skills-content/rewind-skill.js.map +1 -1
  40. package/dist/cli/skills.d.ts +2 -0
  41. package/dist/cli/skills.d.ts.map +1 -1
  42. package/dist/cli/skills.js +191 -40
  43. package/dist/cli/skills.js.map +1 -1
  44. package/dist/cli/telemetry-routing.d.ts +2 -0
  45. package/dist/cli/telemetry-routing.d.ts.map +1 -0
  46. package/dist/cli/telemetry-routing.js +54 -0
  47. package/dist/cli/telemetry-routing.js.map +1 -0
  48. package/dist/cli/telemetry.js +3 -3
  49. package/dist/cli/telemetry.js.map +1 -1
  50. package/dist/collab/awareness.d.ts +2 -2
  51. package/dist/collab/awareness.d.ts.map +1 -1
  52. package/dist/collab/routes.d.ts +1 -1
  53. package/dist/collab/struct-routes.d.ts +1 -1
  54. package/dist/file-upload/actions/upload-image.d.ts +3 -3
  55. package/dist/integrations/a2a-continuation-processor.d.ts +14 -0
  56. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  57. package/dist/integrations/a2a-continuation-processor.js +134 -1
  58. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  59. package/dist/integrations/a2a-continuations-store.d.ts +23 -0
  60. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  61. package/dist/integrations/a2a-continuations-store.js +99 -13
  62. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  63. package/dist/integrations/integration-campaign-recovery.d.ts +16 -0
  64. package/dist/integrations/integration-campaign-recovery.d.ts.map +1 -0
  65. package/dist/integrations/integration-campaign-recovery.js +94 -0
  66. package/dist/integrations/integration-campaign-recovery.js.map +1 -0
  67. package/dist/integrations/integration-campaigns-store.d.ts +121 -0
  68. package/dist/integrations/integration-campaigns-store.d.ts.map +1 -0
  69. package/dist/integrations/integration-campaigns-store.js +784 -0
  70. package/dist/integrations/integration-campaigns-store.js.map +1 -0
  71. package/dist/integrations/integration-durable-dispatch.d.ts +3 -0
  72. package/dist/integrations/integration-durable-dispatch.d.ts.map +1 -1
  73. package/dist/integrations/integration-durable-dispatch.js +22 -3
  74. package/dist/integrations/integration-durable-dispatch.js.map +1 -1
  75. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  76. package/dist/integrations/pending-tasks-retry-job.js +6 -0
  77. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  78. package/dist/integrations/plugin.d.ts.map +1 -1
  79. package/dist/integrations/plugin.js +325 -18
  80. package/dist/integrations/plugin.js.map +1 -1
  81. package/dist/integrations/webhook-handler.d.ts +16 -1
  82. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  83. package/dist/integrations/webhook-handler.js +368 -13
  84. package/dist/integrations/webhook-handler.js.map +1 -1
  85. package/dist/mcp/screen-memory-stdio.d.ts +2 -0
  86. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  87. package/dist/mcp/screen-memory-stdio.js +12 -2
  88. package/dist/mcp/screen-memory-stdio.js.map +1 -1
  89. package/dist/notifications/routes.d.ts +3 -3
  90. package/dist/observability/routes.d.ts +1 -1
  91. package/dist/progress/routes.d.ts +1 -1
  92. package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
  93. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  94. package/dist/resources/handlers.d.ts +1 -1
  95. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  96. package/package.json +1 -1
  97. package/src/agent/run-loop-with-resume.ts +12 -0
  98. package/src/cli/index.ts +2 -1
  99. package/src/cli/mcp.ts +13 -3
  100. package/src/cli/skills-content/rewind-skill.ts +36 -4
  101. package/src/cli/skills.ts +250 -37
  102. package/src/cli/telemetry-routing.ts +57 -0
  103. package/src/cli/telemetry.ts +3 -3
  104. package/src/integrations/a2a-continuation-processor.ts +182 -0
  105. package/src/integrations/a2a-continuations-store.ts +122 -12
  106. package/src/integrations/integration-campaign-recovery.ts +127 -0
  107. package/src/integrations/integration-campaigns-store.ts +1039 -0
  108. package/src/integrations/integration-durable-dispatch.ts +28 -3
  109. package/src/integrations/pending-tasks-retry-job.ts +8 -0
  110. package/src/integrations/plugin.ts +453 -35
  111. package/src/integrations/webhook-handler.ts +469 -11
  112. package/src/mcp/screen-memory-stdio.ts +21 -2
@@ -36,8 +36,12 @@ import { runWithRequestContext } from "../server/request-context.js";
36
36
  import {
37
37
  processA2AContinuationById,
38
38
  processDueA2AContinuations,
39
+ recoverDueA2AContinuations,
39
40
  } from "./a2a-continuation-processor.js";
40
- import { failA2AContinuation } from "./a2a-continuations-store.js";
41
+ import {
42
+ failA2AContinuation,
43
+ hasActiveA2AContinuationsForIntegrationTask,
44
+ } from "./a2a-continuations-store.js";
41
45
  import { mergeIntegrationAdapters } from "./adapter-overrides.js";
42
46
  import { discordAdapter } from "./adapters/discord.js";
43
47
  import { emailAdapter } from "./adapters/email.js";
@@ -69,11 +73,27 @@ import {
69
73
  updateIntegrationInstallation,
70
74
  upsertIntegrationInstallation,
71
75
  } from "./installations-store.js";
76
+ import { recoverDueIntegrationCampaigns } from "./integration-campaign-recovery.js";
77
+ import {
78
+ claimIntegrationCampaignDeliveryForTask,
79
+ completeIntegrationCampaignTaskAfterA2A,
80
+ completeIntegrationCampaignTask,
81
+ failIntegrationCampaignTaskDeliveryContainment,
82
+ failDisabledIntegrationCampaignTask,
83
+ failIntegrationCampaign,
84
+ refreshIntegrationCampaignTaskA2AReceiptRetry,
85
+ terminalizeIntegrationCampaignForTask,
86
+ transitionIntegrationCampaignTaskToA2AReceiptRetry,
87
+ transitionIntegrationCampaignTaskToDeliveryRetry,
88
+ waitForA2AIntegrationCampaign,
89
+ } from "./integration-campaigns-store.js";
72
90
  import {
73
91
  dispatchPendingIntegrationTask,
92
+ INTEGRATION_CAMPAIGN_PROCESSOR_FIELD,
74
93
  INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT,
75
94
  integrationDispatchScopeValue,
76
95
  isIntegrationDurableDispatchConfigured,
96
+ isIntegrationDurableDispatchEnabledForTask,
77
97
  } from "./integration-durable-dispatch.js";
78
98
  import {
79
99
  forgetIntegrationMemory,
@@ -88,8 +108,8 @@ import {
88
108
  } from "./pending-tasks-retry-job.js";
89
109
  import {
90
110
  claimPendingTask,
91
- failTaskDeliveryTransition,
92
111
  getNextPendingTaskForThread,
112
+ getPendingTask,
93
113
  insertPendingTask,
94
114
  isDuplicateEventError,
95
115
  MAX_PENDING_TASK_ATTEMPTS,
@@ -98,6 +118,7 @@ import {
98
118
  markTaskFailed,
99
119
  markTaskRetryable,
100
120
  stageTaskDeliveryPayload,
121
+ type PendingTask,
101
122
  } from "./pending-tasks-store.js";
102
123
  import {
103
124
  claimNextComputerCommand,
@@ -172,6 +193,136 @@ import {
172
193
  type NitroPluginDef = (nitroApp: any) => void | Promise<void>;
173
194
 
174
195
  let a2aContinuationRetryInterval: ReturnType<typeof setInterval> | null = null;
196
+ const INTEGRATION_DELIVERY_LEASE_MS = 2 * 60_000;
197
+
198
+ async function checkpointIntegrationDeliveryRetry(
199
+ task: PendingTask,
200
+ payload: string,
201
+ errorMessage: string,
202
+ event: unknown,
203
+ campaignLease?: {
204
+ campaignId: string;
205
+ runId: string;
206
+ leaseToken: string;
207
+ campaignStatus: "completed" | "failed" | "waiting-a2a";
208
+ },
209
+ ): Promise<"requeued" | "superseded"> {
210
+ let terminalStatus: "completed" | "failed" | undefined;
211
+ let confirmedReceipt = false;
212
+ let awaitingA2ACompletion = false;
213
+ try {
214
+ const parsed = JSON.parse(
215
+ payload,
216
+ ) as Partial<IntegrationResponseDeliveryTaskPayload>;
217
+ terminalStatus = parsed.campaignTerminalStatus;
218
+ confirmedReceipt = parsed.deliveryReceipt?.status === "delivered";
219
+ awaitingA2ACompletion = parsed.awaitingA2ACompletion === true;
220
+ } catch {}
221
+ if (awaitingA2ACompletion) {
222
+ if (campaignLease && campaignLease.campaignStatus !== "waiting-a2a") {
223
+ throw new Error("A2A receipt retry lease has the wrong custody mode");
224
+ }
225
+ const transitioned = campaignLease
226
+ ? await transitionIntegrationCampaignTaskToA2AReceiptRetry(task.id, {
227
+ payload,
228
+ errorMessage,
229
+ campaignId: campaignLease.campaignId,
230
+ runId: campaignLease.runId,
231
+ leaseToken: campaignLease.leaseToken,
232
+ nextRunAt: Date.now() + 15_000,
233
+ })
234
+ : await refreshIntegrationCampaignTaskA2AReceiptRetry(task.id, {
235
+ payload,
236
+ errorMessage,
237
+ });
238
+ if (!transitioned) return "superseded";
239
+ await dispatchPendingIntegrationTask({
240
+ taskId: task.id,
241
+ task: {
242
+ platform: task.platform,
243
+ externalThreadId: task.externalThreadId,
244
+ platformContext: task.dispatchScope
245
+ ? { channelId: task.dispatchScope }
246
+ : undefined,
247
+ },
248
+ event,
249
+ baseUrl: getBaseUrl(event),
250
+ campaignContinuation: true,
251
+ allowPortableConfirmedReceiptReconciliation: confirmedReceipt,
252
+ });
253
+ return "requeued";
254
+ }
255
+ if (terminalStatus && !campaignLease && !confirmedReceipt) {
256
+ throw new Error("Campaign delivery retry is missing its lease");
257
+ }
258
+ if (terminalStatus && campaignLease) {
259
+ if (terminalStatus !== campaignLease.campaignStatus) {
260
+ throw new Error(
261
+ "Campaign delivery retry status does not match its lease",
262
+ );
263
+ }
264
+ const transitioned = await transitionIntegrationCampaignTaskToDeliveryRetry(
265
+ task.id,
266
+ {
267
+ payload,
268
+ errorMessage,
269
+ campaignStatus: campaignLease.campaignStatus,
270
+ campaignId: campaignLease.campaignId,
271
+ runId: campaignLease.runId,
272
+ leaseToken: campaignLease.leaseToken,
273
+ },
274
+ );
275
+ if (!transitioned) {
276
+ return "superseded";
277
+ }
278
+ } else {
279
+ await markTaskDeliveryRetryable(task.id, payload, errorMessage);
280
+ }
281
+ await dispatchPendingIntegrationTask({
282
+ taskId: task.id,
283
+ task: {
284
+ platform: task.platform,
285
+ externalThreadId: task.externalThreadId,
286
+ platformContext: task.dispatchScope
287
+ ? { channelId: task.dispatchScope }
288
+ : undefined,
289
+ },
290
+ event,
291
+ baseUrl: getBaseUrl(event),
292
+ });
293
+ return "requeued";
294
+ }
295
+
296
+ async function containFailedDeliveryTransition(
297
+ task: PendingTask,
298
+ errorMessage: string,
299
+ event: unknown,
300
+ ): Promise<void> {
301
+ const contained = await failIntegrationCampaignTaskDeliveryContainment(
302
+ task.id,
303
+ errorMessage,
304
+ );
305
+ if (!contained) {
306
+ throw new Error("Delivery containment lost pending-task custody");
307
+ }
308
+ const nextTask = await getNextPendingTaskForThread(
309
+ task.platform,
310
+ task.externalThreadId,
311
+ );
312
+ if (!nextTask) return;
313
+ await dispatchPendingIntegrationTask({
314
+ taskId: nextTask.id,
315
+ task: {
316
+ platform: task.platform,
317
+ externalThreadId: task.externalThreadId,
318
+ platformContext: nextTask.dispatchScope
319
+ ? { channelId: nextTask.dispatchScope }
320
+ : undefined,
321
+ },
322
+ event,
323
+ baseUrl: getBaseUrl(event),
324
+ });
325
+ }
175
326
 
176
327
  function startA2AContinuationRetryJob(
177
328
  adapters: Map<string, PlatformAdapter>,
@@ -1627,12 +1778,41 @@ export function createIntegrationsPlugin(
1627
1778
  if (!isIntegrationDurableDispatchConfigured()) {
1628
1779
  return { ok: true, disabled: true };
1629
1780
  }
1630
- const result = await retryStuckPendingTasks({
1631
- webhookBaseUrl: getBaseUrl(event),
1632
- limit: 20,
1633
- durableOnly: true,
1634
- });
1635
- return { ok: true, ...result };
1781
+ const webhookBaseUrl = getBaseUrl(event);
1782
+ const [pendingTasks, campaigns, a2aContinuations] = await Promise.all([
1783
+ retryStuckPendingTasks({
1784
+ webhookBaseUrl,
1785
+ limit: 20,
1786
+ durableOnly: true,
1787
+ }).catch((error) => {
1788
+ console.error(
1789
+ "[integrations] Pending-task recovery failed:",
1790
+ error,
1791
+ );
1792
+ return { error: "pending-task-recovery-failed" };
1793
+ }),
1794
+ recoverDueIntegrationCampaigns({
1795
+ event,
1796
+ webhookBaseUrl,
1797
+ limit: 20,
1798
+ }).catch((error) => {
1799
+ console.error("[integrations] Campaign recovery failed:", error);
1800
+ return { error: "campaign-recovery-failed" };
1801
+ }),
1802
+ recoverDueA2AContinuations({
1803
+ webhookBaseUrl,
1804
+ limit: 10,
1805
+ }).catch((error) => {
1806
+ console.error("[integrations] A2A recovery failed:", error);
1807
+ return { error: "a2a-recovery-failed" };
1808
+ }),
1809
+ ]);
1810
+ return {
1811
+ ok: true,
1812
+ pendingTasks,
1813
+ campaigns,
1814
+ a2aContinuations,
1815
+ };
1636
1816
  }),
1637
1817
  );
1638
1818
 
@@ -1653,6 +1833,7 @@ export function createIntegrationsPlugin(
1653
1833
  const body = (await readBody(event)) as {
1654
1834
  taskId?: string;
1655
1835
  [AGENT_BACKGROUND_PROCESSOR_FIELD]?: string;
1836
+ [INTEGRATION_CAMPAIGN_PROCESSOR_FIELD]?: boolean;
1656
1837
  };
1657
1838
  const taskId = body?.taskId;
1658
1839
  if (!taskId) {
@@ -1695,17 +1876,82 @@ export function createIntegrationsPlugin(
1695
1876
  AGENT_BACKGROUND_PROCESSOR_INTEGRATION
1696
1877
  ? "background-acknowledged"
1697
1878
  : "portable-unconfirmed";
1698
- const task = await claimPendingTask(taskId, { dispatchOutcome });
1879
+ const campaignContinuation =
1880
+ body[INTEGRATION_CAMPAIGN_PROCESSOR_FIELD] === true;
1881
+ const task = campaignContinuation
1882
+ ? await getPendingTask(taskId)
1883
+ : await claimPendingTask(taskId, { dispatchOutcome });
1699
1884
  if (!task) {
1700
1885
  setResponseStatus(event, 200);
1701
1886
  return { ok: true, skipped: "already-claimed-or-missing" };
1702
1887
  }
1888
+ if (campaignContinuation && task.status !== "processing") {
1889
+ setResponseStatus(event, 200);
1890
+ return { ok: true, skipped: "campaign-task-not-processing" };
1891
+ }
1892
+ let taskPayload:
1893
+ | IntegrationSystemNoticeTaskPayload
1894
+ | IntegrationResponseDeliveryTaskPayload
1895
+ | { kind?: undefined };
1896
+ try {
1897
+ taskPayload = JSON.parse(task.payload) as typeof taskPayload;
1898
+ } catch {
1899
+ await markTaskFailed(task.id, "Invalid integration task payload");
1900
+ setResponseStatus(event, 400);
1901
+ return { error: "Invalid integration task payload" };
1902
+ }
1903
+ const durableCampaignEnabled =
1904
+ isIntegrationDurableDispatchEnabledForTask({
1905
+ platform: task.platform,
1906
+ externalThreadId: task.externalThreadId,
1907
+ platformContext: task.dispatchScope
1908
+ ? { channelId: task.dispatchScope }
1909
+ : undefined,
1910
+ });
1911
+ const confirmedDeliveryReceipt =
1912
+ taskPayload.kind === "response-delivery" &&
1913
+ taskPayload.deliveryReceipt?.status === "delivered";
1914
+ if (
1915
+ campaignContinuation &&
1916
+ !durableCampaignEnabled &&
1917
+ !confirmedDeliveryReceipt
1918
+ ) {
1919
+ await failDisabledIntegrationCampaignTask(task.id);
1920
+ const nextTask = await getNextPendingTaskForThread(
1921
+ task.platform,
1922
+ task.externalThreadId,
1923
+ );
1924
+ if (nextTask) {
1925
+ await dispatchPendingIntegrationTask({
1926
+ taskId: nextTask.id,
1927
+ task: {
1928
+ platform: task.platform,
1929
+ externalThreadId: task.externalThreadId,
1930
+ platformContext: nextTask.dispatchScope
1931
+ ? { channelId: nextTask.dispatchScope }
1932
+ : undefined,
1933
+ },
1934
+ event,
1935
+ baseUrl: getBaseUrl(event),
1936
+ });
1937
+ }
1938
+ setResponseStatus(event, 200);
1939
+ return { ok: true, failed: "campaign-disabled" };
1940
+ }
1703
1941
 
1704
1942
  let deliveryRetryTransitionStarted = false;
1705
1943
  let deliveryRetryRecovery:
1706
1944
  | { payload: string; errorMessage: string }
1707
1945
  | undefined;
1708
1946
  let confirmedDeliveryRetryPayload: string | undefined;
1947
+ let campaignDeliveryLease:
1948
+ | {
1949
+ campaignId: string;
1950
+ runId: string;
1951
+ leaseToken: string;
1952
+ campaignStatus: "completed" | "failed" | "waiting-a2a";
1953
+ }
1954
+ | undefined;
1709
1955
  try {
1710
1956
  const adapter = adapterMap.get(task.platform);
1711
1957
  if (!adapter) {
@@ -1720,11 +1966,10 @@ export function createIntegrationsPlugin(
1720
1966
  isIntegrationCaller: true,
1721
1967
  },
1722
1968
  async () => {
1723
- const taskPayload = JSON.parse(task.payload) as
1724
- | IntegrationSystemNoticeTaskPayload
1725
- | IntegrationResponseDeliveryTaskPayload
1726
- | { kind?: undefined };
1727
- if (taskPayload.kind === "system-notice") {
1969
+ if (
1970
+ !campaignContinuation &&
1971
+ taskPayload.kind === "system-notice"
1972
+ ) {
1728
1973
  if (!adapter.sendSystemNotice) {
1729
1974
  throw new Error(
1730
1975
  `Platform ${task.platform} cannot deliver system notices`,
@@ -1752,6 +1997,33 @@ export function createIntegrationsPlugin(
1752
1997
  if (taskPayload.kind === "response-delivery") {
1753
1998
  let receipt: void | PlatformDeliveryReceipt =
1754
1999
  taskPayload.deliveryReceipt;
2000
+ let deliveryLease:
2001
+ | { campaignId: string; runId: string; leaseToken: string }
2002
+ | undefined;
2003
+ if (campaignContinuation && !receipt) {
2004
+ const runId = `integration-delivery-${crypto.randomUUID()}`;
2005
+ const leaseToken = crypto.randomUUID();
2006
+ const deliveryClaim =
2007
+ await claimIntegrationCampaignDeliveryForTask(task.id, {
2008
+ runId,
2009
+ leaseToken,
2010
+ leaseDurationMs: INTEGRATION_DELIVERY_LEASE_MS,
2011
+ });
2012
+ if (!deliveryClaim) return "campaign-active" as const;
2013
+ deliveryLease = {
2014
+ campaignId: deliveryClaim.id,
2015
+ runId,
2016
+ leaseToken,
2017
+ };
2018
+ campaignDeliveryLease = {
2019
+ ...deliveryLease,
2020
+ campaignStatus:
2021
+ taskPayload.campaignTerminalStatus ??
2022
+ (taskPayload.awaitingA2ACompletion
2023
+ ? "waiting-a2a"
2024
+ : "completed"),
2025
+ };
2026
+ }
1755
2027
  if (!receipt) {
1756
2028
  receipt = await adapter.sendResponse(
1757
2029
  taskPayload.message,
@@ -1793,6 +2065,70 @@ export function createIntegrationsPlugin(
1793
2065
  deliveredPayload,
1794
2066
  receipt,
1795
2067
  );
2068
+ const campaignTerminalStatus =
2069
+ taskPayload.campaignTerminalStatus;
2070
+ if (campaignTerminalStatus) {
2071
+ const errorMessage =
2072
+ "Integration campaign exhausted its continuation limit";
2073
+ const terminalized = deliveryLease
2074
+ ? campaignTerminalStatus === "failed"
2075
+ ? await failIntegrationCampaign(
2076
+ deliveryLease.campaignId,
2077
+ {
2078
+ runId: deliveryLease.runId,
2079
+ leaseToken: deliveryLease.leaseToken,
2080
+ errorMessage,
2081
+ },
2082
+ )
2083
+ : await completeIntegrationCampaignTask(
2084
+ deliveryLease.campaignId,
2085
+ {
2086
+ integrationTaskId: task.id,
2087
+ runId: deliveryLease.runId,
2088
+ leaseToken: deliveryLease.leaseToken,
2089
+ },
2090
+ )
2091
+ : await terminalizeIntegrationCampaignForTask(task.id, {
2092
+ status: campaignTerminalStatus,
2093
+ ...(campaignTerminalStatus === "failed"
2094
+ ? { errorMessage }
2095
+ : {}),
2096
+ });
2097
+ if (deliveryLease && !terminalized) {
2098
+ return "campaign-active" as const;
2099
+ }
2100
+ return campaignTerminalStatus === "failed"
2101
+ ? ("campaign-failed" as const)
2102
+ : ("completed" as const);
2103
+ }
2104
+ if (taskPayload.awaitingA2ACompletion) {
2105
+ if (deliveryLease) {
2106
+ const waiting = await waitForA2AIntegrationCampaign(
2107
+ deliveryLease.campaignId,
2108
+ {
2109
+ runId: deliveryLease.runId,
2110
+ leaseToken: deliveryLease.leaseToken,
2111
+ nextRunAt: Date.now() + 15_000,
2112
+ },
2113
+ );
2114
+ if (!waiting) return "campaign-active" as const;
2115
+ }
2116
+ if (
2117
+ !(await hasActiveA2AContinuationsForIntegrationTask(
2118
+ task.id,
2119
+ ))
2120
+ ) {
2121
+ const completed =
2122
+ await completeIntegrationCampaignTaskAfterA2A(task.id);
2123
+ return completed
2124
+ ? ("completed" as const)
2125
+ : ("campaign-active" as const);
2126
+ }
2127
+ return "campaign-active" as const;
2128
+ }
2129
+ if (campaignContinuation) {
2130
+ return "campaign-active" as const;
2131
+ }
1796
2132
  return;
1797
2133
  }
1798
2134
  const resources = await loadResourcesForPrompt(
@@ -1801,26 +2137,47 @@ export function createIntegrationsPlugin(
1801
2137
  options?.appId,
1802
2138
  task.orgId,
1803
2139
  );
1804
- const result = await processIntegrationTask(task, {
1805
- adapter,
1806
- systemPrompt: baseSystemPrompt + resources,
1807
- actions,
1808
- initialToolNames,
1809
- model,
1810
- apiKey: getApiKey(),
1811
- engine: options?.engine,
1812
- ownerEmail: task.ownerEmail,
1813
- appId: options?.appId,
1814
- });
2140
+ const result = await processIntegrationTask(
2141
+ task,
2142
+ {
2143
+ adapter,
2144
+ systemPrompt: baseSystemPrompt + resources,
2145
+ actions,
2146
+ initialToolNames,
2147
+ model,
2148
+ apiKey: getApiKey(),
2149
+ engine: options?.engine,
2150
+ ownerEmail: task.ownerEmail,
2151
+ appId: options?.appId,
2152
+ },
2153
+ {
2154
+ enabled: durableCampaignEnabled,
2155
+ continuationInvocation: campaignContinuation,
2156
+ },
2157
+ );
1815
2158
  if (result?.status === "delivery-pending") {
1816
2159
  deliveryRetryTransitionStarted = true;
1817
- await markTaskDeliveryRetryable(
1818
- task.id,
2160
+ const checkpoint = await checkpointIntegrationDeliveryRetry(
2161
+ task,
1819
2162
  JSON.stringify(result.payload),
1820
2163
  result.errorMessage,
2164
+ event,
2165
+ result.campaignLease,
1821
2166
  );
2167
+ if (checkpoint === "superseded") {
2168
+ return "campaign-active" as const;
2169
+ }
1822
2170
  return "delivery-retry" as const;
1823
2171
  }
2172
+ if (
2173
+ result?.status === "campaign-pending" ||
2174
+ result?.status === "campaign-active"
2175
+ ) {
2176
+ return "campaign-active" as const;
2177
+ }
2178
+ if (result?.status === "campaign-failed") {
2179
+ return "campaign-failed" as const;
2180
+ }
1824
2181
  return "completed" as const;
1825
2182
  },
1826
2183
  );
@@ -1828,6 +2185,36 @@ export function createIntegrationsPlugin(
1828
2185
  setResponseStatus(event, 202);
1829
2186
  return { ok: true, taskId, retrying: "response-delivery" };
1830
2187
  }
2188
+ if (processingResult === "campaign-active") {
2189
+ setResponseStatus(event, 202);
2190
+ return { ok: true, taskId, continuing: true };
2191
+ }
2192
+ if (processingResult === "campaign-failed") {
2193
+ await markTaskFailed(
2194
+ taskId,
2195
+ "Integration campaign exhausted its continuation limit",
2196
+ );
2197
+ const nextTask = await getNextPendingTaskForThread(
2198
+ task.platform,
2199
+ task.externalThreadId,
2200
+ );
2201
+ if (nextTask) {
2202
+ await dispatchPendingIntegrationTask({
2203
+ taskId: nextTask.id,
2204
+ task: {
2205
+ platform: task.platform,
2206
+ externalThreadId: task.externalThreadId,
2207
+ platformContext: nextTask.dispatchScope
2208
+ ? { channelId: nextTask.dispatchScope }
2209
+ : undefined,
2210
+ },
2211
+ event,
2212
+ baseUrl: getBaseUrl(event),
2213
+ });
2214
+ }
2215
+ setResponseStatus(event, 200);
2216
+ return { ok: true, taskId, failed: "campaign-exhausted" };
2217
+ }
1831
2218
  await markTaskCompleted(taskId);
1832
2219
  const nextTask = await getNextPendingTaskForThread(
1833
2220
  task.platform,
@@ -1863,11 +2250,17 @@ export function createIntegrationsPlugin(
1863
2250
  : "processor failed";
1864
2251
  if (deliveryRetryRecovery) {
1865
2252
  try {
1866
- await markTaskDeliveryRetryable(
1867
- taskId,
2253
+ const checkpoint = await checkpointIntegrationDeliveryRetry(
2254
+ task,
1868
2255
  deliveryRetryRecovery.payload,
1869
2256
  `${deliveryRetryRecovery.errorMessage}: ${errorMessage}`,
2257
+ event,
2258
+ campaignDeliveryLease,
1870
2259
  );
2260
+ if (checkpoint === "superseded") {
2261
+ setResponseStatus(event, 202);
2262
+ return { ok: true, taskId, continuing: true };
2263
+ }
1871
2264
  setResponseStatus(event, 202);
1872
2265
  return { ok: true, taskId, retrying: "response-delivery" };
1873
2266
  } catch (transitionError) {
@@ -1875,9 +2268,10 @@ export function createIntegrationsPlugin(
1875
2268
  transitionError instanceof Error
1876
2269
  ? transitionError.message
1877
2270
  : String(transitionError);
1878
- await failTaskDeliveryTransition(
1879
- taskId,
2271
+ await containFailedDeliveryTransition(
2272
+ task,
1880
2273
  `Could not safely checkpoint the delivery receipt: ${transitionMessage}`,
2274
+ event,
1881
2275
  ).catch((failureTransitionError) => {
1882
2276
  console.error(
1883
2277
  "[integrations] Failed to contain delivery receipt transition failure:",
@@ -1887,24 +2281,48 @@ export function createIntegrationsPlugin(
1887
2281
  }
1888
2282
  } else if (confirmedDeliveryRetryPayload) {
1889
2283
  try {
1890
- await markTaskDeliveryRetryable(
1891
- taskId,
2284
+ const checkpoint = await checkpointIntegrationDeliveryRetry(
2285
+ task,
1892
2286
  confirmedDeliveryRetryPayload,
1893
2287
  `Provider delivery was confirmed but history persistence failed: ${errorMessage}`,
2288
+ event,
2289
+ campaignDeliveryLease,
1894
2290
  );
2291
+ if (checkpoint === "superseded") {
2292
+ setResponseStatus(event, 202);
2293
+ return { ok: true, taskId, continuing: true };
2294
+ }
1895
2295
  console.error("[integrations] process-task failure:", err);
1896
2296
  setResponseStatus(event, 202);
1897
2297
  return { ok: true, taskId, retrying: "response-delivery" };
1898
2298
  } catch (transitionError) {
2299
+ const transitionMessage =
2300
+ transitionError instanceof Error
2301
+ ? transitionError.message
2302
+ : String(transitionError);
1899
2303
  console.error(
1900
2304
  "[integrations] Failed to requeue confirmed delivery history:",
1901
2305
  transitionError,
1902
2306
  );
2307
+ await containFailedDeliveryTransition(
2308
+ task,
2309
+ `Could not safely checkpoint confirmed delivery history: ${transitionMessage}`,
2310
+ event,
2311
+ ).catch((failureTransitionError) => {
2312
+ console.error(
2313
+ "[integrations] Failed to contain confirmed delivery history transition failure:",
2314
+ failureTransitionError,
2315
+ );
2316
+ });
2317
+ console.error("[integrations] process-task failure:", err);
2318
+ setResponseStatus(event, 500);
2319
+ return { error: "Internal task failed" };
1903
2320
  }
1904
2321
  } else if (deliveryRetryTransitionStarted) {
1905
- await failTaskDeliveryTransition(
1906
- taskId,
2322
+ await containFailedDeliveryTransition(
2323
+ task,
1907
2324
  `Could not safely checkpoint the delivery retry: ${errorMessage}`,
2325
+ event,
1908
2326
  ).catch((transitionError) => {
1909
2327
  console.error(
1910
2328
  "[integrations] Failed to contain delivery retry transition failure:",