@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { A2AArtifactIdentity } from "../a2a/artifact-response.js";
|
|
1
2
|
import {
|
|
2
3
|
getDbExec,
|
|
3
4
|
isPostgres,
|
|
@@ -15,6 +16,9 @@ import type { IncomingMessage, PlatformRunProgressRef } from "./types.js";
|
|
|
15
16
|
let _initPromise: Promise<void> | undefined;
|
|
16
17
|
const PROCESSING_STUCK_AFTER_MS = 5 * 60 * 1000;
|
|
17
18
|
const PROCESSING_NEXT_CHECK_STALE_AFTER_MS = 60 * 1000;
|
|
19
|
+
const TERMINAL_HISTORY_FINALIZATION_LEASE_MS = 60 * 1000;
|
|
20
|
+
const MAX_VERIFIED_ARTIFACT_CHECKPOINT_CHARS = 16_000;
|
|
21
|
+
const MAX_TERMINAL_HISTORY_PAYLOAD_CHARS = 64_000;
|
|
18
22
|
|
|
19
23
|
// Build the CREATE SQL lazily (not at module scope) so intType() runs at
|
|
20
24
|
// RUNTIME, not import time — a module-scope call breaks any consumer whose
|
|
@@ -37,6 +41,10 @@ function buildCreateSql(): string {
|
|
|
37
41
|
dedupe_key TEXT,
|
|
38
42
|
a2a_task_id TEXT NOT NULL,
|
|
39
43
|
a2a_auth_token TEXT,
|
|
44
|
+
verified_artifact_checkpoint TEXT,
|
|
45
|
+
terminal_delivery_kind TEXT,
|
|
46
|
+
terminal_delivery_confirmed_at ${intType()},
|
|
47
|
+
terminal_history_payload TEXT,
|
|
40
48
|
status TEXT NOT NULL,
|
|
41
49
|
attempts ${intType()} NOT NULL DEFAULT 0,
|
|
42
50
|
next_check_at ${intType()} NOT NULL,
|
|
@@ -88,6 +96,27 @@ async function ensureTable(): Promise<void> {
|
|
|
88
96
|
"progress_ref_claimed",
|
|
89
97
|
`ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS progress_ref_claimed ${intType()} NOT NULL DEFAULT 0`,
|
|
90
98
|
);
|
|
99
|
+
await ensureColumnExists(
|
|
100
|
+
"integration_a2a_continuations",
|
|
101
|
+
"verified_artifact_checkpoint",
|
|
102
|
+
`ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS verified_artifact_checkpoint TEXT`,
|
|
103
|
+
);
|
|
104
|
+
await ensureColumnExists(
|
|
105
|
+
"integration_a2a_continuations",
|
|
106
|
+
"terminal_delivery_kind",
|
|
107
|
+
`ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS terminal_delivery_kind TEXT`,
|
|
108
|
+
);
|
|
109
|
+
await ensureColumnExists(
|
|
110
|
+
"integration_a2a_continuations",
|
|
111
|
+
"terminal_delivery_confirmed_at",
|
|
112
|
+
`ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS terminal_delivery_confirmed_at ${intType()}`,
|
|
113
|
+
);
|
|
114
|
+
await ensureColumnExists(
|
|
115
|
+
"integration_a2a_continuations",
|
|
116
|
+
"terminal_history_payload",
|
|
117
|
+
`ALTER TABLE integration_a2a_continuations ADD COLUMN IF NOT EXISTS terminal_history_payload TEXT`,
|
|
118
|
+
);
|
|
119
|
+
await backfillLegacyCompletedDeliveries(client);
|
|
91
120
|
await backfillProgressRefOwners(client);
|
|
92
121
|
await ensureIndexExists(
|
|
93
122
|
"idx_a2a_continuations_dedupe_key",
|
|
@@ -123,6 +152,11 @@ async function ensureTable(): Promise<void> {
|
|
|
123
152
|
"progress_ref_claimed",
|
|
124
153
|
`${intType()} NOT NULL DEFAULT 0`,
|
|
125
154
|
);
|
|
155
|
+
await addColumnIfMissing("verified_artifact_checkpoint", "TEXT");
|
|
156
|
+
await addColumnIfMissing("terminal_delivery_kind", "TEXT");
|
|
157
|
+
await addColumnIfMissing("terminal_delivery_confirmed_at", intType());
|
|
158
|
+
await addColumnIfMissing("terminal_history_payload", "TEXT");
|
|
159
|
+
await backfillLegacyCompletedDeliveries(client);
|
|
126
160
|
await backfillProgressRefOwners(client);
|
|
127
161
|
await retryOnDdlRace(() =>
|
|
128
162
|
client.execute(
|
|
@@ -143,6 +177,10 @@ async function ensureTable(): Promise<void> {
|
|
|
143
177
|
return _initPromise;
|
|
144
178
|
}
|
|
145
179
|
|
|
180
|
+
export async function ensureA2AContinuationsTable(): Promise<void> {
|
|
181
|
+
await ensureTable();
|
|
182
|
+
}
|
|
183
|
+
|
|
146
184
|
async function addColumnIfMissing(name: string, definition: string) {
|
|
147
185
|
try {
|
|
148
186
|
await retryOnDdlRace(() =>
|
|
@@ -183,6 +221,18 @@ async function backfillProgressRefOwners(
|
|
|
183
221
|
`);
|
|
184
222
|
}
|
|
185
223
|
|
|
224
|
+
async function backfillLegacyCompletedDeliveries(
|
|
225
|
+
client: ReturnType<typeof getDbExec>,
|
|
226
|
+
): Promise<void> {
|
|
227
|
+
await client.execute(`
|
|
228
|
+
UPDATE integration_a2a_continuations
|
|
229
|
+
SET terminal_delivery_kind = 'success',
|
|
230
|
+
terminal_delivery_confirmed_at = COALESCE(completed_at, updated_at)
|
|
231
|
+
WHERE status = 'completed'
|
|
232
|
+
AND terminal_delivery_confirmed_at IS NULL
|
|
233
|
+
`);
|
|
234
|
+
}
|
|
235
|
+
|
|
186
236
|
export type A2AContinuationStatus =
|
|
187
237
|
| "pending"
|
|
188
238
|
| "processing"
|
|
@@ -190,6 +240,21 @@ export type A2AContinuationStatus =
|
|
|
190
240
|
| "completed"
|
|
191
241
|
| "failed";
|
|
192
242
|
|
|
243
|
+
export type A2ATerminalDeliveryKind = "success" | "failure";
|
|
244
|
+
|
|
245
|
+
export interface A2ATerminalHistoryPayload {
|
|
246
|
+
text: string;
|
|
247
|
+
deliveredAt: string;
|
|
248
|
+
messageRefs: string[];
|
|
249
|
+
artifacts: A2AArtifactIdentity[];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type A2AContinuationTaskOutcome =
|
|
253
|
+
| "active"
|
|
254
|
+
| "terminal-delivered"
|
|
255
|
+
| "terminal-without-delivery"
|
|
256
|
+
| "missing";
|
|
257
|
+
|
|
193
258
|
export interface A2AContinuation {
|
|
194
259
|
id: string;
|
|
195
260
|
integrationTaskId: string;
|
|
@@ -206,6 +271,10 @@ export interface A2AContinuation {
|
|
|
206
271
|
dedupeKey: string | null;
|
|
207
272
|
a2aTaskId: string;
|
|
208
273
|
a2aAuthToken: string | null;
|
|
274
|
+
verifiedArtifactCheckpoint: string | null;
|
|
275
|
+
terminalDeliveryKind: A2ATerminalDeliveryKind | null;
|
|
276
|
+
terminalDeliveryConfirmedAt: number | null;
|
|
277
|
+
terminalHistoryPayload: A2ATerminalHistoryPayload | null;
|
|
209
278
|
status: A2AContinuationStatus;
|
|
210
279
|
attempts: number;
|
|
211
280
|
nextCheckAt: number;
|
|
@@ -270,6 +339,20 @@ function rowToContinuation(row: Record<string, unknown>): A2AContinuation {
|
|
|
270
339
|
dedupeKey: (row.dedupe_key as string | null) ?? null,
|
|
271
340
|
a2aTaskId: row.a2a_task_id as string,
|
|
272
341
|
a2aAuthToken: (row.a2a_auth_token as string | null) ?? null,
|
|
342
|
+
verifiedArtifactCheckpoint:
|
|
343
|
+
(row.verified_artifact_checkpoint as string | null) ?? null,
|
|
344
|
+
terminalDeliveryKind:
|
|
345
|
+
row.terminal_delivery_kind === "success" ||
|
|
346
|
+
row.terminal_delivery_kind === "failure"
|
|
347
|
+
? row.terminal_delivery_kind
|
|
348
|
+
: null,
|
|
349
|
+
terminalDeliveryConfirmedAt:
|
|
350
|
+
row.terminal_delivery_confirmed_at == null
|
|
351
|
+
? null
|
|
352
|
+
: Number(row.terminal_delivery_confirmed_at),
|
|
353
|
+
terminalHistoryPayload: parseTerminalHistoryPayload(
|
|
354
|
+
row.terminal_history_payload,
|
|
355
|
+
),
|
|
273
356
|
status: row.status as A2AContinuationStatus,
|
|
274
357
|
attempts: Number(row.attempts ?? 0),
|
|
275
358
|
nextCheckAt: Number(row.next_check_at ?? 0),
|
|
@@ -281,6 +364,42 @@ function rowToContinuation(row: Record<string, unknown>): A2AContinuation {
|
|
|
281
364
|
};
|
|
282
365
|
}
|
|
283
366
|
|
|
367
|
+
function parseTerminalHistoryPayload(
|
|
368
|
+
value: unknown,
|
|
369
|
+
): A2ATerminalHistoryPayload | null {
|
|
370
|
+
if (typeof value !== "string" || !value) return null;
|
|
371
|
+
try {
|
|
372
|
+
const parsed = JSON.parse(value) as Partial<A2ATerminalHistoryPayload>;
|
|
373
|
+
if (
|
|
374
|
+
typeof parsed.text !== "string" ||
|
|
375
|
+
typeof parsed.deliveredAt !== "string" ||
|
|
376
|
+
!Array.isArray(parsed.messageRefs) ||
|
|
377
|
+
!parsed.messageRefs.every((ref) => typeof ref === "string") ||
|
|
378
|
+
!Array.isArray(parsed.artifacts)
|
|
379
|
+
) {
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
text: parsed.text,
|
|
384
|
+
deliveredAt: parsed.deliveredAt,
|
|
385
|
+
messageRefs: parsed.messageRefs,
|
|
386
|
+
artifacts: parsed.artifacts.filter(
|
|
387
|
+
(artifact): artifact is A2AArtifactIdentity =>
|
|
388
|
+
!!artifact &&
|
|
389
|
+
typeof artifact === "object" &&
|
|
390
|
+
!Array.isArray(artifact) &&
|
|
391
|
+
typeof (artifact as { id?: unknown }).id === "string" &&
|
|
392
|
+
typeof (artifact as { resourceType?: unknown }).resourceType ===
|
|
393
|
+
"string" &&
|
|
394
|
+
typeof (artifact as { sourceAction?: unknown }).sourceAction ===
|
|
395
|
+
"string",
|
|
396
|
+
),
|
|
397
|
+
};
|
|
398
|
+
} catch {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
284
403
|
export async function insertA2AContinuation(input: {
|
|
285
404
|
integrationTaskId: string;
|
|
286
405
|
platform: string;
|
|
@@ -432,6 +551,67 @@ export async function hasActiveA2AContinuationsForIntegrationTask(
|
|
|
432
551
|
return rows.length > 0;
|
|
433
552
|
}
|
|
434
553
|
|
|
554
|
+
export async function getA2AContinuationTaskOutcome(
|
|
555
|
+
integrationTaskId: string,
|
|
556
|
+
): Promise<A2AContinuationTaskOutcome> {
|
|
557
|
+
await ensureTable();
|
|
558
|
+
const { rows } = await getDbExec().execute({
|
|
559
|
+
sql: `SELECT status, terminal_delivery_confirmed_at
|
|
560
|
+
FROM integration_a2a_continuations
|
|
561
|
+
WHERE integration_task_id = ?`,
|
|
562
|
+
args: [integrationTaskId],
|
|
563
|
+
});
|
|
564
|
+
if (rows.length === 0) return "missing";
|
|
565
|
+
if (
|
|
566
|
+
rows.some((row) =>
|
|
567
|
+
["pending", "processing", "delivering"].includes(String(row.status)),
|
|
568
|
+
)
|
|
569
|
+
) {
|
|
570
|
+
return "active";
|
|
571
|
+
}
|
|
572
|
+
return rows.every((row) => row.terminal_delivery_confirmed_at != null)
|
|
573
|
+
? "terminal-delivered"
|
|
574
|
+
: "terminal-without-delivery";
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
export async function hasPendingConfirmedA2ADeliveryForIntegrationTask(
|
|
578
|
+
integrationTaskId: string,
|
|
579
|
+
): Promise<boolean> {
|
|
580
|
+
await ensureTable();
|
|
581
|
+
const { rows } = await getDbExec().execute({
|
|
582
|
+
sql: `SELECT 1 AS confirmed FROM integration_a2a_continuations
|
|
583
|
+
WHERE integration_task_id = ?
|
|
584
|
+
AND status IN ('pending', 'processing', 'delivering')
|
|
585
|
+
AND terminal_delivery_confirmed_at IS NOT NULL
|
|
586
|
+
LIMIT 1`,
|
|
587
|
+
args: [integrationTaskId],
|
|
588
|
+
});
|
|
589
|
+
return rows.length > 0;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export async function hasOnlyLegacyFailedA2AContinuationsForIntegrationTask(
|
|
593
|
+
integrationTaskId: string,
|
|
594
|
+
): Promise<boolean> {
|
|
595
|
+
await ensureTable();
|
|
596
|
+
const { rows } = await getDbExec().execute({
|
|
597
|
+
sql: `SELECT status, terminal_delivery_kind,
|
|
598
|
+
terminal_delivery_confirmed_at, terminal_history_payload
|
|
599
|
+
FROM integration_a2a_continuations
|
|
600
|
+
WHERE integration_task_id = ?`,
|
|
601
|
+
args: [integrationTaskId],
|
|
602
|
+
});
|
|
603
|
+
return (
|
|
604
|
+
rows.length > 0 &&
|
|
605
|
+
rows.every(
|
|
606
|
+
(row) =>
|
|
607
|
+
String(row.status) === "failed" &&
|
|
608
|
+
row.terminal_delivery_kind == null &&
|
|
609
|
+
row.terminal_delivery_confirmed_at == null &&
|
|
610
|
+
row.terminal_history_payload == null,
|
|
611
|
+
)
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
435
615
|
export async function failA2AContinuationsForIntegrationTask(
|
|
436
616
|
integrationTaskId: string,
|
|
437
617
|
errorMessage: string,
|
|
@@ -440,9 +620,11 @@ export async function failA2AContinuationsForIntegrationTask(
|
|
|
440
620
|
const now = Date.now();
|
|
441
621
|
await getDbExec().execute({
|
|
442
622
|
sql: `UPDATE integration_a2a_continuations
|
|
443
|
-
SET status = 'failed', error_message = ?, updated_at = ?, completed_at =
|
|
623
|
+
SET status = 'failed', error_message = ?, updated_at = ?, completed_at = ?,
|
|
624
|
+
verified_artifact_checkpoint = NULL
|
|
444
625
|
WHERE integration_task_id = ?
|
|
445
|
-
AND status IN ('pending', 'processing', 'delivering')
|
|
626
|
+
AND status IN ('pending', 'processing', 'delivering')
|
|
627
|
+
AND terminal_delivery_confirmed_at IS NULL`,
|
|
446
628
|
args: [errorMessage.slice(0, 2000), now, now, integrationTaskId],
|
|
447
629
|
});
|
|
448
630
|
}
|
|
@@ -580,6 +762,7 @@ export async function claimDueA2AContinuations(
|
|
|
580
762
|
export async function recoverDueA2AContinuationIds(
|
|
581
763
|
limit = 5,
|
|
582
764
|
integrationTaskIds?: string[],
|
|
765
|
+
confirmedDeliveryOnly = false,
|
|
583
766
|
): Promise<string[]> {
|
|
584
767
|
await ensureTable();
|
|
585
768
|
const client = getDbExec();
|
|
@@ -591,20 +774,25 @@ export async function recoverDueA2AContinuationIds(
|
|
|
591
774
|
? ` AND integration_task_id IN (${integrationTaskIds.map(() => "?").join(", ")})`
|
|
592
775
|
: "";
|
|
593
776
|
const taskArgs = integrationTaskIds ?? [];
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
777
|
+
const receiptFilter = confirmedDeliveryOnly
|
|
778
|
+
? " AND terminal_delivery_confirmed_at IS NOT NULL"
|
|
779
|
+
: "";
|
|
780
|
+
// If a processor dies after a provider receipt, retry history-only custody
|
|
781
|
+
// as soon as its short follow-up deadline passes. A pre-receipt delivery
|
|
782
|
+
// claim retains the longer stale cutoff before an at-least-once resend.
|
|
597
783
|
await client.execute({
|
|
598
784
|
sql: `UPDATE integration_a2a_continuations
|
|
599
785
|
SET status = ?, next_check_at = ?, updated_at = ?
|
|
600
|
-
WHERE status = 'delivering'
|
|
601
|
-
|
|
786
|
+
WHERE status = 'delivering'
|
|
787
|
+
AND ((terminal_delivery_confirmed_at IS NOT NULL AND next_check_at <= ?)
|
|
788
|
+
OR updated_at <= ?)${taskFilter}${receiptFilter}`,
|
|
789
|
+
args: ["pending", now, now, now, now - 5 * 60 * 1000, ...taskArgs],
|
|
602
790
|
});
|
|
603
791
|
await client.execute({
|
|
604
792
|
sql: `UPDATE integration_a2a_continuations
|
|
605
793
|
SET status = ?, next_check_at = ?, updated_at = ?
|
|
606
794
|
WHERE status = 'processing'
|
|
607
|
-
AND (updated_at <= ? OR next_check_at <= ?)${taskFilter}`,
|
|
795
|
+
AND (updated_at <= ? OR next_check_at <= ?)${taskFilter}${receiptFilter}`,
|
|
608
796
|
args: [
|
|
609
797
|
"pending",
|
|
610
798
|
now,
|
|
@@ -616,7 +804,7 @@ export async function recoverDueA2AContinuationIds(
|
|
|
616
804
|
});
|
|
617
805
|
const { rows } = await client.execute({
|
|
618
806
|
sql: `SELECT id FROM integration_a2a_continuations
|
|
619
|
-
WHERE status = 'pending' AND next_check_at <= ?${taskFilter}
|
|
807
|
+
WHERE status = 'pending' AND next_check_at <= ?${taskFilter}${receiptFilter}
|
|
620
808
|
ORDER BY next_check_at ASC
|
|
621
809
|
LIMIT ?`,
|
|
622
810
|
args: [now, ...taskArgs, limit],
|
|
@@ -637,6 +825,7 @@ export interface RecoverableA2AIntegrationTask {
|
|
|
637
825
|
externalThreadId: string;
|
|
638
826
|
dispatchScope: string | null;
|
|
639
827
|
status: string;
|
|
828
|
+
hasPendingConfirmedDelivery: boolean;
|
|
640
829
|
}
|
|
641
830
|
|
|
642
831
|
/**
|
|
@@ -650,7 +839,13 @@ export async function listRecoverableA2AIntegrationTasks(
|
|
|
650
839
|
const now = Date.now();
|
|
651
840
|
const { rows } = await getDbExec().execute({
|
|
652
841
|
sql: `SELECT DISTINCT c.integration_task_id, t.platform,
|
|
653
|
-
t.external_thread_id, t.dispatch_scope, t.status
|
|
842
|
+
t.external_thread_id, t.dispatch_scope, t.status,
|
|
843
|
+
EXISTS (
|
|
844
|
+
SELECT 1 FROM integration_a2a_continuations receipt
|
|
845
|
+
WHERE receipt.integration_task_id = c.integration_task_id
|
|
846
|
+
AND receipt.terminal_delivery_confirmed_at IS NOT NULL
|
|
847
|
+
AND receipt.status IN ('pending', 'processing', 'delivering')
|
|
848
|
+
) AS has_pending_confirmed_delivery
|
|
654
849
|
FROM integration_a2a_continuations c
|
|
655
850
|
INNER JOIN integration_pending_tasks t
|
|
656
851
|
ON t.id = c.integration_task_id
|
|
@@ -658,13 +853,16 @@ export async function listRecoverableA2AIntegrationTasks(
|
|
|
658
853
|
AND ((c.status = 'pending' AND c.next_check_at <= ?)
|
|
659
854
|
OR (c.status = 'processing' AND
|
|
660
855
|
(c.updated_at <= ? OR c.next_check_at <= ?))
|
|
661
|
-
OR (c.status = 'delivering' AND
|
|
856
|
+
OR (c.status = 'delivering' AND
|
|
857
|
+
((c.terminal_delivery_confirmed_at IS NOT NULL AND c.next_check_at <= ?)
|
|
858
|
+
OR c.updated_at <= ?)))
|
|
662
859
|
ORDER BY c.integration_task_id ASC
|
|
663
860
|
LIMIT ?`,
|
|
664
861
|
args: [
|
|
665
862
|
now,
|
|
666
863
|
now - PROCESSING_STUCK_AFTER_MS,
|
|
667
864
|
now - PROCESSING_NEXT_CHECK_STALE_AFTER_MS,
|
|
865
|
+
now,
|
|
668
866
|
now - 5 * 60 * 1000,
|
|
669
867
|
Math.max(1, Math.min(Math.floor(limit), 200)),
|
|
670
868
|
],
|
|
@@ -675,6 +873,9 @@ export async function listRecoverableA2AIntegrationTasks(
|
|
|
675
873
|
externalThreadId: String(row.external_thread_id),
|
|
676
874
|
dispatchScope: (row.dispatch_scope as string | null) ?? null,
|
|
677
875
|
status: String(row.status),
|
|
876
|
+
hasPendingConfirmedDelivery:
|
|
877
|
+
row.has_pending_confirmed_delivery === true ||
|
|
878
|
+
Number(row.has_pending_confirmed_delivery ?? 0) === 1,
|
|
678
879
|
}));
|
|
679
880
|
}
|
|
680
881
|
|
|
@@ -723,6 +924,131 @@ export async function rescheduleA2AContinuation(
|
|
|
723
924
|
});
|
|
724
925
|
}
|
|
725
926
|
|
|
927
|
+
export async function retainA2AUnconfirmedDeliveryClaim(
|
|
928
|
+
id: string,
|
|
929
|
+
): Promise<void> {
|
|
930
|
+
await ensureTable();
|
|
931
|
+
const now = Date.now();
|
|
932
|
+
await getDbExec().execute({
|
|
933
|
+
sql: `UPDATE integration_a2a_continuations
|
|
934
|
+
SET next_check_at = ?, updated_at = ?
|
|
935
|
+
WHERE id = ? AND status = 'delivering'
|
|
936
|
+
AND terminal_delivery_confirmed_at IS NULL`,
|
|
937
|
+
args: [now + PROCESSING_STUCK_AFTER_MS, now, id],
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
export async function saveA2AVerifiedArtifactCheckpoint(
|
|
942
|
+
id: string,
|
|
943
|
+
checkpoint: string,
|
|
944
|
+
): Promise<string | null> {
|
|
945
|
+
await ensureTable();
|
|
946
|
+
const normalized = checkpoint.trim();
|
|
947
|
+
if (!normalized) return null;
|
|
948
|
+
if (normalized.length > MAX_VERIFIED_ARTIFACT_CHECKPOINT_CHARS) {
|
|
949
|
+
throw new Error(
|
|
950
|
+
`Verified artifact checkpoint exceeds ${MAX_VERIFIED_ARTIFACT_CHECKPOINT_CHARS} characters`,
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
const now = Date.now();
|
|
954
|
+
await getDbExec().execute({
|
|
955
|
+
sql: `UPDATE integration_a2a_continuations
|
|
956
|
+
SET verified_artifact_checkpoint = ?, updated_at = ?
|
|
957
|
+
WHERE id = ? AND status IN ('pending', 'processing', 'delivering')`,
|
|
958
|
+
args: [normalized, now, id],
|
|
959
|
+
});
|
|
960
|
+
const persisted = await getA2AContinuation(id);
|
|
961
|
+
return persisted?.verifiedArtifactCheckpoint === normalized
|
|
962
|
+
? normalized
|
|
963
|
+
: null;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
export async function recordA2ATerminalDeliveryReceipt(
|
|
967
|
+
id: string,
|
|
968
|
+
kind: A2ATerminalDeliveryKind,
|
|
969
|
+
historyPayload: A2ATerminalHistoryPayload,
|
|
970
|
+
errorMessage?: string,
|
|
971
|
+
): Promise<A2AContinuation> {
|
|
972
|
+
await ensureTable();
|
|
973
|
+
const serializedPayload = JSON.stringify(historyPayload);
|
|
974
|
+
if (serializedPayload.length > MAX_TERMINAL_HISTORY_PAYLOAD_CHARS) {
|
|
975
|
+
throw new Error(
|
|
976
|
+
`Terminal A2A history payload exceeds ${MAX_TERMINAL_HISTORY_PAYLOAD_CHARS} characters`,
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
const now = Date.now();
|
|
980
|
+
await getDbExec().execute({
|
|
981
|
+
sql: `UPDATE integration_a2a_continuations
|
|
982
|
+
SET status = 'delivering', terminal_delivery_kind = ?,
|
|
983
|
+
terminal_delivery_confirmed_at = COALESCE(terminal_delivery_confirmed_at, ?),
|
|
984
|
+
terminal_history_payload = ?, next_check_at = ?, updated_at = ?,
|
|
985
|
+
error_message = ?
|
|
986
|
+
WHERE id = ?
|
|
987
|
+
AND status IN ('processing', 'delivering')
|
|
988
|
+
AND (terminal_delivery_confirmed_at IS NULL
|
|
989
|
+
OR terminal_delivery_kind = ?)`,
|
|
990
|
+
args: [
|
|
991
|
+
kind,
|
|
992
|
+
now,
|
|
993
|
+
serializedPayload,
|
|
994
|
+
now + TERMINAL_HISTORY_FINALIZATION_LEASE_MS,
|
|
995
|
+
now,
|
|
996
|
+
errorMessage?.slice(0, 2000) ?? null,
|
|
997
|
+
id,
|
|
998
|
+
kind,
|
|
999
|
+
],
|
|
1000
|
+
});
|
|
1001
|
+
const persisted = await getA2AContinuation(id);
|
|
1002
|
+
if (
|
|
1003
|
+
persisted?.status !== "delivering" ||
|
|
1004
|
+
persisted.terminalDeliveryKind !== kind ||
|
|
1005
|
+
persisted.terminalDeliveryConfirmedAt == null ||
|
|
1006
|
+
JSON.stringify(persisted.terminalHistoryPayload) !== serializedPayload
|
|
1007
|
+
) {
|
|
1008
|
+
throw new Error("Terminal A2A delivery confirmation did not persist");
|
|
1009
|
+
}
|
|
1010
|
+
return persisted;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export async function finalizeA2ATerminalHistory(id: string): Promise<void> {
|
|
1014
|
+
await ensureTable();
|
|
1015
|
+
const continuation = await getA2AContinuation(id);
|
|
1016
|
+
if (
|
|
1017
|
+
continuation &&
|
|
1018
|
+
(continuation.status === "completed" || continuation.status === "failed") &&
|
|
1019
|
+
continuation.terminalDeliveryConfirmedAt != null &&
|
|
1020
|
+
continuation.terminalHistoryPayload == null
|
|
1021
|
+
) {
|
|
1022
|
+
return;
|
|
1023
|
+
}
|
|
1024
|
+
if (
|
|
1025
|
+
!continuation?.terminalDeliveryKind ||
|
|
1026
|
+
continuation.terminalDeliveryConfirmedAt == null ||
|
|
1027
|
+
!continuation.terminalHistoryPayload
|
|
1028
|
+
) {
|
|
1029
|
+
throw new Error("Terminal A2A history cannot finalize without a receipt");
|
|
1030
|
+
}
|
|
1031
|
+
const now = Date.now();
|
|
1032
|
+
const status =
|
|
1033
|
+
continuation.terminalDeliveryKind === "success" ? "completed" : "failed";
|
|
1034
|
+
await getDbExec().execute({
|
|
1035
|
+
sql: `UPDATE integration_a2a_continuations
|
|
1036
|
+
SET status = ?, updated_at = ?, completed_at = ?, incoming_payload = ?,
|
|
1037
|
+
a2a_auth_token = NULL, progress_ref = NULL,
|
|
1038
|
+
verified_artifact_checkpoint = NULL, terminal_history_payload = NULL
|
|
1039
|
+
WHERE id = ? AND status IN ('pending', 'processing', 'delivering')
|
|
1040
|
+
AND terminal_delivery_confirmed_at IS NOT NULL`,
|
|
1041
|
+
args: [status, now, now, "{}", id],
|
|
1042
|
+
});
|
|
1043
|
+
const persisted = await getA2AContinuation(id);
|
|
1044
|
+
if (
|
|
1045
|
+
persisted?.status !== status ||
|
|
1046
|
+
persisted.terminalHistoryPayload != null
|
|
1047
|
+
) {
|
|
1048
|
+
throw new Error("Terminal A2A history finalization did not persist");
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
726
1052
|
export async function completeA2AContinuation(id: string): Promise<void> {
|
|
727
1053
|
await ensureTable();
|
|
728
1054
|
const client = getDbExec();
|
|
@@ -730,9 +1056,13 @@ export async function completeA2AContinuation(id: string): Promise<void> {
|
|
|
730
1056
|
await client.execute({
|
|
731
1057
|
sql: `UPDATE integration_a2a_continuations
|
|
732
1058
|
SET status = ?, updated_at = ?, completed_at = ?,
|
|
733
|
-
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL
|
|
1059
|
+
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL,
|
|
1060
|
+
verified_artifact_checkpoint = NULL,
|
|
1061
|
+
terminal_delivery_kind = COALESCE(terminal_delivery_kind, 'success'),
|
|
1062
|
+
terminal_delivery_confirmed_at = COALESCE(terminal_delivery_confirmed_at, ?),
|
|
1063
|
+
terminal_history_payload = NULL
|
|
734
1064
|
WHERE id = ? AND status IN ('processing', 'delivering', 'completed')`,
|
|
735
|
-
args: ["completed", now, now, "{}", id],
|
|
1065
|
+
args: ["completed", now, now, "{}", now, id],
|
|
736
1066
|
});
|
|
737
1067
|
}
|
|
738
1068
|
|
|
@@ -746,7 +1076,8 @@ export async function failA2AContinuation(
|
|
|
746
1076
|
await client.execute({
|
|
747
1077
|
sql: `UPDATE integration_a2a_continuations
|
|
748
1078
|
SET status = ?, updated_at = ?, error_message = ?,
|
|
749
|
-
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL
|
|
1079
|
+
incoming_payload = ?, a2a_auth_token = NULL, progress_ref = NULL,
|
|
1080
|
+
verified_artifact_checkpoint = NULL
|
|
750
1081
|
WHERE id = ? AND status <> 'completed'`,
|
|
751
1082
|
args: ["failed", now, errorMessage.slice(0, 2000), "{}", id],
|
|
752
1083
|
});
|