@agent-native/core 0.130.0 → 0.130.2
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 +12 -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/core/src/server/email-template.ts +43 -13
- package/corpus/core/src/sharing/actions/share-resource.ts +26 -4
- package/corpus/core/src/sharing/index.ts +1 -0
- package/corpus/core/src/sharing/registry.ts +23 -0
- package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +3 -1
- package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
- package/corpus/templates/clips/app/components/meetings/day-header.tsx +1 -1
- package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +1 -22
- package/corpus/templates/clips/app/components/workspace/branding-editor.tsx +26 -1
- package/corpus/templates/clips/app/components/workspace/organization-identity-card.tsx +107 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -4
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +0 -10
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +23 -51
- package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +1 -8
- package/corpus/templates/clips/changelog/2026-07-29-clearing-all-recording-edits-no-longer-leaves-the-editor-unr.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-clip-share-emails-now-open-with-who-shared-the-clip-add-a-su.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-finished-meeting-pills-now-have-a-clear-close-button.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-meeting-cards-no-longer-flicker-or-disappear-when-hovered-in.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-organization-name-logo-brand-color-and-default-visibility-no.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-transcript-lines-on-the-meeting-page-no-longer-jump-to-the-r.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-29-when-a-meeting-wraps-up-the-recording-pill-now-shows-a-green.md +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +6 -3
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +112 -32
- package/corpus/templates/clips/desktop/src/styles.css +42 -2
- package/corpus/templates/clips/server/db/index.ts +6 -0
- package/corpus/templates/clips/server/lib/share-email-hero.ts +33 -0
- package/corpus/templates/clips/shared/clips-ai-prefs.ts +0 -1
- 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/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/file-upload/actions/upload-image.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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/server/email-template.d.ts +4 -0
- package/dist/server/email-template.d.ts.map +1 -1
- package/dist/server/email-template.js +25 -8
- package/dist/server/email-template.js.map +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/actions/share-resource.d.ts.map +1 -1
- package/dist/sharing/actions/share-resource.js +23 -4
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/index.d.ts +1 -1
- package/dist/sharing/index.d.ts.map +1 -1
- package/dist/sharing/index.js.map +1 -1
- package/dist/sharing/registry.d.ts +23 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +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
- package/src/server/email-template.ts +43 -13
- package/src/sharing/actions/share-resource.ts +26 -4
- package/src/sharing/index.ts +1 -0
- package/src/sharing/registry.ts +23 -0
- package/corpus/templates/clips/app/routes/_app.settings.organization.tsx +0 -232
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
appendA2AArtifactLinks,
|
|
3
3
|
extractA2AArtifactIdentities,
|
|
4
4
|
stripA2APersistedArtifactMarkers,
|
|
5
|
+
type A2AArtifactIdentity,
|
|
5
6
|
} from "../a2a/artifact-response.js";
|
|
6
7
|
import { A2AClient, signA2AToken } from "../a2a/client.js";
|
|
7
8
|
import type { Task } from "../a2a/types.js";
|
|
@@ -18,20 +19,28 @@ import {
|
|
|
18
19
|
claimA2AContinuation,
|
|
19
20
|
claimA2AContinuationDelivery,
|
|
20
21
|
claimDueA2AContinuations,
|
|
21
|
-
completeA2AContinuation,
|
|
22
22
|
failA2AContinuationsForIntegrationTask,
|
|
23
23
|
failA2AContinuation,
|
|
24
|
+
finalizeA2ATerminalHistory,
|
|
24
25
|
getA2AContinuation,
|
|
25
|
-
|
|
26
|
+
getA2AContinuationTaskOutcome,
|
|
27
|
+
hasOnlyLegacyFailedA2AContinuationsForIntegrationTask,
|
|
28
|
+
hasPendingConfirmedA2ADeliveryForIntegrationTask,
|
|
26
29
|
listRecoverableA2AIntegrationTasks,
|
|
27
30
|
recoverDueA2AContinuationIds,
|
|
31
|
+
recordA2ATerminalDeliveryReceipt,
|
|
32
|
+
retainA2AUnconfirmedDeliveryClaim,
|
|
28
33
|
rescheduleA2AContinuation,
|
|
34
|
+
saveA2AVerifiedArtifactCheckpoint,
|
|
29
35
|
type A2AContinuation,
|
|
36
|
+
type A2ATerminalDeliveryKind,
|
|
37
|
+
type A2ATerminalHistoryPayload,
|
|
30
38
|
type RecoverableA2AIntegrationTask,
|
|
31
39
|
} from "./a2a-continuations-store.js";
|
|
32
40
|
import {
|
|
33
41
|
completeIntegrationCampaignTaskAfterA2A,
|
|
34
42
|
failDisabledIntegrationCampaignTask,
|
|
43
|
+
failIntegrationCampaignTaskDeliveryContainment,
|
|
35
44
|
getIntegrationCampaignForTask,
|
|
36
45
|
} from "./integration-campaigns-store.js";
|
|
37
46
|
import {
|
|
@@ -67,6 +76,23 @@ const PLATFORM_SEND_TIMEOUT_MS = 12_000;
|
|
|
67
76
|
const DISPATCH_SETTLE_WAIT_MS = 2_000;
|
|
68
77
|
const COMPLETE_AFTER_DELIVERY_ATTEMPTS = 3;
|
|
69
78
|
|
|
79
|
+
function logA2AContinuationTransition(
|
|
80
|
+
event: string,
|
|
81
|
+
continuation: Pick<
|
|
82
|
+
A2AContinuation,
|
|
83
|
+
"id" | "integrationTaskId" | "a2aTaskId" | "attempts" | "status"
|
|
84
|
+
>,
|
|
85
|
+
): void {
|
|
86
|
+
console.info("[integrations] a2a-continuation-transition", {
|
|
87
|
+
event,
|
|
88
|
+
continuationId: continuation.id,
|
|
89
|
+
integrationTaskId: continuation.integrationTaskId,
|
|
90
|
+
downstreamTaskId: continuation.a2aTaskId,
|
|
91
|
+
attempt: continuation.attempts,
|
|
92
|
+
status: continuation.status,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
70
96
|
export async function dispatchA2AContinuation(
|
|
71
97
|
continuationId: string,
|
|
72
98
|
webhookBaseUrl?: string,
|
|
@@ -153,17 +179,129 @@ export async function processA2AContinuationById(
|
|
|
153
179
|
await processClaimedContinuation(continuation, options);
|
|
154
180
|
}
|
|
155
181
|
|
|
182
|
+
export async function recoverA2AContinuationAfterProcessorFailure(
|
|
183
|
+
continuationId: string,
|
|
184
|
+
options: {
|
|
185
|
+
adapters: Map<string, PlatformAdapter>;
|
|
186
|
+
reason: string;
|
|
187
|
+
},
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const continuation = await getA2AContinuation(continuationId);
|
|
190
|
+
if (
|
|
191
|
+
!continuation ||
|
|
192
|
+
continuation.status === "completed" ||
|
|
193
|
+
continuation.status === "failed"
|
|
194
|
+
) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (
|
|
198
|
+
continuation.terminalDeliveryConfirmedAt != null &&
|
|
199
|
+
continuation.terminalHistoryPayload
|
|
200
|
+
) {
|
|
201
|
+
await persistAndFinalizeConfirmedA2ADelivery(continuation);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const adapter = options.adapters.get(continuation.platform);
|
|
205
|
+
if (continuation.attempts < MAX_ATTEMPTS) {
|
|
206
|
+
logA2AContinuationTransition("processor_released", continuation);
|
|
207
|
+
await rescheduleAndRedispatchA2AContinuation(continuation.id);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (!adapter) {
|
|
211
|
+
const reason = `Unknown platform: ${continuation.platform}`;
|
|
212
|
+
logA2AContinuationTransition(
|
|
213
|
+
"processor_exhausted_without_adapter",
|
|
214
|
+
continuation,
|
|
215
|
+
);
|
|
216
|
+
await failA2AContinuationsForIntegrationTask(
|
|
217
|
+
continuation.integrationTaskId,
|
|
218
|
+
reason,
|
|
219
|
+
);
|
|
220
|
+
if (
|
|
221
|
+
await hasPendingConfirmedA2ADeliveryForIntegrationTask(
|
|
222
|
+
continuation.integrationTaskId,
|
|
223
|
+
)
|
|
224
|
+
) {
|
|
225
|
+
await dispatchPendingIntegrationTask({
|
|
226
|
+
taskId: continuation.integrationTaskId,
|
|
227
|
+
task: {
|
|
228
|
+
platform: continuation.platform,
|
|
229
|
+
externalThreadId: continuation.externalThreadId,
|
|
230
|
+
platformContext: continuation.incoming.platformContext,
|
|
231
|
+
},
|
|
232
|
+
campaignContinuation: true,
|
|
233
|
+
allowPortableConfirmedReceiptReconciliation: true,
|
|
234
|
+
}).catch((err) => {
|
|
235
|
+
console.error(
|
|
236
|
+
`[integrations] Failed to wake confirmed sibling history for ${continuation.integrationTaskId}:`,
|
|
237
|
+
err,
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
await failIntegrationCampaignTaskDeliveryContainment(
|
|
243
|
+
continuation.integrationTaskId,
|
|
244
|
+
reason,
|
|
245
|
+
);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const progress = await resumeA2AContinuationProgress(continuation, adapter);
|
|
250
|
+
if (continuation.verifiedArtifactCheckpoint) {
|
|
251
|
+
logA2AContinuationTransition(
|
|
252
|
+
"processor_exhausted_checkpoint_delivery",
|
|
253
|
+
continuation,
|
|
254
|
+
);
|
|
255
|
+
await deliverAndCompleteA2AContinuation(
|
|
256
|
+
continuation,
|
|
257
|
+
adapter,
|
|
258
|
+
formatRecoverableArtifactFallbackText(
|
|
259
|
+
continuation.verifiedArtifactCheckpoint,
|
|
260
|
+
),
|
|
261
|
+
progress,
|
|
262
|
+
);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
logA2AContinuationTransition(
|
|
266
|
+
"processor_exhausted_failure_notice",
|
|
267
|
+
continuation,
|
|
268
|
+
);
|
|
269
|
+
await notifyAndFailA2AContinuation(
|
|
270
|
+
continuation,
|
|
271
|
+
adapter,
|
|
272
|
+
options.reason,
|
|
273
|
+
progress,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
156
277
|
export async function processDueA2AContinuations(options: {
|
|
157
278
|
adapters: Map<string, PlatformAdapter>;
|
|
158
279
|
limit?: number;
|
|
159
280
|
}): Promise<void> {
|
|
160
281
|
const continuations = await claimDueA2AContinuations(options.limit ?? 5);
|
|
161
282
|
for (const continuation of continuations) {
|
|
162
|
-
await processClaimedContinuation(continuation, options).catch(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
283
|
+
await processClaimedContinuation(continuation, options).catch(
|
|
284
|
+
async (err) => {
|
|
285
|
+
console.error(
|
|
286
|
+
`[integrations] A2A continuation ${continuation.id} failed; durable recovery requested`,
|
|
287
|
+
);
|
|
288
|
+
try {
|
|
289
|
+
await recoverA2AContinuationAfterProcessorFailure(continuation.id, {
|
|
290
|
+
adapters: options.adapters,
|
|
291
|
+
reason:
|
|
292
|
+
err instanceof Error
|
|
293
|
+
? err.message.slice(0, 500)
|
|
294
|
+
: "continuation processing failed",
|
|
295
|
+
});
|
|
296
|
+
} catch (recoveryError) {
|
|
297
|
+
console.error(
|
|
298
|
+
`[integrations] A2A continuation ${continuation.id} recovery failed; later continuations will continue`,
|
|
299
|
+
recoveryError instanceof Error
|
|
300
|
+
? recoveryError.name
|
|
301
|
+
: "recovery_error",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
},
|
|
167
305
|
);
|
|
168
306
|
}
|
|
169
307
|
}
|
|
@@ -182,6 +320,7 @@ export async function recoverDueA2AContinuations(options?: {
|
|
|
182
320
|
const limit = options?.limit ?? 5;
|
|
183
321
|
const candidateTasks = await listRecoverableA2AIntegrationTasks(200);
|
|
184
322
|
const eligibleTaskIds: string[] = [];
|
|
323
|
+
const confirmedHistoryTaskIds: string[] = [];
|
|
185
324
|
for (const task of candidateTasks) {
|
|
186
325
|
const enabled = isIntegrationDurableDispatchEnabledForTask({
|
|
187
326
|
platform: task.platform,
|
|
@@ -192,12 +331,24 @@ export async function recoverDueA2AContinuations(options?: {
|
|
|
192
331
|
});
|
|
193
332
|
if (enabled) {
|
|
194
333
|
eligibleTaskIds.push(task.id);
|
|
195
|
-
|
|
334
|
+
} else if (task.hasPendingConfirmedDelivery) {
|
|
335
|
+
confirmedHistoryTaskIds.push(task.id);
|
|
196
336
|
} else {
|
|
197
337
|
await failDisabledDurableA2ATask(task);
|
|
198
338
|
}
|
|
339
|
+
if (eligibleTaskIds.length + confirmedHistoryTaskIds.length >= limit) break;
|
|
199
340
|
}
|
|
200
341
|
const ids = await recoverDueA2AContinuationIds(limit, eligibleTaskIds);
|
|
342
|
+
const remaining = Math.max(0, limit - ids.length);
|
|
343
|
+
const confirmedHistoryIds =
|
|
344
|
+
remaining > 0 && confirmedHistoryTaskIds.length > 0
|
|
345
|
+
? await recoverDueA2AContinuationIds(
|
|
346
|
+
remaining,
|
|
347
|
+
confirmedHistoryTaskIds,
|
|
348
|
+
true,
|
|
349
|
+
)
|
|
350
|
+
: [];
|
|
351
|
+
ids.push(...confirmedHistoryIds);
|
|
201
352
|
let dispatched = 0;
|
|
202
353
|
let failed = 0;
|
|
203
354
|
|
|
@@ -223,6 +374,13 @@ async function processClaimedContinuation(
|
|
|
223
374
|
continuation: A2AContinuation,
|
|
224
375
|
options: { adapters: Map<string, PlatformAdapter> },
|
|
225
376
|
): Promise<void> {
|
|
377
|
+
if (
|
|
378
|
+
continuation.terminalDeliveryConfirmedAt != null &&
|
|
379
|
+
continuation.terminalHistoryPayload
|
|
380
|
+
) {
|
|
381
|
+
await persistAndFinalizeConfirmedA2ADelivery(continuation);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
226
384
|
if (!(await durableContinuationScopeStillEnabled(continuation))) return;
|
|
227
385
|
const adapter = options.adapters.get(continuation.platform);
|
|
228
386
|
if (!adapter) {
|
|
@@ -244,7 +402,7 @@ async function processClaimedContinuation(
|
|
|
244
402
|
const recoverableArtifactSecrets =
|
|
245
403
|
await resolveContinuationArtifactSecrets(continuation);
|
|
246
404
|
let task: Task | null = null;
|
|
247
|
-
let latestRecoverableArtifactText
|
|
405
|
+
let latestRecoverableArtifactText = continuation.verifiedArtifactCheckpoint;
|
|
248
406
|
|
|
249
407
|
try {
|
|
250
408
|
while (Date.now() < deadline) {
|
|
@@ -255,7 +413,13 @@ async function processClaimedContinuation(
|
|
|
255
413
|
recoverableArtifactSecrets,
|
|
256
414
|
);
|
|
257
415
|
if (recoverableArtifactText) {
|
|
258
|
-
latestRecoverableArtifactText =
|
|
416
|
+
latestRecoverableArtifactText = await saveA2AVerifiedArtifactCheckpoint(
|
|
417
|
+
continuation.id,
|
|
418
|
+
recoverableArtifactText,
|
|
419
|
+
);
|
|
420
|
+
if (latestRecoverableArtifactText) {
|
|
421
|
+
logA2AContinuationTransition("checkpoint_persisted", continuation);
|
|
422
|
+
}
|
|
259
423
|
}
|
|
260
424
|
if (TERMINAL_STATES.has(task.status.state)) break;
|
|
261
425
|
await reportA2AContinuationProgress(continuation, progress, task);
|
|
@@ -287,6 +451,15 @@ async function processClaimedContinuation(
|
|
|
287
451
|
return;
|
|
288
452
|
}
|
|
289
453
|
if (continuation.attempts >= MAX_ATTEMPTS) {
|
|
454
|
+
if (latestRecoverableArtifactText) {
|
|
455
|
+
await deliverAndCompleteA2AContinuation(
|
|
456
|
+
continuation,
|
|
457
|
+
adapter,
|
|
458
|
+
formatRecoverableArtifactFallbackText(latestRecoverableArtifactText),
|
|
459
|
+
progress,
|
|
460
|
+
);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
290
463
|
await notifyAndFailA2AContinuation(
|
|
291
464
|
continuation,
|
|
292
465
|
adapter,
|
|
@@ -396,6 +569,19 @@ async function durableContinuationScopeStillEnabled(
|
|
|
396
569
|
});
|
|
397
570
|
if (enabled) return true;
|
|
398
571
|
|
|
572
|
+
if (
|
|
573
|
+
await hasPendingConfirmedA2ADeliveryForIntegrationTask(
|
|
574
|
+
continuation.integrationTaskId,
|
|
575
|
+
)
|
|
576
|
+
) {
|
|
577
|
+
await failA2AContinuation(
|
|
578
|
+
continuation.id,
|
|
579
|
+
"Durable integration campaign was disabled before this continuation delivered",
|
|
580
|
+
);
|
|
581
|
+
await reconcileTerminalA2AParentIfDisabled(continuation.integrationTaskId);
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
584
|
+
|
|
399
585
|
await failDisabledDurableA2ATask({
|
|
400
586
|
id: continuation.integrationTaskId,
|
|
401
587
|
platform: task?.platform ?? continuation.platform,
|
|
@@ -407,7 +593,10 @@ async function durableContinuationScopeStillEnabled(
|
|
|
407
593
|
}
|
|
408
594
|
|
|
409
595
|
async function failDisabledDurableA2ATask(
|
|
410
|
-
task:
|
|
596
|
+
task: Pick<
|
|
597
|
+
RecoverableA2AIntegrationTask,
|
|
598
|
+
"id" | "platform" | "externalThreadId" | "dispatchScope" | "status"
|
|
599
|
+
>,
|
|
411
600
|
): Promise<void> {
|
|
412
601
|
const message = "Durable integration campaign was disabled for this scope";
|
|
413
602
|
await failA2AContinuationsForIntegrationTask(task.id, message);
|
|
@@ -430,6 +619,49 @@ async function failDisabledDurableA2ATask(
|
|
|
430
619
|
}
|
|
431
620
|
}
|
|
432
621
|
|
|
622
|
+
export async function reconcileTerminalA2AParentIfDisabled(
|
|
623
|
+
integrationTaskId: string,
|
|
624
|
+
): Promise<boolean> {
|
|
625
|
+
if (
|
|
626
|
+
(await getA2AContinuationTaskOutcome(integrationTaskId)) !==
|
|
627
|
+
"terminal-without-delivery"
|
|
628
|
+
) {
|
|
629
|
+
return false;
|
|
630
|
+
}
|
|
631
|
+
if (
|
|
632
|
+
await hasOnlyLegacyFailedA2AContinuationsForIntegrationTask(
|
|
633
|
+
integrationTaskId,
|
|
634
|
+
)
|
|
635
|
+
) {
|
|
636
|
+
await failIntegrationCampaignTaskDeliveryContainment(
|
|
637
|
+
integrationTaskId,
|
|
638
|
+
"Legacy A2A continuation ended without durable delivery proof",
|
|
639
|
+
);
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
const task = await getPendingTask(integrationTaskId);
|
|
643
|
+
if (
|
|
644
|
+
!task ||
|
|
645
|
+
isIntegrationDurableDispatchEnabledForTask({
|
|
646
|
+
platform: task.platform,
|
|
647
|
+
externalThreadId: task.externalThreadId,
|
|
648
|
+
platformContext: task.dispatchScope
|
|
649
|
+
? { channelId: task.dispatchScope }
|
|
650
|
+
: undefined,
|
|
651
|
+
})
|
|
652
|
+
) {
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
await failDisabledDurableA2ATask({
|
|
656
|
+
id: task.id,
|
|
657
|
+
platform: task.platform,
|
|
658
|
+
externalThreadId: task.externalThreadId,
|
|
659
|
+
dispatchScope: task.dispatchScope,
|
|
660
|
+
status: task.status,
|
|
661
|
+
});
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
|
|
433
665
|
async function resumeA2AContinuationProgress(
|
|
434
666
|
continuation: A2AContinuation,
|
|
435
667
|
adapter: PlatformAdapter,
|
|
@@ -507,21 +739,29 @@ async function notifyAndFailA2AContinuation(
|
|
|
507
739
|
continuation.id,
|
|
508
740
|
);
|
|
509
741
|
if (!deliveryContinuation) return;
|
|
742
|
+
logA2AContinuationTransition(
|
|
743
|
+
"failure_delivery_claimed",
|
|
744
|
+
deliveryContinuation,
|
|
745
|
+
);
|
|
510
746
|
|
|
511
747
|
const message = formatContinuationFailureMessage(
|
|
512
748
|
deliveryContinuation,
|
|
513
749
|
reason,
|
|
514
750
|
);
|
|
751
|
+
let outgoing: OutgoingMessage;
|
|
752
|
+
let deliveryReceipt: PlatformDeliveryReceipt;
|
|
515
753
|
try {
|
|
516
|
-
|
|
517
|
-
await withTimeout(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
754
|
+
outgoing = adapter.formatAgentResponse(message);
|
|
755
|
+
deliveryReceipt = await withTimeout(
|
|
756
|
+
(signal) =>
|
|
757
|
+
deliverA2AContinuationResponse(
|
|
758
|
+
adapter,
|
|
759
|
+
deliveryContinuation,
|
|
760
|
+
outgoing,
|
|
761
|
+
progress,
|
|
762
|
+
"error",
|
|
763
|
+
signal,
|
|
764
|
+
),
|
|
525
765
|
PLATFORM_SEND_TIMEOUT_MS,
|
|
526
766
|
`${deliveryContinuation.platform} failure notification timed out`,
|
|
527
767
|
);
|
|
@@ -530,17 +770,20 @@ async function notifyAndFailA2AContinuation(
|
|
|
530
770
|
`[integrations] Failed to notify ${deliveryContinuation.platform} about failed A2A continuation ${deliveryContinuation.id}:`,
|
|
531
771
|
err,
|
|
532
772
|
);
|
|
533
|
-
if (deliveryContinuation.attempts >= MAX_ATTEMPTS) {
|
|
534
|
-
await failA2AContinuation(deliveryContinuation.id, reason);
|
|
535
|
-
await completeParentCampaignAfterTerminalA2A(deliveryContinuation);
|
|
536
|
-
return;
|
|
537
|
-
}
|
|
538
773
|
await rescheduleAndRedispatchA2AContinuation(deliveryContinuation.id);
|
|
539
774
|
return;
|
|
540
775
|
}
|
|
541
776
|
|
|
542
|
-
await
|
|
543
|
-
|
|
777
|
+
const confirmed = await recordTerminalA2ADelivery(
|
|
778
|
+
deliveryContinuation,
|
|
779
|
+
"failure",
|
|
780
|
+
outgoing,
|
|
781
|
+
deliveryReceipt,
|
|
782
|
+
[],
|
|
783
|
+
reason,
|
|
784
|
+
);
|
|
785
|
+
if (!confirmed) return;
|
|
786
|
+
await persistAndFinalizeConfirmedA2ADelivery(confirmed);
|
|
544
787
|
}
|
|
545
788
|
|
|
546
789
|
async function deliverAndCompleteA2AContinuation(
|
|
@@ -554,59 +797,49 @@ async function deliverAndCompleteA2AContinuation(
|
|
|
554
797
|
continuation.id,
|
|
555
798
|
);
|
|
556
799
|
if (!deliveryContinuation) return;
|
|
800
|
+
logA2AContinuationTransition(
|
|
801
|
+
"response_delivery_claimed",
|
|
802
|
+
deliveryContinuation,
|
|
803
|
+
);
|
|
557
804
|
|
|
805
|
+
let outgoing: OutgoingMessage;
|
|
806
|
+
let deliveryReceipt: PlatformDeliveryReceipt;
|
|
807
|
+
const artifactSecrets =
|
|
808
|
+
await resolveContinuationArtifactSecrets(deliveryContinuation);
|
|
809
|
+
const artifacts = extractA2AArtifactIdentities(
|
|
810
|
+
[{ tool: "call-agent", result: text }],
|
|
811
|
+
{ persistedArtifactSecrets: artifactSecrets },
|
|
812
|
+
);
|
|
558
813
|
try {
|
|
559
|
-
|
|
814
|
+
outgoing = adapter.formatAgentResponse(
|
|
560
815
|
stripA2APersistedArtifactMarkers(text),
|
|
561
816
|
);
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
817
|
+
deliveryReceipt = await withTimeout(
|
|
818
|
+
(signal) =>
|
|
819
|
+
deliverA2AContinuationResponse(
|
|
820
|
+
adapter,
|
|
821
|
+
deliveryContinuation,
|
|
822
|
+
outgoing,
|
|
823
|
+
progress,
|
|
824
|
+
"done",
|
|
825
|
+
signal,
|
|
826
|
+
),
|
|
570
827
|
PLATFORM_SEND_TIMEOUT_MS,
|
|
571
828
|
`${deliveryContinuation.platform} response delivery timed out`,
|
|
572
829
|
);
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
artifactSecrets,
|
|
584
|
-
);
|
|
585
|
-
persistenceError = undefined;
|
|
586
|
-
break;
|
|
587
|
-
} catch (err) {
|
|
588
|
-
persistenceError = err;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
if (persistenceError) {
|
|
592
|
-
console.error(
|
|
593
|
-
`[integrations] Delivered A2A continuation ${deliveryContinuation.id} but could not persist its thread history:`,
|
|
594
|
-
persistenceError,
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
} catch (err) {
|
|
598
|
-
if (deliveryContinuation.attempts >= MAX_ATTEMPTS) {
|
|
599
|
-
await failA2AContinuation(
|
|
600
|
-
deliveryContinuation.id,
|
|
601
|
-
err instanceof Error ? err.message : String(err),
|
|
602
|
-
);
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
830
|
+
const confirmed = await recordTerminalA2ADelivery(
|
|
831
|
+
deliveryContinuation,
|
|
832
|
+
"success",
|
|
833
|
+
outgoing,
|
|
834
|
+
deliveryReceipt,
|
|
835
|
+
artifacts,
|
|
836
|
+
);
|
|
837
|
+
if (!confirmed) return;
|
|
838
|
+
await persistAndFinalizeConfirmedA2ADelivery(confirmed);
|
|
839
|
+
} catch {
|
|
605
840
|
await rescheduleAndRedispatchA2AContinuation(deliveryContinuation.id);
|
|
606
841
|
return;
|
|
607
842
|
}
|
|
608
|
-
|
|
609
|
-
await completeAfterSuccessfulDelivery(deliveryContinuation);
|
|
610
843
|
}
|
|
611
844
|
|
|
612
845
|
async function deliverA2AContinuationResponse(
|
|
@@ -615,18 +848,24 @@ async function deliverA2AContinuationResponse(
|
|
|
615
848
|
message: OutgoingMessage,
|
|
616
849
|
progress: PlatformRunProgress | null,
|
|
617
850
|
status: "done" | "error",
|
|
851
|
+
signal: AbortSignal,
|
|
618
852
|
): Promise<PlatformDeliveryReceipt> {
|
|
619
853
|
if (progress) {
|
|
620
854
|
try {
|
|
621
|
-
await progress.onEvent(
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
855
|
+
await progress.onEvent(
|
|
856
|
+
{
|
|
857
|
+
type: "agent_call",
|
|
858
|
+
agent: continuation.agentName,
|
|
859
|
+
status,
|
|
860
|
+
},
|
|
861
|
+
{ signal },
|
|
862
|
+
);
|
|
863
|
+
throwIfAborted(signal);
|
|
864
|
+
const receipt = await progress.complete(message, { signal });
|
|
627
865
|
if (receipt?.status === "delivered") return receipt;
|
|
628
866
|
throw new Error("Continuation progress completed without delivery proof");
|
|
629
867
|
} catch {
|
|
868
|
+
throwIfAborted(signal);
|
|
630
869
|
// A resumed Slack stream can no longer be finalized (for example when
|
|
631
870
|
// chat.stopStream rejects). Preserve the final answer with the same
|
|
632
871
|
// thread reply fallback used by the initial webhook run. Also ask the
|
|
@@ -635,14 +874,22 @@ async function deliverA2AContinuationResponse(
|
|
|
635
874
|
try {
|
|
636
875
|
await progress.fail?.(
|
|
637
876
|
"I couldn't update the live response, but I posted the final result in this thread.",
|
|
877
|
+
{ signal },
|
|
638
878
|
);
|
|
639
879
|
} catch {
|
|
640
880
|
// The thread reply below is still the authoritative final answer.
|
|
641
881
|
}
|
|
882
|
+
logA2AContinuationTransition("native_progress_fallback", continuation);
|
|
642
883
|
}
|
|
643
884
|
}
|
|
885
|
+
throwIfAborted(signal);
|
|
644
886
|
const receipt = await adapter.sendResponse(message, continuation.incoming, {
|
|
645
|
-
|
|
887
|
+
idempotencyKey: `a2a-continuation:${continuation.id}`,
|
|
888
|
+
reconcileAfter: continuation.createdAt,
|
|
889
|
+
signal,
|
|
890
|
+
placeholderRef:
|
|
891
|
+
progress?.responseTargetRef ?? continuation.placeholderRef ?? undefined,
|
|
892
|
+
strictTargetRef: true,
|
|
646
893
|
});
|
|
647
894
|
if (receipt?.status !== "delivered") {
|
|
648
895
|
throw new Error("Continuation response completed without delivery proof");
|
|
@@ -652,18 +899,19 @@ async function deliverA2AContinuationResponse(
|
|
|
652
899
|
|
|
653
900
|
async function persistA2AContinuationDelivery(
|
|
654
901
|
continuation: A2AContinuation,
|
|
655
|
-
|
|
656
|
-
receipt: PlatformDeliveryReceipt,
|
|
657
|
-
artifactText: string,
|
|
658
|
-
artifactSecrets: readonly string[],
|
|
902
|
+
history: A2ATerminalHistoryPayload,
|
|
659
903
|
): Promise<void> {
|
|
660
904
|
const mapping = await getThreadMapping(
|
|
661
905
|
continuation.platform,
|
|
662
906
|
continuation.externalThreadId,
|
|
663
907
|
);
|
|
664
|
-
if (!mapping)
|
|
908
|
+
if (!mapping) {
|
|
909
|
+
throw new Error("Integration thread mapping is not available");
|
|
910
|
+
}
|
|
665
911
|
const thread = await getThread(mapping.internalThreadId);
|
|
666
|
-
if (!thread)
|
|
912
|
+
if (!thread) {
|
|
913
|
+
throw new Error("Integration chat thread is not available");
|
|
914
|
+
}
|
|
667
915
|
|
|
668
916
|
let repo: any;
|
|
669
917
|
try {
|
|
@@ -673,33 +921,32 @@ async function persistA2AContinuationDelivery(
|
|
|
673
921
|
}
|
|
674
922
|
if (!Array.isArray(repo.messages)) repo.messages = [];
|
|
675
923
|
|
|
676
|
-
const artifacts = extractA2AArtifactIdentities(
|
|
677
|
-
[{ tool: "call-agent", result: artifactText }],
|
|
678
|
-
{
|
|
679
|
-
persistedArtifactSecrets: artifactSecrets,
|
|
680
|
-
},
|
|
681
|
-
);
|
|
682
924
|
const metadata: Record<string, unknown> = {
|
|
683
925
|
integrationDeliveryAttempted: true,
|
|
684
926
|
integrationDelivery: {
|
|
685
927
|
platform: continuation.platform,
|
|
686
928
|
status: "delivered",
|
|
687
|
-
text:
|
|
688
|
-
deliveredAt:
|
|
689
|
-
...(
|
|
690
|
-
? { messageRefs:
|
|
929
|
+
text: history.text,
|
|
930
|
+
deliveredAt: history.deliveredAt,
|
|
931
|
+
...(history.messageRefs.length
|
|
932
|
+
? { messageRefs: history.messageRefs }
|
|
691
933
|
: {}),
|
|
692
934
|
},
|
|
693
935
|
};
|
|
694
|
-
if (artifacts.length > 0)
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
936
|
+
if (history.artifacts.length > 0) {
|
|
937
|
+
metadata.integrationArtifacts = history.artifacts;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
const messageId = `msg-${continuation.id}-assistant-continuation`;
|
|
941
|
+
if (!repo.messages.some((message: any) => message?.id === messageId)) {
|
|
942
|
+
repo.messages.push({
|
|
943
|
+
id: messageId,
|
|
944
|
+
role: "assistant",
|
|
945
|
+
content: [{ type: "text", text: history.text }],
|
|
946
|
+
createdAt: history.deliveredAt,
|
|
947
|
+
metadata,
|
|
948
|
+
});
|
|
949
|
+
}
|
|
703
950
|
const meta = extractThreadMeta(repo);
|
|
704
951
|
await updateThreadData(
|
|
705
952
|
mapping.internalThreadId,
|
|
@@ -722,25 +969,110 @@ async function rescheduleAndRedispatchA2AContinuation(
|
|
|
722
969
|
});
|
|
723
970
|
}
|
|
724
971
|
|
|
725
|
-
async function
|
|
972
|
+
async function recordTerminalA2ADelivery(
|
|
973
|
+
continuation: A2AContinuation,
|
|
974
|
+
kind: A2ATerminalDeliveryKind,
|
|
975
|
+
outgoing: OutgoingMessage,
|
|
976
|
+
receipt: PlatformDeliveryReceipt,
|
|
977
|
+
artifacts: A2AArtifactIdentity[],
|
|
978
|
+
errorMessage?: string,
|
|
979
|
+
): Promise<A2AContinuation | null> {
|
|
980
|
+
const historyPayload: A2ATerminalHistoryPayload = {
|
|
981
|
+
text: outgoing.text,
|
|
982
|
+
deliveredAt: new Date().toISOString(),
|
|
983
|
+
messageRefs: receipt.messageRefs ?? [],
|
|
984
|
+
artifacts,
|
|
985
|
+
};
|
|
986
|
+
for (let attempt = 0; attempt < COMPLETE_AFTER_DELIVERY_ATTEMPTS; attempt++) {
|
|
987
|
+
try {
|
|
988
|
+
const confirmed = await recordA2ATerminalDeliveryReceipt(
|
|
989
|
+
continuation.id,
|
|
990
|
+
kind,
|
|
991
|
+
historyPayload,
|
|
992
|
+
errorMessage,
|
|
993
|
+
);
|
|
994
|
+
logA2AContinuationTransition(
|
|
995
|
+
kind === "success"
|
|
996
|
+
? "response_delivery_confirmed"
|
|
997
|
+
: "failure_delivery_confirmed",
|
|
998
|
+
continuation,
|
|
999
|
+
);
|
|
1000
|
+
return confirmed;
|
|
1001
|
+
} catch {}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
console.error(
|
|
1005
|
+
`[integrations] ${continuation.platform} accepted terminal A2A delivery for ${continuation.id}, ` +
|
|
1006
|
+
`but recording its receipt failed after ${COMPLETE_AFTER_DELIVERY_ATTEMPTS} attempts. Leaving it in delivering for stale recovery.`,
|
|
1007
|
+
);
|
|
1008
|
+
try {
|
|
1009
|
+
await retainA2AUnconfirmedDeliveryClaim(continuation.id);
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
console.error(
|
|
1012
|
+
`[integrations] Failed to retain unconfirmed A2A delivery claim ${continuation.id}:`,
|
|
1013
|
+
err instanceof Error ? err.name : "receipt_recovery_error",
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
async function persistAndFinalizeConfirmedA2ADelivery(
|
|
726
1020
|
continuation: A2AContinuation,
|
|
727
1021
|
): Promise<void> {
|
|
1022
|
+
const history = continuation.terminalHistoryPayload;
|
|
1023
|
+
if (!history || continuation.terminalDeliveryConfirmedAt == null) {
|
|
1024
|
+
throw new Error("Confirmed A2A delivery is missing durable history");
|
|
1025
|
+
}
|
|
728
1026
|
let lastError: unknown;
|
|
729
|
-
|
|
1027
|
+
let historyFinalized = false;
|
|
1028
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
730
1029
|
try {
|
|
731
|
-
|
|
732
|
-
|
|
1030
|
+
if (!historyFinalized) {
|
|
1031
|
+
await persistA2AContinuationDelivery(continuation, history);
|
|
1032
|
+
await finalizeA2ATerminalHistory(continuation.id);
|
|
1033
|
+
historyFinalized = true;
|
|
1034
|
+
logA2AContinuationTransition(
|
|
1035
|
+
"terminal_history_persisted",
|
|
1036
|
+
continuation,
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
await completeParentCampaignAfterTerminalA2A({
|
|
1040
|
+
...continuation,
|
|
1041
|
+
status:
|
|
1042
|
+
continuation.terminalDeliveryKind === "success"
|
|
1043
|
+
? "completed"
|
|
1044
|
+
: "failed",
|
|
1045
|
+
});
|
|
733
1046
|
return;
|
|
734
1047
|
} catch (err) {
|
|
735
1048
|
lastError = err;
|
|
736
1049
|
}
|
|
737
1050
|
}
|
|
738
|
-
|
|
739
1051
|
console.error(
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1052
|
+
historyFinalized
|
|
1053
|
+
? `[integrations] A2A continuation ${continuation.id} finalized history but parent completion remains retryable:`
|
|
1054
|
+
: `[integrations] A2A continuation ${continuation.id} has a provider receipt but its history remains retryable:`,
|
|
1055
|
+
lastError instanceof Error ? lastError.name : "persistence_error",
|
|
743
1056
|
);
|
|
1057
|
+
if (historyFinalized) {
|
|
1058
|
+
await dispatchPendingIntegrationTask({
|
|
1059
|
+
taskId: continuation.integrationTaskId,
|
|
1060
|
+
task: {
|
|
1061
|
+
platform: continuation.platform,
|
|
1062
|
+
externalThreadId: continuation.externalThreadId,
|
|
1063
|
+
platformContext: continuation.incoming.platformContext,
|
|
1064
|
+
},
|
|
1065
|
+
campaignContinuation: true,
|
|
1066
|
+
allowPortableConfirmedReceiptReconciliation: true,
|
|
1067
|
+
}).catch((err) => {
|
|
1068
|
+
console.error(
|
|
1069
|
+
`[integrations] Failed to wake A2A parent ${continuation.integrationTaskId} after terminal history finalization:`,
|
|
1070
|
+
err,
|
|
1071
|
+
);
|
|
1072
|
+
});
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
await rescheduleAndRedispatchA2AContinuation(continuation.id);
|
|
744
1076
|
}
|
|
745
1077
|
|
|
746
1078
|
async function completeParentCampaignAfterTerminalA2A(
|
|
@@ -750,17 +1082,31 @@ async function completeParentCampaignAfterTerminalA2A(
|
|
|
750
1082
|
continuation.integrationTaskId,
|
|
751
1083
|
);
|
|
752
1084
|
if (!campaign) return;
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
1085
|
+
const outcome = await getA2AContinuationTaskOutcome(
|
|
1086
|
+
continuation.integrationTaskId,
|
|
1087
|
+
);
|
|
1088
|
+
if (outcome !== "terminal-delivered") {
|
|
1089
|
+
if (outcome === "terminal-without-delivery") {
|
|
1090
|
+
if (
|
|
1091
|
+
await reconcileTerminalA2AParentIfDisabled(
|
|
1092
|
+
continuation.integrationTaskId,
|
|
1093
|
+
)
|
|
1094
|
+
) {
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
if (outcome !== "active") {
|
|
1099
|
+
console.warn(
|
|
1100
|
+
`[integrations] Refusing to complete A2A parent ${continuation.integrationTaskId} with outcome ${outcome}`,
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
758
1103
|
return;
|
|
759
1104
|
}
|
|
760
1105
|
const completed = await completeIntegrationCampaignTaskAfterA2A(
|
|
761
1106
|
continuation.integrationTaskId,
|
|
762
1107
|
);
|
|
763
1108
|
if (!completed) return;
|
|
1109
|
+
logA2AContinuationTransition("parent_completed", continuation);
|
|
764
1110
|
|
|
765
1111
|
const nextTask = await getNextPendingTaskForThread(
|
|
766
1112
|
continuation.platform,
|
|
@@ -861,23 +1207,36 @@ function sleep(ms: number): Promise<void> {
|
|
|
861
1207
|
}
|
|
862
1208
|
|
|
863
1209
|
async function withTimeout<T>(
|
|
864
|
-
|
|
1210
|
+
operation: (signal: AbortSignal) => Promise<T>,
|
|
865
1211
|
timeoutMs: number,
|
|
866
1212
|
message: string,
|
|
867
1213
|
): Promise<T> {
|
|
1214
|
+
const controller = new AbortController();
|
|
868
1215
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1216
|
+
let timedOut = false;
|
|
869
1217
|
try {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
new
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1218
|
+
timer = setTimeout(() => {
|
|
1219
|
+
timedOut = true;
|
|
1220
|
+
controller.abort(new Error(message));
|
|
1221
|
+
}, timeoutMs);
|
|
1222
|
+
const result = await operation(controller.signal);
|
|
1223
|
+
if (timedOut) throw new Error(message);
|
|
1224
|
+
return result;
|
|
1225
|
+
} catch (error) {
|
|
1226
|
+
if (timedOut) throw new Error(message);
|
|
1227
|
+
throw error;
|
|
876
1228
|
} finally {
|
|
877
1229
|
if (timer) clearTimeout(timer);
|
|
878
1230
|
}
|
|
879
1231
|
}
|
|
880
1232
|
|
|
1233
|
+
function throwIfAborted(signal: AbortSignal): void {
|
|
1234
|
+
if (!signal.aborted) return;
|
|
1235
|
+
throw signal.reason instanceof Error
|
|
1236
|
+
? signal.reason
|
|
1237
|
+
: new Error("Platform delivery was aborted");
|
|
1238
|
+
}
|
|
1239
|
+
|
|
881
1240
|
function sanitizeFailureReason(reason: string): string {
|
|
882
1241
|
const oneLine = reason.replace(/\s+/g, " ").trim();
|
|
883
1242
|
const withoutEnvNames = oneLine.replace(
|