@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
@@ -0,0 +1,784 @@
1
+ import { getDbExec, intType, isPostgres, } from "../db/client.js";
2
+ import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
3
+ let initPromise;
4
+ const MAX_PROGRESS_REF_CHARS = 1_024;
5
+ const MAX_CHECKPOINT_CHARS = 8_192;
6
+ const MAX_ERROR_MESSAGE_CHARS = 2_000;
7
+ const MAX_DUE_LIST_LIMIT = 100;
8
+ const A2A_WAITING_CHECKPOINT = '{"waitingForA2A":true}';
9
+ function buildCreateSql() {
10
+ return `CREATE TABLE IF NOT EXISTS integration_campaigns (
11
+ id TEXT PRIMARY KEY,
12
+ integration_task_id TEXT NOT NULL UNIQUE,
13
+ thread_id TEXT NOT NULL,
14
+ turn_id TEXT NOT NULL,
15
+ status TEXT NOT NULL,
16
+ chunk_count ${intType()} NOT NULL DEFAULT 0,
17
+ current_run_id TEXT,
18
+ lease_token TEXT,
19
+ lease_expires_at ${intType()},
20
+ next_run_at ${intType()} NOT NULL,
21
+ progress_ref TEXT,
22
+ checkpoint TEXT,
23
+ error_message TEXT,
24
+ created_at ${intType()} NOT NULL,
25
+ updated_at ${intType()} NOT NULL,
26
+ completed_at ${intType()}
27
+ )`;
28
+ }
29
+ async function ensureTable() {
30
+ if (!initPromise) {
31
+ initPromise = (async () => {
32
+ const createSql = buildCreateSql();
33
+ if (isPostgres()) {
34
+ await ensureTableExists("integration_campaigns", createSql);
35
+ await ensureIndexExists("idx_integration_campaigns_due", "CREATE INDEX IF NOT EXISTS idx_integration_campaigns_due ON integration_campaigns(status, next_run_at)");
36
+ await ensureIndexExists("idx_integration_campaigns_lease", "CREATE INDEX IF NOT EXISTS idx_integration_campaigns_lease ON integration_campaigns(status, lease_expires_at)");
37
+ return;
38
+ }
39
+ const client = getDbExec();
40
+ await client.execute(createSql);
41
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_integration_campaigns_due ON integration_campaigns(status, next_run_at)");
42
+ await client.execute("CREATE INDEX IF NOT EXISTS idx_integration_campaigns_lease ON integration_campaigns(status, lease_expires_at)");
43
+ })().catch((error) => {
44
+ initPromise = undefined;
45
+ throw error;
46
+ });
47
+ }
48
+ return initPromise;
49
+ }
50
+ export async function ensureIntegrationCampaignsTable() {
51
+ await ensureTable();
52
+ }
53
+ function boundedOpaqueValue(value, maxLength, name) {
54
+ if (value == null)
55
+ return null;
56
+ if (value.length > maxLength) {
57
+ throw new Error(`${name} exceeds ${maxLength} characters`);
58
+ }
59
+ return value;
60
+ }
61
+ function rowToCampaign(row) {
62
+ return {
63
+ id: String(row.id),
64
+ integrationTaskId: String(row.integration_task_id),
65
+ threadId: String(row.thread_id),
66
+ turnId: String(row.turn_id),
67
+ status: row.status,
68
+ chunkCount: Number(row.chunk_count ?? 0),
69
+ currentRunId: row.current_run_id ?? null,
70
+ leaseToken: row.lease_token ?? null,
71
+ leaseExpiresAt: row.lease_expires_at == null ? null : Number(row.lease_expires_at),
72
+ nextRunAt: Number(row.next_run_at ?? 0),
73
+ progressRef: row.progress_ref ?? null,
74
+ checkpoint: row.checkpoint ?? null,
75
+ errorMessage: row.error_message ?? null,
76
+ createdAt: Number(row.created_at ?? 0),
77
+ updatedAt: Number(row.updated_at ?? 0),
78
+ completedAt: row.completed_at == null ? null : Number(row.completed_at),
79
+ };
80
+ }
81
+ function isDuplicateCampaignError(error) {
82
+ const candidate = error;
83
+ if (!candidate)
84
+ return false;
85
+ if (candidate.code === "23505")
86
+ return true;
87
+ const message = String(candidate.message ?? "").toLowerCase();
88
+ return (message.includes("unique") ||
89
+ message.includes("duplicate entry") ||
90
+ message.includes("duplicate key"));
91
+ }
92
+ export async function getIntegrationCampaign(id) {
93
+ await ensureTable();
94
+ const { rows } = await getDbExec().execute({
95
+ sql: "SELECT * FROM integration_campaigns WHERE id = ? LIMIT 1",
96
+ args: [id],
97
+ });
98
+ return rows[0] ? rowToCampaign(rows[0]) : null;
99
+ }
100
+ export async function getIntegrationCampaignForTask(integrationTaskId) {
101
+ await ensureTable();
102
+ const { rows } = await getDbExec().execute({
103
+ sql: "SELECT * FROM integration_campaigns WHERE integration_task_id = ? LIMIT 1",
104
+ args: [integrationTaskId],
105
+ });
106
+ return rows[0] ? rowToCampaign(rows[0]) : null;
107
+ }
108
+ export async function createIntegrationCampaign(input) {
109
+ await ensureTable();
110
+ const now = Date.now();
111
+ const id = `integration-campaign-${now}-${Math.random().toString(36).slice(2, 10)}`;
112
+ const progressRef = boundedOpaqueValue(input.progressRef, MAX_PROGRESS_REF_CHARS, "progressRef");
113
+ const checkpoint = boundedOpaqueValue(input.checkpoint, MAX_CHECKPOINT_CHARS, "checkpoint");
114
+ try {
115
+ await getDbExec().execute({
116
+ sql: `INSERT INTO integration_campaigns
117
+ (id, integration_task_id, thread_id, turn_id, status, chunk_count,
118
+ next_run_at, progress_ref, checkpoint, created_at, updated_at)
119
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
120
+ args: [
121
+ id,
122
+ input.integrationTaskId,
123
+ input.threadId,
124
+ input.turnId,
125
+ "pending",
126
+ 0,
127
+ input.nextRunAt ?? now,
128
+ progressRef,
129
+ checkpoint,
130
+ now,
131
+ now,
132
+ ],
133
+ });
134
+ }
135
+ catch (error) {
136
+ if (!isDuplicateCampaignError(error))
137
+ throw error;
138
+ const existing = await getIntegrationCampaignForTask(input.integrationTaskId);
139
+ if (existing)
140
+ return existing;
141
+ throw error;
142
+ }
143
+ const campaign = await getIntegrationCampaign(id);
144
+ if (!campaign)
145
+ throw new Error("Created integration campaign was not found");
146
+ return campaign;
147
+ }
148
+ function affectedRows(result) {
149
+ const value = result;
150
+ return Number(value.rowsAffected ?? value.rowCount ?? 0);
151
+ }
152
+ export async function claimIntegrationCampaign(id, input) {
153
+ await ensureTable();
154
+ if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
155
+ throw new Error("leaseDurationMs must be positive");
156
+ }
157
+ if (!Number.isInteger(input.maxChunks) || input.maxChunks <= 0) {
158
+ throw new Error("maxChunks must be a positive integer");
159
+ }
160
+ const now = Date.now();
161
+ const leaseExpiresAt = now + input.leaseDurationMs;
162
+ const dueClause = `(status IN ('pending', 'waiting') AND next_run_at <= ?)
163
+ OR (status = 'processing' AND lease_expires_at IS NOT NULL AND lease_expires_at <= ?)`;
164
+ const result = await getDbExec().execute({
165
+ sql: isPostgres()
166
+ ? `UPDATE integration_campaigns
167
+ SET status = ?, chunk_count = CASE WHEN status = 'waiting' OR checkpoint = ? THEN chunk_count ELSE chunk_count + 1 END, current_run_id = ?,
168
+ lease_token = ?, lease_expires_at = ?, updated_at = ?, error_message = NULL
169
+ WHERE id = ? AND (status = 'waiting' OR checkpoint = ? OR chunk_count < ?) AND (${dueClause})
170
+ RETURNING *`
171
+ : `UPDATE integration_campaigns
172
+ SET status = ?, chunk_count = CASE WHEN status = 'waiting' OR checkpoint = ? THEN chunk_count ELSE chunk_count + 1 END, current_run_id = ?,
173
+ lease_token = ?, lease_expires_at = ?, updated_at = ?, error_message = NULL
174
+ WHERE id = ? AND (status = 'waiting' OR checkpoint = ? OR chunk_count < ?) AND (${dueClause})`,
175
+ args: [
176
+ "processing",
177
+ A2A_WAITING_CHECKPOINT,
178
+ input.runId,
179
+ input.leaseToken,
180
+ leaseExpiresAt,
181
+ now,
182
+ id,
183
+ A2A_WAITING_CHECKPOINT,
184
+ input.maxChunks,
185
+ now,
186
+ now,
187
+ ],
188
+ });
189
+ if (isPostgres()) {
190
+ const row = result.rows?.[0];
191
+ if (row)
192
+ return {
193
+ kind: "claimed",
194
+ campaign: rowToCampaign(row),
195
+ };
196
+ }
197
+ else if (affectedRows(result) > 0) {
198
+ const campaign = await getIntegrationCampaign(id);
199
+ if (campaign &&
200
+ campaign.status === "processing" &&
201
+ campaign.currentRunId === input.runId &&
202
+ campaign.leaseToken === input.leaseToken) {
203
+ return { kind: "claimed", campaign };
204
+ }
205
+ return { kind: "not-due" };
206
+ }
207
+ const campaign = await getIntegrationCampaign(id);
208
+ const waitingForA2A = campaign?.checkpoint === A2A_WAITING_CHECKPOINT;
209
+ const exhaustedAndRecoverable = campaign &&
210
+ campaign.chunkCount >= input.maxChunks &&
211
+ !waitingForA2A &&
212
+ (campaign.status === "pending" ||
213
+ (campaign.status === "processing" &&
214
+ (campaign.leaseExpiresAt == null || campaign.leaseExpiresAt <= now)));
215
+ if (exhaustedAndRecoverable) {
216
+ return { kind: "chunk-limit", campaign };
217
+ }
218
+ return { kind: "not-due" };
219
+ }
220
+ export async function claimIntegrationCampaignDeliveryForTask(integrationTaskId, input) {
221
+ await ensureTable();
222
+ if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
223
+ throw new Error("leaseDurationMs must be positive");
224
+ }
225
+ const now = Date.now();
226
+ const result = await getDbExec().execute({
227
+ sql: isPostgres()
228
+ ? `UPDATE integration_campaigns
229
+ SET status = 'processing', current_run_id = ?, lease_token = ?,
230
+ lease_expires_at = ?, updated_at = ?
231
+ WHERE integration_task_id = ?
232
+ AND (status IN ('pending', 'waiting') OR
233
+ (status = 'processing' AND
234
+ (lease_expires_at IS NULL OR lease_expires_at <= ?)))
235
+ RETURNING *`
236
+ : `UPDATE integration_campaigns
237
+ SET status = 'processing', current_run_id = ?, lease_token = ?,
238
+ lease_expires_at = ?, updated_at = ?
239
+ WHERE integration_task_id = ?
240
+ AND (status IN ('pending', 'waiting') OR
241
+ (status = 'processing' AND
242
+ (lease_expires_at IS NULL OR lease_expires_at <= ?)))`,
243
+ args: [
244
+ input.runId,
245
+ input.leaseToken,
246
+ now + input.leaseDurationMs,
247
+ now,
248
+ integrationTaskId,
249
+ now,
250
+ ],
251
+ });
252
+ if (isPostgres()) {
253
+ const row = result.rows?.[0];
254
+ return row ? rowToCampaign(row) : null;
255
+ }
256
+ if (affectedRows(result) === 0)
257
+ return null;
258
+ const campaign = await getIntegrationCampaignForTask(integrationTaskId);
259
+ return campaign?.currentRunId === input.runId &&
260
+ campaign.leaseToken === input.leaseToken
261
+ ? campaign
262
+ : null;
263
+ }
264
+ function leaseWhere() {
265
+ return "id = ? AND status = 'processing' AND current_run_id = ? AND lease_token = ?";
266
+ }
267
+ export async function heartbeatIntegrationCampaign(id, input) {
268
+ await ensureTable();
269
+ if (!Number.isFinite(input.leaseDurationMs) || input.leaseDurationMs <= 0) {
270
+ throw new Error("leaseDurationMs must be positive");
271
+ }
272
+ const now = Date.now();
273
+ const result = await getDbExec().execute({
274
+ sql: `UPDATE integration_campaigns
275
+ SET lease_expires_at = ?, updated_at = ?
276
+ WHERE ${leaseWhere()}`,
277
+ args: [now + input.leaseDurationMs, now, id, input.runId, input.leaseToken],
278
+ });
279
+ return affectedRows(result) > 0;
280
+ }
281
+ export async function scheduleNextIntegrationCampaign(id, input) {
282
+ await ensureTable();
283
+ const now = Date.now();
284
+ const progressRef = boundedOpaqueValue(input.progressRef, MAX_PROGRESS_REF_CHARS, "progressRef");
285
+ const checkpoint = boundedOpaqueValue(input.checkpoint, MAX_CHECKPOINT_CHARS, "checkpoint");
286
+ const result = await getDbExec().execute({
287
+ sql: `UPDATE integration_campaigns
288
+ SET status = ?, current_run_id = NULL, lease_token = NULL, lease_expires_at = NULL,
289
+ next_run_at = ?, progress_ref = COALESCE(?, progress_ref),
290
+ checkpoint = COALESCE(?, checkpoint), updated_at = ?
291
+ WHERE ${leaseWhere()}`,
292
+ args: [
293
+ "pending",
294
+ input.nextRunAt,
295
+ progressRef,
296
+ checkpoint,
297
+ now,
298
+ id,
299
+ input.runId,
300
+ input.leaseToken,
301
+ ],
302
+ });
303
+ return affectedRows(result) > 0;
304
+ }
305
+ export async function waitForA2AIntegrationCampaign(id, input) {
306
+ await ensureTable();
307
+ const now = Date.now();
308
+ const progressRef = boundedOpaqueValue(input.progressRef, MAX_PROGRESS_REF_CHARS, "progressRef");
309
+ const result = await getDbExec().execute({
310
+ sql: `UPDATE integration_campaigns
311
+ SET status = 'waiting', current_run_id = NULL, lease_token = NULL,
312
+ lease_expires_at = NULL, next_run_at = ?,
313
+ progress_ref = COALESCE(?, progress_ref),
314
+ checkpoint = ?, updated_at = ?
315
+ WHERE ${leaseWhere()}`,
316
+ args: [
317
+ input.nextRunAt,
318
+ progressRef,
319
+ A2A_WAITING_CHECKPOINT,
320
+ now,
321
+ id,
322
+ input.runId,
323
+ input.leaseToken,
324
+ ],
325
+ });
326
+ return affectedRows(result) > 0;
327
+ }
328
+ async function finishIntegrationCampaign(id, input) {
329
+ await ensureTable();
330
+ const now = Date.now();
331
+ const errorMessage = boundedOpaqueValue(input.errorMessage, MAX_ERROR_MESSAGE_CHARS, "errorMessage");
332
+ const result = await getDbExec().execute({
333
+ sql: `UPDATE integration_campaigns
334
+ SET status = ?, current_run_id = NULL, lease_token = NULL, lease_expires_at = NULL,
335
+ progress_ref = NULL, checkpoint = NULL, error_message = ?,
336
+ updated_at = ?, completed_at = ?
337
+ WHERE ${leaseWhere()}`,
338
+ args: [
339
+ input.status,
340
+ errorMessage,
341
+ now,
342
+ now,
343
+ id,
344
+ input.runId,
345
+ input.leaseToken,
346
+ ],
347
+ });
348
+ return affectedRows(result) > 0;
349
+ }
350
+ export async function completeIntegrationCampaign(id, input) {
351
+ return finishIntegrationCampaign(id, { ...input, status: "completed" });
352
+ }
353
+ export async function completeIntegrationCampaignTask(id, input) {
354
+ await ensureTable();
355
+ const now = Date.now();
356
+ const previousWriteGuard = isPostgres() ? "" : "AND changes() = 1";
357
+ const statements = [
358
+ {
359
+ sql: `UPDATE integration_campaigns
360
+ SET status = 'completed', current_run_id = NULL,
361
+ lease_token = NULL, lease_expires_at = NULL,
362
+ progress_ref = NULL, checkpoint = NULL, error_message = NULL,
363
+ updated_at = ?, completed_at = ?
364
+ WHERE id = ? AND integration_task_id = ?
365
+ AND status = 'processing'
366
+ AND current_run_id = ? AND lease_token = ?`,
367
+ args: [
368
+ now,
369
+ now,
370
+ id,
371
+ input.integrationTaskId,
372
+ input.runId,
373
+ input.leaseToken,
374
+ ],
375
+ },
376
+ {
377
+ sql: `UPDATE integration_pending_tasks
378
+ SET status = 'completed', payload = '{}', error_message = NULL,
379
+ updated_at = ?, completed_at = ?
380
+ WHERE id = ? AND status = 'processing'
381
+ ${previousWriteGuard}
382
+ AND EXISTS (
383
+ SELECT 1 FROM integration_campaigns
384
+ WHERE id = ? AND integration_task_id = ?
385
+ AND status = 'completed' AND updated_at = ?
386
+ AND completed_at = ?
387
+ )`,
388
+ args: [
389
+ now,
390
+ now,
391
+ input.integrationTaskId,
392
+ id,
393
+ input.integrationTaskId,
394
+ now,
395
+ now,
396
+ ],
397
+ },
398
+ ];
399
+ const db = getDbExec();
400
+ if (db.atomicBatch) {
401
+ const results = await db.atomicBatch(statements);
402
+ return (results.length === statements.length &&
403
+ results.every((result) => affectedRows(result) > 0));
404
+ }
405
+ if (db.transaction) {
406
+ return await db.transaction(async (tx) => {
407
+ const campaignResult = await tx.execute(statements[0]);
408
+ if (affectedRows(campaignResult) === 0)
409
+ return false;
410
+ const taskResult = await tx.execute(statements[1]);
411
+ if (affectedRows(taskResult) === 0) {
412
+ throw new Error("Campaign completion transition lost its task custody");
413
+ }
414
+ return true;
415
+ });
416
+ }
417
+ throw new Error("Database does not support atomic campaign completion");
418
+ }
419
+ export async function failIntegrationCampaign(id, input) {
420
+ return finishIntegrationCampaign(id, { ...input, status: "failed" });
421
+ }
422
+ export async function failExhaustedIntegrationCampaign(id, input) {
423
+ await ensureTable();
424
+ const now = Date.now();
425
+ const errorMessage = boundedOpaqueValue(input.errorMessage, MAX_ERROR_MESSAGE_CHARS, "errorMessage");
426
+ const result = await getDbExec().execute({
427
+ sql: `UPDATE integration_campaigns
428
+ SET status = 'failed', current_run_id = NULL, lease_token = NULL,
429
+ lease_expires_at = NULL, progress_ref = NULL, checkpoint = NULL,
430
+ error_message = ?, updated_at = ?, completed_at = ?
431
+ WHERE id = ? AND chunk_count >= ?
432
+ AND (checkpoint IS NULL OR checkpoint <> ?)
433
+ AND (status = 'pending' OR
434
+ (status = 'processing' AND
435
+ (lease_expires_at IS NULL OR lease_expires_at <= ?)))`,
436
+ args: [
437
+ errorMessage,
438
+ now,
439
+ now,
440
+ id,
441
+ input.maxChunks,
442
+ A2A_WAITING_CHECKPOINT,
443
+ now,
444
+ ],
445
+ });
446
+ return affectedRows(result) > 0;
447
+ }
448
+ export async function terminalizeIntegrationCampaignForTask(integrationTaskId, input) {
449
+ await ensureTable();
450
+ const now = Date.now();
451
+ const errorMessage = boundedOpaqueValue(input.errorMessage, MAX_ERROR_MESSAGE_CHARS, "errorMessage");
452
+ const result = await getDbExec().execute({
453
+ sql: `UPDATE integration_campaigns
454
+ SET status = ?, current_run_id = NULL, lease_token = NULL,
455
+ lease_expires_at = NULL, progress_ref = NULL, checkpoint = NULL,
456
+ error_message = ?, updated_at = ?, completed_at = ?
457
+ WHERE integration_task_id = ?
458
+ AND status IN ('pending', 'processing', 'waiting')`,
459
+ args: [input.status, errorMessage, now, now, integrationTaskId],
460
+ });
461
+ return affectedRows(result) > 0;
462
+ }
463
+ export async function failIntegrationCampaignTaskDeliveryContainment(integrationTaskId, errorMessage) {
464
+ await ensureTable();
465
+ const now = Date.now();
466
+ const boundedError = boundedOpaqueValue(errorMessage, MAX_ERROR_MESSAGE_CHARS, "errorMessage") ??
467
+ "Delivery transition failed";
468
+ const statements = [
469
+ {
470
+ sql: `UPDATE integration_campaigns
471
+ SET status = 'failed', current_run_id = NULL,
472
+ lease_token = NULL, lease_expires_at = NULL,
473
+ progress_ref = NULL, checkpoint = NULL, error_message = ?,
474
+ updated_at = ?, completed_at = ?
475
+ WHERE integration_task_id = ?
476
+ AND status IN ('pending', 'processing', 'waiting')`,
477
+ args: [boundedError, now, now, integrationTaskId],
478
+ },
479
+ {
480
+ sql: `UPDATE integration_pending_tasks
481
+ SET status = 'failed', payload = '{}', error_message = ?,
482
+ updated_at = ?, completed_at = ?
483
+ WHERE id = ? AND status = 'processing'
484
+ AND NOT EXISTS (
485
+ SELECT 1 FROM integration_campaigns
486
+ WHERE integration_task_id = ?
487
+ AND status IN ('pending', 'processing', 'waiting')
488
+ )`,
489
+ args: [boundedError, now, now, integrationTaskId, integrationTaskId],
490
+ },
491
+ ];
492
+ const db = getDbExec();
493
+ if (db.atomicBatch) {
494
+ const results = await db.atomicBatch(statements);
495
+ return (results.length === statements.length && affectedRows(results[1]) > 0);
496
+ }
497
+ if (db.transaction) {
498
+ return await db.transaction(async (tx) => {
499
+ await tx.execute(statements[0]);
500
+ const taskResult = await tx.execute(statements[1]);
501
+ return affectedRows(taskResult) > 0;
502
+ });
503
+ }
504
+ throw new Error("Database does not support atomic delivery containment");
505
+ }
506
+ function disabledCampaignAndTaskStatements(integrationTaskId, errorMessage) {
507
+ const now = Date.now();
508
+ return [
509
+ {
510
+ sql: `UPDATE integration_campaigns
511
+ SET status = 'failed', current_run_id = NULL, lease_token = NULL,
512
+ lease_expires_at = NULL, progress_ref = NULL, checkpoint = NULL,
513
+ error_message = ?, updated_at = ?, completed_at = ?
514
+ WHERE integration_task_id = ?
515
+ AND status IN ('pending', 'processing', 'waiting')`,
516
+ args: [errorMessage, now, now, integrationTaskId],
517
+ },
518
+ {
519
+ sql: `UPDATE integration_pending_tasks
520
+ SET status = 'failed', payload = '{}', external_event_key = NULL,
521
+ error_message = ?, updated_at = ?, completed_at = ?
522
+ WHERE id = ? AND status = 'processing'`,
523
+ args: [errorMessage, now, now, integrationTaskId],
524
+ },
525
+ ];
526
+ }
527
+ async function executeStatementsWithDb(db, statements) {
528
+ for (const statement of statements)
529
+ await db.execute(statement);
530
+ }
531
+ /**
532
+ * Fail closed when a durable campaign's rollout scope is removed. Both rows
533
+ * transition in one transaction so the per-thread processing lock cannot be
534
+ * stranded while the campaign is no longer recoverable.
535
+ */
536
+ export async function failDisabledIntegrationCampaignTask(integrationTaskId, errorMessage = "Durable integration campaign was disabled for this scope") {
537
+ await ensureTable();
538
+ const boundedError = boundedOpaqueValue(errorMessage, MAX_ERROR_MESSAGE_CHARS, "errorMessage") ??
539
+ "Durable integration campaign was disabled";
540
+ const db = getDbExec();
541
+ const statements = disabledCampaignAndTaskStatements(integrationTaskId, boundedError);
542
+ if (db.atomicBatch) {
543
+ await db.atomicBatch(statements);
544
+ return;
545
+ }
546
+ if (db.transaction) {
547
+ await db.transaction((tx) => executeStatementsWithDb(tx, statements));
548
+ return;
549
+ }
550
+ throw new Error("Database does not support atomic campaign cancellation");
551
+ }
552
+ export async function transitionIntegrationCampaignTaskToDeliveryRetry(integrationTaskId, input) {
553
+ await ensureTable();
554
+ const now = Date.now();
555
+ const errorMessage = boundedOpaqueValue(input.errorMessage, MAX_ERROR_MESSAGE_CHARS, "errorMessage") ?? "Integration response delivery needs retry";
556
+ const previousWriteGuard = isPostgres() ? "" : "AND changes() = 1";
557
+ const statements = [
558
+ {
559
+ sql: `UPDATE integration_campaigns
560
+ SET status = ?, current_run_id = NULL, lease_token = NULL,
561
+ lease_expires_at = NULL, progress_ref = NULL, checkpoint = NULL,
562
+ error_message = ?, updated_at = ?, completed_at = ?
563
+ WHERE id = ? AND integration_task_id = ?
564
+ AND status = 'processing'
565
+ AND current_run_id = ? AND lease_token = ?`,
566
+ args: [
567
+ input.campaignStatus,
568
+ input.campaignStatus === "failed" ? errorMessage : null,
569
+ now,
570
+ now,
571
+ input.campaignId,
572
+ integrationTaskId,
573
+ input.runId,
574
+ input.leaseToken,
575
+ ],
576
+ },
577
+ {
578
+ sql: `UPDATE integration_pending_tasks
579
+ SET status = 'pending', payload = ?, error_message = ?, updated_at = ?
580
+ WHERE id = ? AND status = 'processing'
581
+ ${previousWriteGuard}
582
+ AND EXISTS (
583
+ SELECT 1 FROM integration_campaigns
584
+ WHERE id = ? AND integration_task_id = ?
585
+ AND status = ? AND updated_at = ? AND completed_at = ?
586
+ AND current_run_id IS NULL AND lease_token IS NULL
587
+ )`,
588
+ args: [
589
+ input.payload,
590
+ errorMessage,
591
+ now,
592
+ integrationTaskId,
593
+ input.campaignId,
594
+ integrationTaskId,
595
+ input.campaignStatus,
596
+ now,
597
+ now,
598
+ ],
599
+ },
600
+ ];
601
+ const db = getDbExec();
602
+ if (db.atomicBatch) {
603
+ const results = await db.atomicBatch(statements);
604
+ return (results.length === statements.length &&
605
+ results.every((result) => affectedRows(result) > 0));
606
+ }
607
+ if (db.transaction) {
608
+ return await db.transaction(async (tx) => {
609
+ const campaignResult = await tx.execute(statements[0]);
610
+ if (affectedRows(campaignResult) === 0)
611
+ return false;
612
+ const taskResult = await tx.execute(statements[1]);
613
+ if (affectedRows(taskResult) === 0) {
614
+ throw new Error("Campaign delivery retry lease transition lost its race");
615
+ }
616
+ return true;
617
+ });
618
+ }
619
+ throw new Error("Database does not support atomic campaign delivery retry");
620
+ }
621
+ export async function transitionIntegrationCampaignTaskToA2AReceiptRetry(integrationTaskId, input) {
622
+ await ensureTable();
623
+ const now = Date.now();
624
+ const errorMessage = boundedOpaqueValue(input.errorMessage, MAX_ERROR_MESSAGE_CHARS, "errorMessage") ?? "A2A partial response history needs retry";
625
+ const previousWriteGuard = isPostgres() ? "" : "AND changes() = 1";
626
+ const statements = [
627
+ {
628
+ sql: `UPDATE integration_campaigns
629
+ SET status = 'waiting', current_run_id = NULL, lease_token = NULL,
630
+ lease_expires_at = NULL, next_run_at = ?, checkpoint = ?,
631
+ error_message = NULL, updated_at = ?
632
+ WHERE id = ? AND integration_task_id = ?
633
+ AND status = 'processing'
634
+ AND current_run_id = ? AND lease_token = ?`,
635
+ args: [
636
+ input.nextRunAt,
637
+ A2A_WAITING_CHECKPOINT,
638
+ now,
639
+ input.campaignId,
640
+ integrationTaskId,
641
+ input.runId,
642
+ input.leaseToken,
643
+ ],
644
+ },
645
+ {
646
+ sql: `UPDATE integration_pending_tasks
647
+ SET payload = ?, error_message = ?, updated_at = ?
648
+ WHERE id = ? AND status = 'processing'
649
+ ${previousWriteGuard}
650
+ AND EXISTS (
651
+ SELECT 1 FROM integration_campaigns
652
+ WHERE id = ? AND integration_task_id = ?
653
+ AND status = 'waiting' AND checkpoint = ?
654
+ AND updated_at = ? AND current_run_id IS NULL
655
+ AND lease_token IS NULL
656
+ )`,
657
+ args: [
658
+ input.payload,
659
+ errorMessage,
660
+ now,
661
+ integrationTaskId,
662
+ input.campaignId,
663
+ integrationTaskId,
664
+ A2A_WAITING_CHECKPOINT,
665
+ now,
666
+ ],
667
+ },
668
+ ];
669
+ const db = getDbExec();
670
+ if (db.atomicBatch) {
671
+ const results = await db.atomicBatch(statements);
672
+ return (results.length === statements.length &&
673
+ results.every((result) => affectedRows(result) > 0));
674
+ }
675
+ if (db.transaction) {
676
+ return await db.transaction(async (tx) => {
677
+ const campaignResult = await tx.execute(statements[0]);
678
+ if (affectedRows(campaignResult) === 0)
679
+ return false;
680
+ const taskResult = await tx.execute(statements[1]);
681
+ if (affectedRows(taskResult) === 0) {
682
+ throw new Error("A2A receipt custody transition lost its race");
683
+ }
684
+ return true;
685
+ });
686
+ }
687
+ throw new Error("Database does not support atomic A2A receipt custody");
688
+ }
689
+ export async function refreshIntegrationCampaignTaskA2AReceiptRetry(integrationTaskId, input) {
690
+ await ensureTable();
691
+ const result = await getDbExec().execute({
692
+ sql: `UPDATE integration_pending_tasks
693
+ SET payload = ?, error_message = ?, updated_at = ?
694
+ WHERE id = ? AND status = 'processing'
695
+ AND EXISTS (
696
+ SELECT 1 FROM integration_campaigns
697
+ WHERE integration_task_id = ? AND status = 'waiting'
698
+ AND checkpoint = ?
699
+ )`,
700
+ args: [
701
+ input.payload,
702
+ input.errorMessage.slice(0, MAX_ERROR_MESSAGE_CHARS),
703
+ Date.now(),
704
+ integrationTaskId,
705
+ integrationTaskId,
706
+ A2A_WAITING_CHECKPOINT,
707
+ ],
708
+ });
709
+ return affectedRows(result) > 0;
710
+ }
711
+ export async function completeIntegrationCampaignTaskAfterA2A(integrationTaskId) {
712
+ await ensureTable();
713
+ const now = Date.now();
714
+ const previousWriteGuard = isPostgres() ? "" : "AND changes() = 1";
715
+ const statements = [
716
+ {
717
+ sql: `UPDATE integration_campaigns
718
+ SET status = 'completed', current_run_id = NULL,
719
+ lease_token = NULL, lease_expires_at = NULL,
720
+ progress_ref = NULL, checkpoint = NULL, error_message = NULL,
721
+ updated_at = ?, completed_at = ?
722
+ WHERE integration_task_id = ? AND status = 'waiting'
723
+ AND checkpoint = ?`,
724
+ args: [now, now, integrationTaskId, A2A_WAITING_CHECKPOINT],
725
+ },
726
+ {
727
+ sql: `UPDATE integration_pending_tasks
728
+ SET status = 'completed', payload = '{}', error_message = NULL,
729
+ updated_at = ?, completed_at = ?
730
+ WHERE id = ? AND status = 'processing'
731
+ ${previousWriteGuard}
732
+ AND EXISTS (
733
+ SELECT 1 FROM integration_campaigns
734
+ WHERE integration_task_id = ? AND status = 'completed'
735
+ AND updated_at = ? AND completed_at = ?
736
+ )`,
737
+ args: [now, now, integrationTaskId, integrationTaskId, now, now],
738
+ },
739
+ ];
740
+ const db = getDbExec();
741
+ if (db.atomicBatch) {
742
+ const results = await db.atomicBatch(statements);
743
+ return (results.length === statements.length &&
744
+ results.every((result) => affectedRows(result) > 0));
745
+ }
746
+ if (db.transaction) {
747
+ return await db.transaction(async (tx) => {
748
+ const campaignResult = await tx.execute(statements[0]);
749
+ if (affectedRows(campaignResult) === 0)
750
+ return false;
751
+ const taskResult = await tx.execute(statements[1]);
752
+ if (affectedRows(taskResult) === 0) {
753
+ throw new Error("A2A parent completion transition lost its race");
754
+ }
755
+ return true;
756
+ });
757
+ }
758
+ throw new Error("Database does not support atomic A2A parent completion");
759
+ }
760
+ export async function listDueIntegrationCampaignIds(limit = 25) {
761
+ await ensureTable();
762
+ const boundedLimit = Math.max(1, Math.min(Math.floor(limit), MAX_DUE_LIST_LIMIT));
763
+ const now = Date.now();
764
+ const { rows } = await getDbExec().execute({
765
+ sql: `SELECT id FROM integration_campaigns
766
+ WHERE (status IN ('pending', 'waiting') AND next_run_at <= ?)
767
+ OR (status = 'processing' AND lease_expires_at IS NOT NULL AND lease_expires_at <= ?)
768
+ ORDER BY next_run_at ASC, id ASC
769
+ LIMIT ?`,
770
+ args: [now, now, boundedLimit],
771
+ });
772
+ return rows.map((row) => String(row.id));
773
+ }
774
+ export async function hasActiveIntegrationCampaign(integrationTaskId) {
775
+ await ensureTable();
776
+ const { rows } = await getDbExec().execute({
777
+ sql: `SELECT 1 AS active FROM integration_campaigns
778
+ WHERE integration_task_id = ? AND status IN ('pending', 'processing', 'waiting')
779
+ LIMIT 1`,
780
+ args: [integrationTaskId],
781
+ });
782
+ return rows.length > 0;
783
+ }
784
+ //# sourceMappingURL=integration-campaigns-store.js.map