@agent-native/core 0.122.2 → 0.122.3

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 (71) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +6 -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/integrations/a2a-continuation-processor.ts +182 -0
  6. package/corpus/core/src/integrations/a2a-continuations-store.ts +122 -12
  7. package/corpus/core/src/integrations/integration-campaign-recovery.ts +127 -0
  8. package/corpus/core/src/integrations/integration-campaigns-store.ts +1039 -0
  9. package/corpus/core/src/integrations/integration-durable-dispatch.ts +28 -3
  10. package/corpus/core/src/integrations/pending-tasks-retry-job.ts +8 -0
  11. package/corpus/core/src/integrations/plugin.ts +453 -35
  12. package/corpus/core/src/integrations/webhook-handler.ts +469 -11
  13. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +7 -3
  14. package/corpus/templates/content/app/components/editor/body-hydration.ts +10 -0
  15. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -0
  16. package/corpus/templates/content/app/root.tsx +12 -1
  17. package/corpus/templates/content/changelog/2026-07-23-new-pages-open-immediately-while-slow-network-work-continues.md +6 -0
  18. package/corpus/templates/content/changelog/2026-07-25-database-preview-actions-open-reliably.md +6 -0
  19. package/corpus/templates/content/e2e/playwright.config.ts +1 -1
  20. package/dist/agent/run-loop-with-resume.d.ts +7 -1
  21. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  22. package/dist/agent/run-loop-with-resume.js +7 -0
  23. package/dist/agent/run-loop-with-resume.js.map +1 -1
  24. package/dist/collab/awareness.d.ts +2 -2
  25. package/dist/collab/awareness.d.ts.map +1 -1
  26. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  27. package/dist/integrations/a2a-continuation-processor.d.ts +14 -0
  28. package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
  29. package/dist/integrations/a2a-continuation-processor.js +134 -1
  30. package/dist/integrations/a2a-continuation-processor.js.map +1 -1
  31. package/dist/integrations/a2a-continuations-store.d.ts +23 -0
  32. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  33. package/dist/integrations/a2a-continuations-store.js +99 -13
  34. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  35. package/dist/integrations/integration-campaign-recovery.d.ts +16 -0
  36. package/dist/integrations/integration-campaign-recovery.d.ts.map +1 -0
  37. package/dist/integrations/integration-campaign-recovery.js +94 -0
  38. package/dist/integrations/integration-campaign-recovery.js.map +1 -0
  39. package/dist/integrations/integration-campaigns-store.d.ts +121 -0
  40. package/dist/integrations/integration-campaigns-store.d.ts.map +1 -0
  41. package/dist/integrations/integration-campaigns-store.js +784 -0
  42. package/dist/integrations/integration-campaigns-store.js.map +1 -0
  43. package/dist/integrations/integration-durable-dispatch.d.ts +3 -0
  44. package/dist/integrations/integration-durable-dispatch.d.ts.map +1 -1
  45. package/dist/integrations/integration-durable-dispatch.js +22 -3
  46. package/dist/integrations/integration-durable-dispatch.js.map +1 -1
  47. package/dist/integrations/pending-tasks-retry-job.d.ts.map +1 -1
  48. package/dist/integrations/pending-tasks-retry-job.js +6 -0
  49. package/dist/integrations/pending-tasks-retry-job.js.map +1 -1
  50. package/dist/integrations/plugin.d.ts.map +1 -1
  51. package/dist/integrations/plugin.js +325 -18
  52. package/dist/integrations/plugin.js.map +1 -1
  53. package/dist/integrations/webhook-handler.d.ts +16 -1
  54. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  55. package/dist/integrations/webhook-handler.js +368 -13
  56. package/dist/integrations/webhook-handler.js.map +1 -1
  57. package/dist/notifications/routes.d.ts +3 -3
  58. package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
  59. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  60. package/dist/resources/handlers.d.ts +1 -1
  61. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  62. package/package.json +1 -1
  63. package/src/agent/run-loop-with-resume.ts +12 -0
  64. package/src/integrations/a2a-continuation-processor.ts +182 -0
  65. package/src/integrations/a2a-continuations-store.ts +122 -12
  66. package/src/integrations/integration-campaign-recovery.ts +127 -0
  67. package/src/integrations/integration-campaigns-store.ts +1039 -0
  68. package/src/integrations/integration-durable-dispatch.ts +28 -3
  69. package/src/integrations/pending-tasks-retry-job.ts +8 -0
  70. package/src/integrations/plugin.ts +453 -35
  71. package/src/integrations/webhook-handler.ts +469 -11
@@ -13,8 +13,8 @@ import { getH3App, markDefaultPluginProvided, } from "../server/framework-reques
13
13
  import { decodeOAuthState, encodeOAuthState, oauthCallbackResponse, oauthErrorPage, resolveOAuthRedirectUri, } from "../server/google-oauth.js";
14
14
  import { readBody } from "../server/h3-helpers.js";
15
15
  import { runWithRequestContext } from "../server/request-context.js";
16
- import { processA2AContinuationById, processDueA2AContinuations, } from "./a2a-continuation-processor.js";
17
- import { failA2AContinuation } from "./a2a-continuations-store.js";
16
+ import { processA2AContinuationById, processDueA2AContinuations, recoverDueA2AContinuations, } from "./a2a-continuation-processor.js";
17
+ import { failA2AContinuation, hasActiveA2AContinuationsForIntegrationTask, } from "./a2a-continuations-store.js";
18
18
  import { mergeIntegrationAdapters } from "./adapter-overrides.js";
19
19
  import { discordAdapter } from "./adapters/discord.js";
20
20
  import { emailAdapter } from "./adapters/email.js";
@@ -30,11 +30,13 @@ import { claimIntegrationControl } from "./controls-store.js";
30
30
  import { startGoogleDocsPoller, handlePushNotification, } from "./google-docs-poller.js";
31
31
  import { IntegrationIdentityDeclinedError, resolveDefaultIntegrationExecutionContext, } from "./identity.js";
32
32
  import { disconnectIntegrationInstallation, listIntegrationInstallations, resolveIntegrationTokenBundle, updateIntegrationInstallation, upsertIntegrationInstallation, } from "./installations-store.js";
33
- import { dispatchPendingIntegrationTask, INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT, integrationDispatchScopeValue, isIntegrationDurableDispatchConfigured, } from "./integration-durable-dispatch.js";
33
+ import { recoverDueIntegrationCampaigns } from "./integration-campaign-recovery.js";
34
+ import { claimIntegrationCampaignDeliveryForTask, completeIntegrationCampaignTaskAfterA2A, completeIntegrationCampaignTask, failIntegrationCampaignTaskDeliveryContainment, failDisabledIntegrationCampaignTask, failIntegrationCampaign, refreshIntegrationCampaignTaskA2AReceiptRetry, terminalizeIntegrationCampaignForTask, transitionIntegrationCampaignTaskToA2AReceiptRetry, transitionIntegrationCampaignTaskToDeliveryRetry, waitForA2AIntegrationCampaign, } from "./integration-campaigns-store.js";
35
+ import { dispatchPendingIntegrationTask, INTEGRATION_CAMPAIGN_PROCESSOR_FIELD, INTEGRATION_RETRY_SWEEP_TOKEN_SUBJECT, integrationDispatchScopeValue, isIntegrationDurableDispatchConfigured, isIntegrationDurableDispatchEnabledForTask, } from "./integration-durable-dispatch.js";
34
36
  import { forgetIntegrationMemory, integrationMemoryActions, listIntegrationMemory, rememberForIntegrationScope, } from "./integration-memory.js";
35
37
  import { extractBearerToken, verifyInternalToken } from "./internal-token.js";
36
38
  import { retryStuckPendingTasks, startPendingTasksRetryJob, } from "./pending-tasks-retry-job.js";
37
- import { claimPendingTask, failTaskDeliveryTransition, getNextPendingTaskForThread, insertPendingTask, isDuplicateEventError, MAX_PENDING_TASK_ATTEMPTS, markTaskCompleted, markTaskDeliveryRetryable, markTaskFailed, markTaskRetryable, stageTaskDeliveryPayload, } from "./pending-tasks-store.js";
39
+ import { claimPendingTask, getNextPendingTaskForThread, getPendingTask, insertPendingTask, isDuplicateEventError, MAX_PENDING_TASK_ATTEMPTS, markTaskCompleted, markTaskDeliveryRetryable, markTaskFailed, markTaskRetryable, stageTaskDeliveryPayload, } from "./pending-tasks-store.js";
38
40
  import { claimNextComputerCommand, claimNextRemoteCommand, enqueueComputerCommand, enqueueRemoteCommand as enqueueRemoteCommandRow, isRemoteCommandKind, listRemoteCommandsForOwner, updateRemoteCommandResult, } from "./remote-commands-store.js";
39
41
  import { authenticateRemoteDeviceToken, createRemoteDevice, getRemoteComputerCapabilities, getRemoteDeviceForOwner, listRemoteDevicesForOwner, revokeRemoteDeviceForOwner, toPublicRemoteDevice, unregisterRemoteDevice, updateRemoteDeviceDetails, } from "./remote-devices-store.js";
40
42
  import { startRemotePushDeliveryJob } from "./remote-push-delivery-job.js";
@@ -48,6 +50,110 @@ import { getTaskQueueStats } from "./task-queue-stats.js";
48
50
  import { listIntegrationUsageBudgets, saveIntegrationUsageBudget, } from "./usage-budget-store.js";
49
51
  import { handleWebhook, processIntegrationTask, recordIntegrationResponseDelivery, } from "./webhook-handler.js";
50
52
  let a2aContinuationRetryInterval = null;
53
+ const INTEGRATION_DELIVERY_LEASE_MS = 2 * 60_000;
54
+ async function checkpointIntegrationDeliveryRetry(task, payload, errorMessage, event, campaignLease) {
55
+ let terminalStatus;
56
+ let confirmedReceipt = false;
57
+ let awaitingA2ACompletion = false;
58
+ try {
59
+ const parsed = JSON.parse(payload);
60
+ terminalStatus = parsed.campaignTerminalStatus;
61
+ confirmedReceipt = parsed.deliveryReceipt?.status === "delivered";
62
+ awaitingA2ACompletion = parsed.awaitingA2ACompletion === true;
63
+ }
64
+ catch { }
65
+ if (awaitingA2ACompletion) {
66
+ if (campaignLease && campaignLease.campaignStatus !== "waiting-a2a") {
67
+ throw new Error("A2A receipt retry lease has the wrong custody mode");
68
+ }
69
+ const transitioned = campaignLease
70
+ ? await transitionIntegrationCampaignTaskToA2AReceiptRetry(task.id, {
71
+ payload,
72
+ errorMessage,
73
+ campaignId: campaignLease.campaignId,
74
+ runId: campaignLease.runId,
75
+ leaseToken: campaignLease.leaseToken,
76
+ nextRunAt: Date.now() + 15_000,
77
+ })
78
+ : await refreshIntegrationCampaignTaskA2AReceiptRetry(task.id, {
79
+ payload,
80
+ errorMessage,
81
+ });
82
+ if (!transitioned)
83
+ return "superseded";
84
+ await dispatchPendingIntegrationTask({
85
+ taskId: task.id,
86
+ task: {
87
+ platform: task.platform,
88
+ externalThreadId: task.externalThreadId,
89
+ platformContext: task.dispatchScope
90
+ ? { channelId: task.dispatchScope }
91
+ : undefined,
92
+ },
93
+ event,
94
+ baseUrl: getBaseUrl(event),
95
+ campaignContinuation: true,
96
+ allowPortableConfirmedReceiptReconciliation: confirmedReceipt,
97
+ });
98
+ return "requeued";
99
+ }
100
+ if (terminalStatus && !campaignLease && !confirmedReceipt) {
101
+ throw new Error("Campaign delivery retry is missing its lease");
102
+ }
103
+ if (terminalStatus && campaignLease) {
104
+ if (terminalStatus !== campaignLease.campaignStatus) {
105
+ throw new Error("Campaign delivery retry status does not match its lease");
106
+ }
107
+ const transitioned = await transitionIntegrationCampaignTaskToDeliveryRetry(task.id, {
108
+ payload,
109
+ errorMessage,
110
+ campaignStatus: campaignLease.campaignStatus,
111
+ campaignId: campaignLease.campaignId,
112
+ runId: campaignLease.runId,
113
+ leaseToken: campaignLease.leaseToken,
114
+ });
115
+ if (!transitioned) {
116
+ return "superseded";
117
+ }
118
+ }
119
+ else {
120
+ await markTaskDeliveryRetryable(task.id, payload, errorMessage);
121
+ }
122
+ await dispatchPendingIntegrationTask({
123
+ taskId: task.id,
124
+ task: {
125
+ platform: task.platform,
126
+ externalThreadId: task.externalThreadId,
127
+ platformContext: task.dispatchScope
128
+ ? { channelId: task.dispatchScope }
129
+ : undefined,
130
+ },
131
+ event,
132
+ baseUrl: getBaseUrl(event),
133
+ });
134
+ return "requeued";
135
+ }
136
+ async function containFailedDeliveryTransition(task, errorMessage, event) {
137
+ const contained = await failIntegrationCampaignTaskDeliveryContainment(task.id, errorMessage);
138
+ if (!contained) {
139
+ throw new Error("Delivery containment lost pending-task custody");
140
+ }
141
+ const nextTask = await getNextPendingTaskForThread(task.platform, task.externalThreadId);
142
+ if (!nextTask)
143
+ return;
144
+ await dispatchPendingIntegrationTask({
145
+ taskId: nextTask.id,
146
+ task: {
147
+ platform: task.platform,
148
+ externalThreadId: task.externalThreadId,
149
+ platformContext: nextTask.dispatchScope
150
+ ? { channelId: nextTask.dispatchScope }
151
+ : undefined,
152
+ },
153
+ event,
154
+ baseUrl: getBaseUrl(event),
155
+ });
156
+ }
51
157
  function startA2AContinuationRetryJob(adapters) {
52
158
  if (a2aContinuationRetryInterval)
53
159
  return;
@@ -1262,12 +1368,38 @@ export function createIntegrationsPlugin(options) {
1262
1368
  if (!isIntegrationDurableDispatchConfigured()) {
1263
1369
  return { ok: true, disabled: true };
1264
1370
  }
1265
- const result = await retryStuckPendingTasks({
1266
- webhookBaseUrl: getBaseUrl(event),
1267
- limit: 20,
1268
- durableOnly: true,
1269
- });
1270
- return { ok: true, ...result };
1371
+ const webhookBaseUrl = getBaseUrl(event);
1372
+ const [pendingTasks, campaigns, a2aContinuations] = await Promise.all([
1373
+ retryStuckPendingTasks({
1374
+ webhookBaseUrl,
1375
+ limit: 20,
1376
+ durableOnly: true,
1377
+ }).catch((error) => {
1378
+ console.error("[integrations] Pending-task recovery failed:", error);
1379
+ return { error: "pending-task-recovery-failed" };
1380
+ }),
1381
+ recoverDueIntegrationCampaigns({
1382
+ event,
1383
+ webhookBaseUrl,
1384
+ limit: 20,
1385
+ }).catch((error) => {
1386
+ console.error("[integrations] Campaign recovery failed:", error);
1387
+ return { error: "campaign-recovery-failed" };
1388
+ }),
1389
+ recoverDueA2AContinuations({
1390
+ webhookBaseUrl,
1391
+ limit: 10,
1392
+ }).catch((error) => {
1393
+ console.error("[integrations] A2A recovery failed:", error);
1394
+ return { error: "a2a-recovery-failed" };
1395
+ }),
1396
+ ]);
1397
+ return {
1398
+ ok: true,
1399
+ pendingTasks,
1400
+ campaigns,
1401
+ a2aContinuations,
1402
+ };
1271
1403
  }));
1272
1404
  // ─── Process pending task (cross-platform task queue) ────────
1273
1405
  // POST /_agent-native/integrations/process-task
@@ -1317,14 +1449,62 @@ export function createIntegrationsPlugin(options) {
1317
1449
  AGENT_BACKGROUND_PROCESSOR_INTEGRATION
1318
1450
  ? "background-acknowledged"
1319
1451
  : "portable-unconfirmed";
1320
- const task = await claimPendingTask(taskId, { dispatchOutcome });
1452
+ const campaignContinuation = body[INTEGRATION_CAMPAIGN_PROCESSOR_FIELD] === true;
1453
+ const task = campaignContinuation
1454
+ ? await getPendingTask(taskId)
1455
+ : await claimPendingTask(taskId, { dispatchOutcome });
1321
1456
  if (!task) {
1322
1457
  setResponseStatus(event, 200);
1323
1458
  return { ok: true, skipped: "already-claimed-or-missing" };
1324
1459
  }
1460
+ if (campaignContinuation && task.status !== "processing") {
1461
+ setResponseStatus(event, 200);
1462
+ return { ok: true, skipped: "campaign-task-not-processing" };
1463
+ }
1464
+ let taskPayload;
1465
+ try {
1466
+ taskPayload = JSON.parse(task.payload);
1467
+ }
1468
+ catch {
1469
+ await markTaskFailed(task.id, "Invalid integration task payload");
1470
+ setResponseStatus(event, 400);
1471
+ return { error: "Invalid integration task payload" };
1472
+ }
1473
+ const durableCampaignEnabled = isIntegrationDurableDispatchEnabledForTask({
1474
+ platform: task.platform,
1475
+ externalThreadId: task.externalThreadId,
1476
+ platformContext: task.dispatchScope
1477
+ ? { channelId: task.dispatchScope }
1478
+ : undefined,
1479
+ });
1480
+ const confirmedDeliveryReceipt = taskPayload.kind === "response-delivery" &&
1481
+ taskPayload.deliveryReceipt?.status === "delivered";
1482
+ if (campaignContinuation &&
1483
+ !durableCampaignEnabled &&
1484
+ !confirmedDeliveryReceipt) {
1485
+ await failDisabledIntegrationCampaignTask(task.id);
1486
+ const nextTask = await getNextPendingTaskForThread(task.platform, task.externalThreadId);
1487
+ if (nextTask) {
1488
+ await dispatchPendingIntegrationTask({
1489
+ taskId: nextTask.id,
1490
+ task: {
1491
+ platform: task.platform,
1492
+ externalThreadId: task.externalThreadId,
1493
+ platformContext: nextTask.dispatchScope
1494
+ ? { channelId: nextTask.dispatchScope }
1495
+ : undefined,
1496
+ },
1497
+ event,
1498
+ baseUrl: getBaseUrl(event),
1499
+ });
1500
+ }
1501
+ setResponseStatus(event, 200);
1502
+ return { ok: true, failed: "campaign-disabled" };
1503
+ }
1325
1504
  let deliveryRetryTransitionStarted = false;
1326
1505
  let deliveryRetryRecovery;
1327
1506
  let confirmedDeliveryRetryPayload;
1507
+ let campaignDeliveryLease;
1328
1508
  try {
1329
1509
  const adapter = adapterMap.get(task.platform);
1330
1510
  if (!adapter) {
@@ -1337,8 +1517,8 @@ export function createIntegrationsPlugin(options) {
1337
1517
  ...(task.orgId ? { orgId: task.orgId } : {}),
1338
1518
  isIntegrationCaller: true,
1339
1519
  }, async () => {
1340
- const taskPayload = JSON.parse(task.payload);
1341
- if (taskPayload.kind === "system-notice") {
1520
+ if (!campaignContinuation &&
1521
+ taskPayload.kind === "system-notice") {
1342
1522
  if (!adapter.sendSystemNotice) {
1343
1523
  throw new Error(`Platform ${task.platform} cannot deliver system notices`);
1344
1524
  }
@@ -1356,6 +1536,30 @@ export function createIntegrationsPlugin(options) {
1356
1536
  }
1357
1537
  if (taskPayload.kind === "response-delivery") {
1358
1538
  let receipt = taskPayload.deliveryReceipt;
1539
+ let deliveryLease;
1540
+ if (campaignContinuation && !receipt) {
1541
+ const runId = `integration-delivery-${crypto.randomUUID()}`;
1542
+ const leaseToken = crypto.randomUUID();
1543
+ const deliveryClaim = await claimIntegrationCampaignDeliveryForTask(task.id, {
1544
+ runId,
1545
+ leaseToken,
1546
+ leaseDurationMs: INTEGRATION_DELIVERY_LEASE_MS,
1547
+ });
1548
+ if (!deliveryClaim)
1549
+ return "campaign-active";
1550
+ deliveryLease = {
1551
+ campaignId: deliveryClaim.id,
1552
+ runId,
1553
+ leaseToken,
1554
+ };
1555
+ campaignDeliveryLease = {
1556
+ ...deliveryLease,
1557
+ campaignStatus: taskPayload.campaignTerminalStatus ??
1558
+ (taskPayload.awaitingA2ACompletion
1559
+ ? "waiting-a2a"
1560
+ : "completed"),
1561
+ };
1562
+ }
1359
1563
  if (!receipt) {
1360
1564
  receipt = await adapter.sendResponse(taskPayload.message, taskPayload.incoming, {
1361
1565
  ...(taskPayload.placeholderRef
@@ -1384,6 +1588,55 @@ export function createIntegrationsPlugin(options) {
1384
1588
  deliveryRetryRecovery = undefined;
1385
1589
  }
1386
1590
  await recordIntegrationResponseDelivery(deliveredPayload, receipt);
1591
+ const campaignTerminalStatus = taskPayload.campaignTerminalStatus;
1592
+ if (campaignTerminalStatus) {
1593
+ const errorMessage = "Integration campaign exhausted its continuation limit";
1594
+ const terminalized = deliveryLease
1595
+ ? campaignTerminalStatus === "failed"
1596
+ ? await failIntegrationCampaign(deliveryLease.campaignId, {
1597
+ runId: deliveryLease.runId,
1598
+ leaseToken: deliveryLease.leaseToken,
1599
+ errorMessage,
1600
+ })
1601
+ : await completeIntegrationCampaignTask(deliveryLease.campaignId, {
1602
+ integrationTaskId: task.id,
1603
+ runId: deliveryLease.runId,
1604
+ leaseToken: deliveryLease.leaseToken,
1605
+ })
1606
+ : await terminalizeIntegrationCampaignForTask(task.id, {
1607
+ status: campaignTerminalStatus,
1608
+ ...(campaignTerminalStatus === "failed"
1609
+ ? { errorMessage }
1610
+ : {}),
1611
+ });
1612
+ if (deliveryLease && !terminalized) {
1613
+ return "campaign-active";
1614
+ }
1615
+ return campaignTerminalStatus === "failed"
1616
+ ? "campaign-failed"
1617
+ : "completed";
1618
+ }
1619
+ if (taskPayload.awaitingA2ACompletion) {
1620
+ if (deliveryLease) {
1621
+ const waiting = await waitForA2AIntegrationCampaign(deliveryLease.campaignId, {
1622
+ runId: deliveryLease.runId,
1623
+ leaseToken: deliveryLease.leaseToken,
1624
+ nextRunAt: Date.now() + 15_000,
1625
+ });
1626
+ if (!waiting)
1627
+ return "campaign-active";
1628
+ }
1629
+ if (!(await hasActiveA2AContinuationsForIntegrationTask(task.id))) {
1630
+ const completed = await completeIntegrationCampaignTaskAfterA2A(task.id);
1631
+ return completed
1632
+ ? "completed"
1633
+ : "campaign-active";
1634
+ }
1635
+ return "campaign-active";
1636
+ }
1637
+ if (campaignContinuation) {
1638
+ return "campaign-active";
1639
+ }
1387
1640
  return;
1388
1641
  }
1389
1642
  const resources = await loadResourcesForPrompt(task.ownerEmail, true, options?.appId, task.orgId);
@@ -1397,18 +1650,55 @@ export function createIntegrationsPlugin(options) {
1397
1650
  engine: options?.engine,
1398
1651
  ownerEmail: task.ownerEmail,
1399
1652
  appId: options?.appId,
1653
+ }, {
1654
+ enabled: durableCampaignEnabled,
1655
+ continuationInvocation: campaignContinuation,
1400
1656
  });
1401
1657
  if (result?.status === "delivery-pending") {
1402
1658
  deliveryRetryTransitionStarted = true;
1403
- await markTaskDeliveryRetryable(task.id, JSON.stringify(result.payload), result.errorMessage);
1659
+ const checkpoint = await checkpointIntegrationDeliveryRetry(task, JSON.stringify(result.payload), result.errorMessage, event, result.campaignLease);
1660
+ if (checkpoint === "superseded") {
1661
+ return "campaign-active";
1662
+ }
1404
1663
  return "delivery-retry";
1405
1664
  }
1665
+ if (result?.status === "campaign-pending" ||
1666
+ result?.status === "campaign-active") {
1667
+ return "campaign-active";
1668
+ }
1669
+ if (result?.status === "campaign-failed") {
1670
+ return "campaign-failed";
1671
+ }
1406
1672
  return "completed";
1407
1673
  });
1408
1674
  if (processingResult === "delivery-retry") {
1409
1675
  setResponseStatus(event, 202);
1410
1676
  return { ok: true, taskId, retrying: "response-delivery" };
1411
1677
  }
1678
+ if (processingResult === "campaign-active") {
1679
+ setResponseStatus(event, 202);
1680
+ return { ok: true, taskId, continuing: true };
1681
+ }
1682
+ if (processingResult === "campaign-failed") {
1683
+ await markTaskFailed(taskId, "Integration campaign exhausted its continuation limit");
1684
+ const nextTask = await getNextPendingTaskForThread(task.platform, task.externalThreadId);
1685
+ if (nextTask) {
1686
+ await dispatchPendingIntegrationTask({
1687
+ taskId: nextTask.id,
1688
+ task: {
1689
+ platform: task.platform,
1690
+ externalThreadId: task.externalThreadId,
1691
+ platformContext: nextTask.dispatchScope
1692
+ ? { channelId: nextTask.dispatchScope }
1693
+ : undefined,
1694
+ },
1695
+ event,
1696
+ baseUrl: getBaseUrl(event),
1697
+ });
1698
+ }
1699
+ setResponseStatus(event, 200);
1700
+ return { ok: true, taskId, failed: "campaign-exhausted" };
1701
+ }
1412
1702
  await markTaskCompleted(taskId);
1413
1703
  const nextTask = await getNextPendingTaskForThread(task.platform, task.externalThreadId);
1414
1704
  if (nextTask) {
@@ -1439,7 +1729,11 @@ export function createIntegrationsPlugin(options) {
1439
1729
  : "processor failed";
1440
1730
  if (deliveryRetryRecovery) {
1441
1731
  try {
1442
- await markTaskDeliveryRetryable(taskId, deliveryRetryRecovery.payload, `${deliveryRetryRecovery.errorMessage}: ${errorMessage}`);
1732
+ const checkpoint = await checkpointIntegrationDeliveryRetry(task, deliveryRetryRecovery.payload, `${deliveryRetryRecovery.errorMessage}: ${errorMessage}`, event, campaignDeliveryLease);
1733
+ if (checkpoint === "superseded") {
1734
+ setResponseStatus(event, 202);
1735
+ return { ok: true, taskId, continuing: true };
1736
+ }
1443
1737
  setResponseStatus(event, 202);
1444
1738
  return { ok: true, taskId, retrying: "response-delivery" };
1445
1739
  }
@@ -1447,24 +1741,37 @@ export function createIntegrationsPlugin(options) {
1447
1741
  const transitionMessage = transitionError instanceof Error
1448
1742
  ? transitionError.message
1449
1743
  : String(transitionError);
1450
- await failTaskDeliveryTransition(taskId, `Could not safely checkpoint the delivery receipt: ${transitionMessage}`).catch((failureTransitionError) => {
1744
+ await containFailedDeliveryTransition(task, `Could not safely checkpoint the delivery receipt: ${transitionMessage}`, event).catch((failureTransitionError) => {
1451
1745
  console.error("[integrations] Failed to contain delivery receipt transition failure:", failureTransitionError);
1452
1746
  });
1453
1747
  }
1454
1748
  }
1455
1749
  else if (confirmedDeliveryRetryPayload) {
1456
1750
  try {
1457
- await markTaskDeliveryRetryable(taskId, confirmedDeliveryRetryPayload, `Provider delivery was confirmed but history persistence failed: ${errorMessage}`);
1751
+ const checkpoint = await checkpointIntegrationDeliveryRetry(task, confirmedDeliveryRetryPayload, `Provider delivery was confirmed but history persistence failed: ${errorMessage}`, event, campaignDeliveryLease);
1752
+ if (checkpoint === "superseded") {
1753
+ setResponseStatus(event, 202);
1754
+ return { ok: true, taskId, continuing: true };
1755
+ }
1458
1756
  console.error("[integrations] process-task failure:", err);
1459
1757
  setResponseStatus(event, 202);
1460
1758
  return { ok: true, taskId, retrying: "response-delivery" };
1461
1759
  }
1462
1760
  catch (transitionError) {
1761
+ const transitionMessage = transitionError instanceof Error
1762
+ ? transitionError.message
1763
+ : String(transitionError);
1463
1764
  console.error("[integrations] Failed to requeue confirmed delivery history:", transitionError);
1765
+ await containFailedDeliveryTransition(task, `Could not safely checkpoint confirmed delivery history: ${transitionMessage}`, event).catch((failureTransitionError) => {
1766
+ console.error("[integrations] Failed to contain confirmed delivery history transition failure:", failureTransitionError);
1767
+ });
1768
+ console.error("[integrations] process-task failure:", err);
1769
+ setResponseStatus(event, 500);
1770
+ return { error: "Internal task failed" };
1464
1771
  }
1465
1772
  }
1466
1773
  else if (deliveryRetryTransitionStarted) {
1467
- await failTaskDeliveryTransition(taskId, `Could not safely checkpoint the delivery retry: ${errorMessage}`).catch((transitionError) => {
1774
+ await containFailedDeliveryTransition(task, `Could not safely checkpoint the delivery retry: ${errorMessage}`, event).catch((transitionError) => {
1468
1775
  console.error("[integrations] Failed to contain delivery retry transition failure:", transitionError);
1469
1776
  });
1470
1777
  }