@agent-native/core 0.130.0 → 0.130.1
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.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +481 -122
- package/corpus/core/src/integrations/a2a-continuations-store.ts +345 -14
- package/corpus/core/src/integrations/adapters/slack.ts +243 -53
- package/corpus/core/src/integrations/integration-campaign-recovery.ts +4 -1
- package/corpus/core/src/integrations/plugin.ts +39 -21
- package/corpus/core/src/integrations/task-queue-stats.ts +146 -0
- package/corpus/core/src/integrations/types.ts +25 -4
- package/corpus/core/src/integrations/webhook-handler.ts +18 -5
- package/corpus/templates/dispatch/changelog/2026-07-26-slack-replies-recover-after-connected-app-updates.md +6 -0
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +15 -0
- package/corpus/templates/slides/actions/delete-design-system.ts +145 -0
- package/corpus/templates/slides/actions/list-design-systems.ts +94 -2
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +43 -1
- package/corpus/templates/slides/app/hooks/use-design-systems.ts +2 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +7 -0
- package/corpus/templates/slides/app/pages/DesignSystems.tsx +56 -1
- package/corpus/templates/slides/changelog/2026-07-28-delete-a-design-system-you-own-from-the-design-systems-page-.md +6 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/integrations/a2a-continuation-processor.d.ts +5 -0
- package/dist/integrations/a2a-continuation-processor.d.ts.map +1 -1
- package/dist/integrations/a2a-continuation-processor.js +293 -79
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts +23 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +249 -15
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/adapters/slack.d.ts.map +1 -1
- package/dist/integrations/adapters/slack.js +155 -31
- package/dist/integrations/adapters/slack.js.map +1 -1
- package/dist/integrations/integration-campaign-recovery.d.ts.map +1 -1
- package/dist/integrations/integration-campaign-recovery.js +3 -1
- package/dist/integrations/integration-campaign-recovery.js.map +1 -1
- package/dist/integrations/plugin.d.ts.map +1 -1
- package/dist/integrations/plugin.js +29 -12
- package/dist/integrations/plugin.js.map +1 -1
- package/dist/integrations/task-queue-stats.d.ts +12 -0
- package/dist/integrations/task-queue-stats.d.ts.map +1 -1
- package/dist/integrations/task-queue-stats.js +106 -0
- package/dist/integrations/task-queue-stats.js.map +1 -1
- package/dist/integrations/types.d.ts +24 -6
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/integrations/types.js.map +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +12 -3
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/src/integrations/a2a-continuation-processor.ts +481 -122
- package/src/integrations/a2a-continuations-store.ts +345 -14
- package/src/integrations/adapters/slack.ts +243 -53
- package/src/integrations/integration-campaign-recovery.ts +4 -1
- package/src/integrations/plugin.ts +39 -21
- package/src/integrations/task-queue-stats.ts +146 -0
- package/src/integrations/types.ts +25 -4
- package/src/integrations/webhook-handler.ts +18 -5
|
@@ -4,6 +4,9 @@ import { isDuplicateColumnError } from "../db/migrations.js";
|
|
|
4
4
|
let _initPromise;
|
|
5
5
|
const PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
|
|
6
6
|
const PROCESSING_NEXT_CHECK_STALE_AFTER_MS = 60 * 1000;
|
|
7
|
+
const TERMINAL_HISTORY_FINALIZATION_LEASE_MS = 60 * 1000;
|
|
8
|
+
const MAX_VERIFIED_ARTIFACT_CHECKPOINT_CHARS = 16_000;
|
|
9
|
+
const MAX_TERMINAL_HISTORY_PAYLOAD_CHARS = 64_000;
|
|
7
10
|
// Build the CREATE SQL lazily (not at module scope) so intType() runs at
|
|
8
11
|
// RUNTIME, not import time — a module-scope call breaks any consumer whose
|
|
9
12
|
// db/client mock doesn't stub intType (e.g. db-admin specs).
|
|
@@ -25,6 +28,10 @@ function buildCreateSql() {
|
|
|
25
28
|
dedupe_key TEXT,
|
|
26
29
|
a2a_task_id TEXT NOT NULL,
|
|
27
30
|
a2a_auth_token TEXT,
|
|
31
|
+
verified_artifact_checkpoint TEXT,
|
|
32
|
+
terminal_delivery_kind TEXT,
|
|
33
|
+
terminal_delivery_confirmed_at ${intType()},
|
|
34
|
+
terminal_history_payload TEXT,
|
|
28
35
|
status TEXT NOT NULL,
|
|
29
36
|
attempts ${intType()} NOT NULL DEFAULT 0,
|
|
30
37
|
next_check_at ${intType()} NOT NULL,
|
|
@@ -50,6 +57,11 @@ async function ensureTable() {
|
|
|
50
57
|
await ensureColumnExists("integration_a2a_continuations", "dedupe_key", `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS dedupe_key TEXT`);
|
|
51
58
|
await ensureColumnExists("integration_a2a_continuations", "progress_ref", `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS progress_ref TEXT`);
|
|
52
59
|
await ensureColumnExists("integration_a2a_continuations", "progress_ref_claimed", `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS progress_ref_claimed ${intType()} NOT NULL DEFAULT 0`);
|
|
60
|
+
await ensureColumnExists("integration_a2a_continuations", "verified_artifact_checkpoint", `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS verified_artifact_checkpoint TEXT`);
|
|
61
|
+
await ensureColumnExists("integration_a2a_continuations", "terminal_delivery_kind", `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS terminal_delivery_kind TEXT`);
|
|
62
|
+
await ensureColumnExists("integration_a2a_continuations", "terminal_delivery_confirmed_at", `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS terminal_delivery_confirmed_at ${intType()}`);
|
|
63
|
+
await ensureColumnExists("integration_a2a_continuations", "terminal_history_payload", `ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS terminal_history_payload TEXT`);
|
|
64
|
+
await backfillLegacyCompletedDeliveries(client);
|
|
53
65
|
await backfillProgressRefOwners(client);
|
|
54
66
|
await ensureIndexExists("idx_a2a_continuations_dedupe_key", `CREATE INDEX IF NOT EXISTS idx_a2a_continuations_dedupe_key ON integration_a2a_continuations(integration_task_id, agent_url, dedupe_key)`);
|
|
55
67
|
await ensureIndexExists("idx_a2a_continuations_one_progress_owner", `CREATE UNIQUE INDEX IF NOT EXISTS idx_a2a_continuations_one_progress_owner ON integration_a2a_continuations(integration_task_id) WHERE progress_ref_claimed = 1`);
|
|
@@ -64,6 +76,11 @@ async function ensureTable() {
|
|
|
64
76
|
await addColumnIfMissing("dedupe_key", "TEXT");
|
|
65
77
|
await addColumnIfMissing("progress_ref", "TEXT");
|
|
66
78
|
await addColumnIfMissing("progress_ref_claimed", `${intType()} NOT NULL DEFAULT 0`);
|
|
79
|
+
await addColumnIfMissing("verified_artifact_checkpoint", "TEXT");
|
|
80
|
+
await addColumnIfMissing("terminal_delivery_kind", "TEXT");
|
|
81
|
+
await addColumnIfMissing("terminal_delivery_confirmed_at", intType());
|
|
82
|
+
await addColumnIfMissing("terminal_history_payload", "TEXT");
|
|
83
|
+
await backfillLegacyCompletedDeliveries(client);
|
|
67
84
|
await backfillProgressRefOwners(client);
|
|
68
85
|
await retryOnDdlRace(() => client.execute(`CREATE INDEX IF NOT EXISTS idx_a2a_continuations_dedupe_key ON integration_a2a_continuations(integration_task_id, agent_url, dedupe_key)`));
|
|
69
86
|
await retryOnDdlRace(() => client.execute(`CREATE UNIQUE INDEX IF NOT EXISTS idx_a2a_continuations_one_progress_owner ON integration_a2a_continuations(integration_task_id) WHERE progress_ref_claimed = 1`));
|
|
@@ -75,6 +92,9 @@ async function ensureTable() {
|
|
|
75
92
|
}
|
|
76
93
|
return _initPromise;
|
|
77
94
|
}
|
|
95
|
+
export async function ensureA2AContinuationsTable() {
|
|
96
|
+
await ensureTable();
|
|
97
|
+
}
|
|
78
98
|
async function addColumnIfMissing(name, definition) {
|
|
79
99
|
try {
|
|
80
100
|
await retryOnDdlRace(() => getDbExec().execute(`ALTER TABLE integration_a2a_continuations ADD COLUMN ${name} ${definition}`));
|
|
@@ -109,6 +129,15 @@ async function backfillProgressRefOwners(client) {
|
|
|
109
129
|
)
|
|
110
130
|
`);
|
|
111
131
|
}
|
|
132
|
+
async function backfillLegacyCompletedDeliveries(client) {
|
|
133
|
+
await client.execute(`
|
|
134
|
+
UPDATE integration_a2a_continuations
|
|
135
|
+
SET terminal_delivery_kind = 'success',
|
|
136
|
+
terminal_delivery_confirmed_at = COALESCE(completed_at, updated_at)
|
|
137
|
+
WHERE status = 'completed'
|
|
138
|
+
AND terminal_delivery_confirmed_at IS NULL
|
|
139
|
+
`);
|
|
140
|
+
}
|
|
112
141
|
const MAX_PROGRESS_REF_KIND_CHARS = 128;
|
|
113
142
|
const MAX_PROGRESS_REF_STREAM_TS_CHARS = 256;
|
|
114
143
|
/**
|
|
@@ -159,6 +188,15 @@ function rowToContinuation(row) {
|
|
|
159
188
|
dedupeKey: row.dedupe_key ?? null,
|
|
160
189
|
a2aTaskId: row.a2a_task_id,
|
|
161
190
|
a2aAuthToken: row.a2a_auth_token ?? null,
|
|
191
|
+
verifiedArtifactCheckpoint: row.verified_artifact_checkpoint ?? null,
|
|
192
|
+
terminalDeliveryKind: row.terminal_delivery_kind === "success" ||
|
|
193
|
+
row.terminal_delivery_kind === "failure"
|
|
194
|
+
? row.terminal_delivery_kind
|
|
195
|
+
: null,
|
|
196
|
+
terminalDeliveryConfirmedAt: row.terminal_delivery_confirmed_at == null
|
|
197
|
+
? null
|
|
198
|
+
: Number(row.terminal_delivery_confirmed_at),
|
|
199
|
+
terminalHistoryPayload: parseTerminalHistoryPayload(row.terminal_history_payload),
|
|
162
200
|
status: row.status,
|
|
163
201
|
attempts: Number(row.attempts ?? 0),
|
|
164
202
|
nextCheckAt: Number(row.next_check_at ?? 0),
|
|
@@ -168,6 +206,36 @@ function rowToContinuation(row) {
|
|
|
168
206
|
completedAt: row.completed_at == null ? null : Number(row.completed_at),
|
|
169
207
|
};
|
|
170
208
|
}
|
|
209
|
+
function parseTerminalHistoryPayload(value) {
|
|
210
|
+
if (typeof value !== "string" || !value)
|
|
211
|
+
return null;
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(value);
|
|
214
|
+
if (typeof parsed.text !== "string" ||
|
|
215
|
+
typeof parsed.deliveredAt !== "string" ||
|
|
216
|
+
!Array.isArray(parsed.messageRefs) ||
|
|
217
|
+
!parsed.messageRefs.every((ref) => typeof ref === "string") ||
|
|
218
|
+
!Array.isArray(parsed.artifacts)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
return {
|
|
222
|
+
text: parsed.text,
|
|
223
|
+
deliveredAt: parsed.deliveredAt,
|
|
224
|
+
messageRefs: parsed.messageRefs,
|
|
225
|
+
artifacts: parsed.artifacts.filter((artifact) => !!artifact &&
|
|
226
|
+
typeof artifact === "object" &&
|
|
227
|
+
!Array.isArray(artifact) &&
|
|
228
|
+
typeof artifact.id === "string" &&
|
|
229
|
+
typeof artifact.resourceType ===
|
|
230
|
+
"string" &&
|
|
231
|
+
typeof artifact.sourceAction ===
|
|
232
|
+
"string"),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
171
239
|
export async function insertA2AContinuation(input) {
|
|
172
240
|
await ensureTable();
|
|
173
241
|
const client = getDbExec();
|
|
@@ -291,14 +359,60 @@ export async function hasActiveA2AContinuationsForIntegrationTask(integrationTas
|
|
|
291
359
|
});
|
|
292
360
|
return rows.length > 0;
|
|
293
361
|
}
|
|
362
|
+
export async function getA2AContinuationTaskOutcome(integrationTaskId) {
|
|
363
|
+
await ensureTable();
|
|
364
|
+
const { rows } = await getDbExec().execute({
|
|
365
|
+
sql: `SELECT status, terminal_delivery_confirmed_at
|
|
366
|
+
FROM integration_a2a_continuations
|
|
367
|
+
WHERE integration_task_id = ?`,
|
|
368
|
+
args: [integrationTaskId],
|
|
369
|
+
});
|
|
370
|
+
if (rows.length === 0)
|
|
371
|
+
return "missing";
|
|
372
|
+
if (rows.some((row) => ["pending", "processing", "delivering"].includes(String(row.status)))) {
|
|
373
|
+
return "active";
|
|
374
|
+
}
|
|
375
|
+
return rows.every((row) => row.terminal_delivery_confirmed_at != null)
|
|
376
|
+
? "terminal-delivered"
|
|
377
|
+
: "terminal-without-delivery";
|
|
378
|
+
}
|
|
379
|
+
export async function hasPendingConfirmedA2ADeliveryForIntegrationTask(integrationTaskId) {
|
|
380
|
+
await ensureTable();
|
|
381
|
+
const { rows } = await getDbExec().execute({
|
|
382
|
+
sql: `SELECT 1 AS confirmed FROM integration_a2a_continuations
|
|
383
|
+
WHERE integration_task_id = ?
|
|
384
|
+
AND status IN ('pending', 'processing', 'delivering')
|
|
385
|
+
AND terminal_delivery_confirmed_at IS NOT NULL
|
|
386
|
+
LIMIT 1`,
|
|
387
|
+
args: [integrationTaskId],
|
|
388
|
+
});
|
|
389
|
+
return rows.length > 0;
|
|
390
|
+
}
|
|
391
|
+
export async function hasOnlyLegacyFailedA2AContinuationsForIntegrationTask(integrationTaskId) {
|
|
392
|
+
await ensureTable();
|
|
393
|
+
const { rows } = await getDbExec().execute({
|
|
394
|
+
sql: `SELECT status, terminal_delivery_kind,
|
|
395
|
+
terminal_delivery_confirmed_at, terminal_history_payload
|
|
396
|
+
FROM integration_a2a_continuations
|
|
397
|
+
WHERE integration_task_id = ?`,
|
|
398
|
+
args: [integrationTaskId],
|
|
399
|
+
});
|
|
400
|
+
return (rows.length > 0 &&
|
|
401
|
+
rows.every((row) => String(row.status) === "failed" &&
|
|
402
|
+
row.terminal_delivery_kind == null &&
|
|
403
|
+
row.terminal_delivery_confirmed_at == null &&
|
|
404
|
+
row.terminal_history_payload == null));
|
|
405
|
+
}
|
|
294
406
|
export async function failA2AContinuationsForIntegrationTask(integrationTaskId, errorMessage) {
|
|
295
407
|
await ensureTable();
|
|
296
408
|
const now = Date.now();
|
|
297
409
|
await getDbExec().execute({
|
|
298
410
|
sql: `UPDATE integration_a2a_continuations
|
|
299
|
-
SET status = 'failed', error_message = ?, updated_at = ?, completed_at =
|
|
411
|
+
SET status = 'failed', error_message = ?, updated_at = ?, completed_at = ?,
|
|
412
|
+
verified_artifact_checkpoint = NULL
|
|
300
413
|
WHERE integration_task_id = ?
|
|
301
|
-
AND status IN ('pending', 'processing', 'delivering')
|
|
414
|
+
AND status IN ('pending', 'processing', 'delivering')
|
|
415
|
+
AND terminal_delivery_confirmed_at IS NULL`,
|
|
302
416
|
args: [errorMessage.slice(0, 2000), now, now, integrationTaskId],
|
|
303
417
|
});
|
|
304
418
|
}
|
|
@@ -413,7 +527,7 @@ export async function claimDueA2AContinuations(limit = 5) {
|
|
|
413
527
|
* to wake the normal processor, whose atomic claim remains the sole progress
|
|
414
528
|
* and delivery owner under overlapping scheduler/self-dispatch executions.
|
|
415
529
|
*/
|
|
416
|
-
export async function recoverDueA2AContinuationIds(limit = 5, integrationTaskIds) {
|
|
530
|
+
export async function recoverDueA2AContinuationIds(limit = 5, integrationTaskIds, confirmedDeliveryOnly = false) {
|
|
417
531
|
await ensureTable();
|
|
418
532
|
const client = getDbExec();
|
|
419
533
|
const now = Date.now();
|
|
@@ -425,20 +539,25 @@ export async function recoverDueA2AContinuationIds(limit = 5, integrationTaskIds
|
|
|
425
539
|
? ` AND integration_task_id IN (${integrationTaskIds.map(() => "?").join(", ")})`
|
|
426
540
|
: "";
|
|
427
541
|
const taskArgs = integrationTaskIds ?? [];
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
542
|
+
const receiptFilter = confirmedDeliveryOnly
|
|
543
|
+
? " AND terminal_delivery_confirmed_at IS NOT NULL"
|
|
544
|
+
: "";
|
|
545
|
+
// If a processor dies after a provider receipt, retry history-only custody
|
|
546
|
+
// as soon as its short follow-up deadline passes. A pre-receipt delivery
|
|
547
|
+
// claim retains the longer stale cutoff before an at-least-once resend.
|
|
431
548
|
await client.execute({
|
|
432
549
|
sql: `UPDATE integration_a2a_continuations
|
|
433
550
|
SET status = ?, next_check_at = ?, updated_at = ?
|
|
434
|
-
WHERE status = 'delivering'
|
|
435
|
-
|
|
551
|
+
WHERE status = 'delivering'
|
|
552
|
+
AND ((terminal_delivery_confirmed_at IS NOT NULL AND next_check_at <= ?)
|
|
553
|
+
OR updated_at <= ?)${taskFilter}${receiptFilter}`,
|
|
554
|
+
args: ["pending", now, now, now, now - 5 * 60 * 1000, ...taskArgs],
|
|
436
555
|
});
|
|
437
556
|
await client.execute({
|
|
438
557
|
sql: `UPDATE integration_a2a_continuations
|
|
439
558
|
SET status = ?, next_check_at = ?, updated_at = ?
|
|
440
559
|
WHERE status = 'processing'
|
|
441
|
-
AND (updated_at <= ? OR next_check_at <= ?)${taskFilter}`,
|
|
560
|
+
AND (updated_at <= ? OR next_check_at <= ?)${taskFilter}${receiptFilter}`,
|
|
442
561
|
args: [
|
|
443
562
|
"pending",
|
|
444
563
|
now,
|
|
@@ -450,7 +569,7 @@ export async function recoverDueA2AContinuationIds(limit = 5, integrationTaskIds
|
|
|
450
569
|
});
|
|
451
570
|
const { rows } = await client.execute({
|
|
452
571
|
sql: `SELECT id FROM integration_a2a_continuations
|
|
453
|
-
WHERE status = 'pending' AND next_check_at <= ?${taskFilter}
|
|
572
|
+
WHERE status = 'pending' AND next_check_at <= ?${taskFilter}${receiptFilter}
|
|
454
573
|
ORDER BY next_check_at ASC
|
|
455
574
|
LIMIT ?`,
|
|
456
575
|
args: [now, ...taskArgs, limit],
|
|
@@ -470,7 +589,13 @@ export async function listRecoverableA2AIntegrationTasks(limit = 50) {
|
|
|
470
589
|
const now = Date.now();
|
|
471
590
|
const { rows } = await getDbExec().execute({
|
|
472
591
|
sql: `SELECT DISTINCT c.integration_task_id, t.platform,
|
|
473
|
-
t.external_thread_id, t.dispatch_scope, t.status
|
|
592
|
+
t.external_thread_id, t.dispatch_scope, t.status,
|
|
593
|
+
EXISTS (
|
|
594
|
+
SELECT 1 FROM integration_a2a_continuations receipt
|
|
595
|
+
WHERE receipt.integration_task_id = c.integration_task_id
|
|
596
|
+
AND receipt.terminal_delivery_confirmed_at IS NOT NULL
|
|
597
|
+
AND receipt.status IN ('pending', 'processing', 'delivering')
|
|
598
|
+
) AS has_pending_confirmed_delivery
|
|
474
599
|
FROM integration_a2a_continuations c
|
|
475
600
|
INNER JOIN integration_pending_tasks t
|
|
476
601
|
ON t.id = c.integration_task_id
|
|
@@ -478,13 +603,16 @@ export async function listRecoverableA2AIntegrationTasks(limit = 50) {
|
|
|
478
603
|
AND ((c.status = 'pending' AND c.next_check_at <= ?)
|
|
479
604
|
OR (c.status = 'processing' AND
|
|
480
605
|
(c.updated_at <= ? OR c.next_check_at <= ?))
|
|
481
|
-
OR (c.status = 'delivering' AND
|
|
606
|
+
OR (c.status = 'delivering' AND
|
|
607
|
+
((c.terminal_delivery_confirmed_at IS NOT NULL AND c.next_check_at <= ?)
|
|
608
|
+
OR c.updated_at <= ?)))
|
|
482
609
|
ORDER BY c.integration_task_id ASC
|
|
483
610
|
LIMIT ?`,
|
|
484
611
|
args: [
|
|
485
612
|
now,
|
|
486
613
|
now - PROCESSING_STUCK_AFTER_MS,
|
|
487
614
|
now - PROCESSING_NEXT_CHECK_STALE_AFTER_MS,
|
|
615
|
+
now,
|
|
488
616
|
now - 5 * 60 * 1000,
|
|
489
617
|
Math.max(1, Math.min(Math.floor(limit), 200)),
|
|
490
618
|
],
|
|
@@ -495,6 +623,8 @@ export async function listRecoverableA2AIntegrationTasks(limit = 50) {
|
|
|
495
623
|
externalThreadId: String(row.external_thread_id),
|
|
496
624
|
dispatchScope: row.dispatch_scope ?? null,
|
|
497
625
|
status: String(row.status),
|
|
626
|
+
hasPendingConfirmedDelivery: row.has_pending_confirmed_delivery === true ||
|
|
627
|
+
Number(row.has_pending_confirmed_delivery ?? 0) === 1,
|
|
498
628
|
}));
|
|
499
629
|
}
|
|
500
630
|
export async function claimA2AContinuationDelivery(id) {
|
|
@@ -537,6 +667,105 @@ export async function rescheduleA2AContinuation(id, delayMs) {
|
|
|
537
667
|
args: ["pending", now + delayMs, now, id],
|
|
538
668
|
});
|
|
539
669
|
}
|
|
670
|
+
export async function retainA2AUnconfirmedDeliveryClaim(id) {
|
|
671
|
+
await ensureTable();
|
|
672
|
+
const now = Date.now();
|
|
673
|
+
await getDbExec().execute({
|
|
674
|
+
sql: `UPDATE integration_a2a_continuations
|
|
675
|
+
SET next_check_at = ?, updated_at = ?
|
|
676
|
+
WHERE id = ? AND status = 'delivering'
|
|
677
|
+
AND terminal_delivery_confirmed_at IS NULL`,
|
|
678
|
+
args: [now + PROCESSING_STUCK_AFTER_MS, now, id],
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
export async function saveA2AVerifiedArtifactCheckpoint(id, checkpoint) {
|
|
682
|
+
await ensureTable();
|
|
683
|
+
const normalized = checkpoint.trim();
|
|
684
|
+
if (!normalized)
|
|
685
|
+
return null;
|
|
686
|
+
if (normalized.length > MAX_VERIFIED_ARTIFACT_CHECKPOINT_CHARS) {
|
|
687
|
+
throw new Error(`Verified artifact checkpoint exceeds ${MAX_VERIFIED_ARTIFACT_CHECKPOINT_CHARS} characters`);
|
|
688
|
+
}
|
|
689
|
+
const now = Date.now();
|
|
690
|
+
await getDbExec().execute({
|
|
691
|
+
sql: `UPDATE integration_a2a_continuations
|
|
692
|
+
SET verified_artifact_checkpoint = ?, updated_at = ?
|
|
693
|
+
WHERE id = ? AND status IN ('pending', 'processing', 'delivering')`,
|
|
694
|
+
args: [normalized, now, id],
|
|
695
|
+
});
|
|
696
|
+
const persisted = await getA2AContinuation(id);
|
|
697
|
+
return persisted?.verifiedArtifactCheckpoint === normalized
|
|
698
|
+
? normalized
|
|
699
|
+
: null;
|
|
700
|
+
}
|
|
701
|
+
export async function recordA2ATerminalDeliveryReceipt(id, kind, historyPayload, errorMessage) {
|
|
702
|
+
await ensureTable();
|
|
703
|
+
const serializedPayload = JSON.stringify(historyPayload);
|
|
704
|
+
if (serializedPayload.length > MAX_TERMINAL_HISTORY_PAYLOAD_CHARS) {
|
|
705
|
+
throw new Error(`Terminal A2A history payload exceeds ${MAX_TERMINAL_HISTORY_PAYLOAD_CHARS} characters`);
|
|
706
|
+
}
|
|
707
|
+
const now = Date.now();
|
|
708
|
+
await getDbExec().execute({
|
|
709
|
+
sql: `UPDATE integration_a2a_continuations
|
|
710
|
+
SET status = 'delivering', terminal_delivery_kind = ?,
|
|
711
|
+
terminal_delivery_confirmed_at = COALESCE(terminal_delivery_confirmed_at, ?),
|
|
712
|
+
terminal_history_payload = ?, next_check_at = ?, updated_at = ?,
|
|
713
|
+
error_message = ?
|
|
714
|
+
WHERE id = ?
|
|
715
|
+
AND status IN ('processing', 'delivering')
|
|
716
|
+
AND (terminal_delivery_confirmed_at IS NULL
|
|
717
|
+
OR terminal_delivery_kind = ?)`,
|
|
718
|
+
args: [
|
|
719
|
+
kind,
|
|
720
|
+
now,
|
|
721
|
+
serializedPayload,
|
|
722
|
+
now + TERMINAL_HISTORY_FINALIZATION_LEASE_MS,
|
|
723
|
+
now,
|
|
724
|
+
errorMessage?.slice(0, 2000) ?? null,
|
|
725
|
+
id,
|
|
726
|
+
kind,
|
|
727
|
+
],
|
|
728
|
+
});
|
|
729
|
+
const persisted = await getA2AContinuation(id);
|
|
730
|
+
if (persisted?.status !== "delivering" ||
|
|
731
|
+
persisted.terminalDeliveryKind !== kind ||
|
|
732
|
+
persisted.terminalDeliveryConfirmedAt == null ||
|
|
733
|
+
JSON.stringify(persisted.terminalHistoryPayload) !== serializedPayload) {
|
|
734
|
+
throw new Error("Terminal A2A delivery confirmation did not persist");
|
|
735
|
+
}
|
|
736
|
+
return persisted;
|
|
737
|
+
}
|
|
738
|
+
export async function finalizeA2ATerminalHistory(id) {
|
|
739
|
+
await ensureTable();
|
|
740
|
+
const continuation = await getA2AContinuation(id);
|
|
741
|
+
if (continuation &&
|
|
742
|
+
(continuation.status === "completed" || continuation.status === "failed") &&
|
|
743
|
+
continuation.terminalDeliveryConfirmedAt != null &&
|
|
744
|
+
continuation.terminalHistoryPayload == null) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
if (!continuation?.terminalDeliveryKind ||
|
|
748
|
+
continuation.terminalDeliveryConfirmedAt == null ||
|
|
749
|
+
!continuation.terminalHistoryPayload) {
|
|
750
|
+
throw new Error("Terminal A2A history cannot finalize without a receipt");
|
|
751
|
+
}
|
|
752
|
+
const now = Date.now();
|
|
753
|
+
const status = continuation.terminalDeliveryKind === "success" ? "completed" : "failed";
|
|
754
|
+
await getDbExec().execute({
|
|
755
|
+
sql: `UPDATE integration_a2a_continuations
|
|
756
|
+
SET status = ?, updated_at = ?, completed_at = ?, incoming_payload = ?,
|
|
757
|
+
a2a_auth_token = NULL, progress_ref = NULL,
|
|
758
|
+
verified_artifact_checkpoint = NULL, terminal_history_payload = NULL
|
|
759
|
+
WHERE id = ? AND status IN ('pending', 'processing', 'delivering')
|
|
760
|
+
AND terminal_delivery_confirmed_at IS NOT NULL`,
|
|
761
|
+
args: [status, now, now, "{}", id],
|
|
762
|
+
});
|
|
763
|
+
const persisted = await getA2AContinuation(id);
|
|
764
|
+
if (persisted?.status !== status ||
|
|
765
|
+
persisted.terminalHistoryPayload != null) {
|
|
766
|
+
throw new Error("Terminal A2A history finalization did not persist");
|
|
767
|
+
}
|
|
768
|
+
}
|
|
540
769
|
export async function completeA2AContinuation(id) {
|
|
541
770
|
await ensureTable();
|
|
542
771
|
const client = getDbExec();
|
|
@@ -544,9 +773,13 @@ export async function completeA2AContinuation(id) {
|
|
|
544
773
|
await client.execute({
|
|
545
774
|
sql: `UPDATE integration_a2a_continuations
|
|
546
775
|
SET status = ?, updated_at = ?, completed_at = ?,
|
|
547
|
-
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL
|
|
776
|
+
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL,
|
|
777
|
+
verified_artifact_checkpoint = NULL,
|
|
778
|
+
terminal_delivery_kind = COALESCE(terminal_delivery_kind, 'success'),
|
|
779
|
+
terminal_delivery_confirmed_at = COALESCE(terminal_delivery_confirmed_at, ?),
|
|
780
|
+
terminal_history_payload = NULL
|
|
548
781
|
WHERE id = ? AND status IN ('processing', 'delivering', 'completed')`,
|
|
549
|
-
args: ["completed", now, now, "{}", id],
|
|
782
|
+
args: ["completed", now, now, "{}", now, id],
|
|
550
783
|
});
|
|
551
784
|
}
|
|
552
785
|
export async function failA2AContinuation(id, errorMessage) {
|
|
@@ -556,7 +789,8 @@ export async function failA2AContinuation(id, errorMessage) {
|
|
|
556
789
|
await client.execute({
|
|
557
790
|
sql: `UPDATE integration_a2a_continuations
|
|
558
791
|
SET status = ?, updated_at = ?, error_message = ?,
|
|
559
|
-
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL
|
|
792
|
+
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL,
|
|
793
|
+
verified_artifact_checkpoint = NULL
|
|
560
794
|
WHERE id = ? AND status <> 'completed'`,
|
|
561
795
|
args: ["failed", now, errorMessage.slice(0, 2000), "{}", id],
|
|
562
796
|
});
|