@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,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import type { H3Event } from "h3";
|
|
2
4
|
import { createError, getHeader, readRawBody } from "h3";
|
|
3
5
|
|
|
@@ -24,6 +26,7 @@ import type {
|
|
|
24
26
|
PlatformRunProgress,
|
|
25
27
|
PlatformRunProgressRef,
|
|
26
28
|
PlatformDeliveryReceipt,
|
|
29
|
+
PlatformDeliveryOptions,
|
|
27
30
|
IntegrationContextMessage,
|
|
28
31
|
IntegrationFileReference,
|
|
29
32
|
} from "../types.js";
|
|
@@ -47,6 +50,9 @@ const SLACK_IDENTITY_NEGATIVE_CACHE_TTL_MS = 30 * 1_000;
|
|
|
47
50
|
const SLACK_IDENTITY_CACHE_MAX_ENTRIES = 1_000;
|
|
48
51
|
const SLACK_TOKEN_IDENTITY_CACHE_TTL_MS = 10 * 60 * 1_000;
|
|
49
52
|
const SLACK_TOKEN_IDENTITY_NEGATIVE_CACHE_TTL_MS = 30 * 1_000;
|
|
53
|
+
const SLACK_DELIVERY_RECONCILIATION_PAGE_LIMIT = 200;
|
|
54
|
+
const SLACK_DELIVERY_RECONCILIATION_MAX_PAGES = 3;
|
|
55
|
+
const SLACK_DELIVERY_MARKER_PREFIX = "agent_native_terminal_";
|
|
50
56
|
|
|
51
57
|
type SlackTokenIdentity = {
|
|
52
58
|
teamId: string | null;
|
|
@@ -441,7 +447,7 @@ export function slackAdapter(
|
|
|
441
447
|
async sendResponse(
|
|
442
448
|
message: OutgoingMessage,
|
|
443
449
|
context: IncomingMessage,
|
|
444
|
-
opts?:
|
|
450
|
+
opts?: PlatformDeliveryOptions,
|
|
445
451
|
): Promise<void | PlatformDeliveryReceipt> {
|
|
446
452
|
const token = await resolveBotToken(context);
|
|
447
453
|
if (!token) {
|
|
@@ -471,6 +477,22 @@ export function slackAdapter(
|
|
|
471
477
|
}
|
|
472
478
|
const restChunks = chunks.slice(1);
|
|
473
479
|
const messageRefs: string[] = [];
|
|
480
|
+
const freshChunkIndexes = [
|
|
481
|
+
...(!placeholderRef ? [0] : []),
|
|
482
|
+
...restChunks.map((_, index) => index + 1),
|
|
483
|
+
];
|
|
484
|
+
const reconciledRefs =
|
|
485
|
+
opts?.idempotencyKey && freshChunkIndexes.length > 0
|
|
486
|
+
? await reconcileSlackDeliveryChunks(
|
|
487
|
+
token,
|
|
488
|
+
channelId,
|
|
489
|
+
threadTs,
|
|
490
|
+
opts.idempotencyKey,
|
|
491
|
+
freshChunkIndexes,
|
|
492
|
+
opts.reconcileAfter,
|
|
493
|
+
opts.signal,
|
|
494
|
+
)
|
|
495
|
+
: new Map<number, string>();
|
|
474
496
|
|
|
475
497
|
const finalBlocks =
|
|
476
498
|
blocks ??
|
|
@@ -491,40 +513,57 @@ export function slackAdapter(
|
|
|
491
513
|
try {
|
|
492
514
|
if (placeholderRef) {
|
|
493
515
|
// Replace the "thinking…" placeholder in place.
|
|
494
|
-
const
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
516
|
+
const data = (await slackApiJson(
|
|
517
|
+
"https://slack.com/api/chat.update",
|
|
518
|
+
{
|
|
519
|
+
method: "POST",
|
|
520
|
+
headers: {
|
|
521
|
+
Authorization: `Bearer ${token}`,
|
|
522
|
+
"Content-Type": "application/json",
|
|
523
|
+
},
|
|
524
|
+
body: JSON.stringify({ ...baseBody, ts: placeholderRef }),
|
|
525
|
+
signal: opts?.signal,
|
|
499
526
|
},
|
|
500
|
-
|
|
501
|
-
});
|
|
502
|
-
const data = (await res.json()) as {
|
|
527
|
+
)) as {
|
|
503
528
|
ok: boolean;
|
|
504
529
|
error?: string;
|
|
505
530
|
ts?: string;
|
|
506
531
|
};
|
|
507
532
|
if (!data.ok) {
|
|
508
533
|
console.error("[slack] chat.update error:", data.error);
|
|
534
|
+
if (opts?.strictTargetRef) {
|
|
535
|
+
throw new Error(data.error || "chat.update failed");
|
|
536
|
+
}
|
|
509
537
|
// Fall back to a fresh post so the user still sees a reply
|
|
510
538
|
const postedTs = await postFresh(
|
|
511
539
|
token,
|
|
512
540
|
channelId,
|
|
513
541
|
threadTs,
|
|
514
|
-
|
|
542
|
+
opts?.idempotencyKey
|
|
543
|
+
? withSlackDeliveryMarker(baseBody, opts.idempotencyKey, 0)
|
|
544
|
+
: baseBody,
|
|
545
|
+
opts?.signal,
|
|
515
546
|
);
|
|
516
547
|
if (postedTs) messageRefs.push(postedTs);
|
|
517
548
|
} else {
|
|
518
549
|
messageRefs.push(data.ts || placeholderRef);
|
|
519
550
|
}
|
|
520
551
|
} else {
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
552
|
+
const reconciledRef = reconciledRefs.get(0);
|
|
553
|
+
if (reconciledRef) {
|
|
554
|
+
messageRefs.push(reconciledRef);
|
|
555
|
+
} else {
|
|
556
|
+
const postedTs = await postFresh(
|
|
557
|
+
token,
|
|
558
|
+
channelId,
|
|
559
|
+
threadTs,
|
|
560
|
+
opts?.idempotencyKey
|
|
561
|
+
? withSlackDeliveryMarker(baseBody, opts.idempotencyKey, 0)
|
|
562
|
+
: baseBody,
|
|
563
|
+
opts?.signal,
|
|
564
|
+
);
|
|
565
|
+
if (postedTs) messageRefs.push(postedTs);
|
|
566
|
+
}
|
|
528
567
|
}
|
|
529
568
|
|
|
530
569
|
// Clear the AI-assistant "is thinking…" status now that we've
|
|
@@ -534,14 +573,33 @@ export function slackAdapter(
|
|
|
534
573
|
}
|
|
535
574
|
|
|
536
575
|
// Overflow chunks (rare) — post as plain follow-ups in the same thread
|
|
537
|
-
for (const chunk of restChunks) {
|
|
538
|
-
const
|
|
576
|
+
for (const [index, chunk] of restChunks.entries()) {
|
|
577
|
+
const chunkIndex = index + 1;
|
|
578
|
+
const reconciledRef = reconciledRefs.get(chunkIndex);
|
|
579
|
+
if (reconciledRef) {
|
|
580
|
+
messageRefs.push(reconciledRef);
|
|
581
|
+
continue;
|
|
582
|
+
}
|
|
583
|
+
const overflowBody: Record<string, unknown> = {
|
|
539
584
|
channel: channelId,
|
|
540
585
|
text: chunk,
|
|
541
586
|
unfurl_links: false,
|
|
542
587
|
unfurl_media: false,
|
|
543
588
|
mrkdwn: true,
|
|
544
|
-
}
|
|
589
|
+
};
|
|
590
|
+
const postedTs = await postFresh(
|
|
591
|
+
token,
|
|
592
|
+
channelId,
|
|
593
|
+
threadTs,
|
|
594
|
+
opts?.idempotencyKey
|
|
595
|
+
? withSlackDeliveryMarker(
|
|
596
|
+
overflowBody,
|
|
597
|
+
opts.idempotencyKey,
|
|
598
|
+
chunkIndex,
|
|
599
|
+
)
|
|
600
|
+
: overflowBody,
|
|
601
|
+
opts?.signal,
|
|
602
|
+
);
|
|
545
603
|
if (postedTs) messageRefs.push(postedTs);
|
|
546
604
|
}
|
|
547
605
|
return {
|
|
@@ -628,7 +686,7 @@ export function slackAdapter(
|
|
|
628
686
|
if (target.threadRef) body.thread_ts = target.threadRef;
|
|
629
687
|
|
|
630
688
|
try {
|
|
631
|
-
const
|
|
689
|
+
const data = (await slackApiJson(
|
|
632
690
|
"https://slack.com/api/chat.postMessage",
|
|
633
691
|
{
|
|
634
692
|
method: "POST",
|
|
@@ -638,8 +696,7 @@ export function slackAdapter(
|
|
|
638
696
|
},
|
|
639
697
|
body: JSON.stringify(body),
|
|
640
698
|
},
|
|
641
|
-
);
|
|
642
|
-
const data = (await res.json()) as { ok: boolean; error?: string };
|
|
699
|
+
)) as { ok: boolean; error?: string };
|
|
643
700
|
if (!data.ok) {
|
|
644
701
|
throw new Error(data.error || "chat.postMessage failed");
|
|
645
702
|
}
|
|
@@ -1037,7 +1094,7 @@ function setSlackAssistantStatus(
|
|
|
1037
1094
|
threadTs: string,
|
|
1038
1095
|
status: string,
|
|
1039
1096
|
): void {
|
|
1040
|
-
|
|
1097
|
+
slackApiJson("https://slack.com/api/assistant.threads.setStatus", {
|
|
1041
1098
|
method: "POST",
|
|
1042
1099
|
headers: {
|
|
1043
1100
|
Authorization: `Bearer ${token}`,
|
|
@@ -1094,6 +1151,7 @@ async function postFresh(
|
|
|
1094
1151
|
channelId: string,
|
|
1095
1152
|
threadTs: string | undefined,
|
|
1096
1153
|
body: Record<string, unknown>,
|
|
1154
|
+
signal?: AbortSignal,
|
|
1097
1155
|
): Promise<string | undefined> {
|
|
1098
1156
|
const hasBlocks =
|
|
1099
1157
|
Array.isArray(body.blocks) && (body.blocks as unknown[]).length > 0;
|
|
@@ -1110,15 +1168,15 @@ async function postFresh(
|
|
|
1110
1168
|
channel: channelId,
|
|
1111
1169
|
};
|
|
1112
1170
|
if (threadTs && !payload.thread_ts) payload.thread_ts = threadTs;
|
|
1113
|
-
const
|
|
1171
|
+
const data = (await slackApiJson("https://slack.com/api/chat.postMessage", {
|
|
1114
1172
|
method: "POST",
|
|
1115
1173
|
headers: {
|
|
1116
1174
|
Authorization: `Bearer ${token}`,
|
|
1117
1175
|
"Content-Type": "application/json",
|
|
1118
1176
|
},
|
|
1119
1177
|
body: JSON.stringify(payload),
|
|
1120
|
-
|
|
1121
|
-
|
|
1178
|
+
signal,
|
|
1179
|
+
})) as {
|
|
1122
1180
|
ok: boolean;
|
|
1123
1181
|
error?: string;
|
|
1124
1182
|
ts?: string;
|
|
@@ -1130,23 +1188,145 @@ async function postFresh(
|
|
|
1130
1188
|
return data.ts;
|
|
1131
1189
|
}
|
|
1132
1190
|
|
|
1133
|
-
|
|
1191
|
+
function slackDeliveryMarker(key: string, chunkIndex: number): string {
|
|
1192
|
+
const digest = createHash("sha256")
|
|
1193
|
+
.update(`${key}:${chunkIndex}`)
|
|
1194
|
+
.digest("hex")
|
|
1195
|
+
.slice(0, 32);
|
|
1196
|
+
return `${SLACK_DELIVERY_MARKER_PREFIX}${digest}`;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function withSlackDeliveryMarker(
|
|
1200
|
+
body: Record<string, unknown>,
|
|
1201
|
+
key: string,
|
|
1202
|
+
chunkIndex: number,
|
|
1203
|
+
): Record<string, unknown> {
|
|
1204
|
+
const marker = slackDeliveryMarker(key, chunkIndex);
|
|
1205
|
+
const blocks = Array.isArray(body.blocks) ? body.blocks : [];
|
|
1206
|
+
if (blocks.length > 0) {
|
|
1207
|
+
const [first, ...rest] = blocks;
|
|
1208
|
+
if (first && typeof first === "object" && !Array.isArray(first)) {
|
|
1209
|
+
return {
|
|
1210
|
+
...body,
|
|
1211
|
+
blocks: [
|
|
1212
|
+
{ ...(first as Record<string, unknown>), block_id: marker },
|
|
1213
|
+
...rest,
|
|
1214
|
+
],
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
return {
|
|
1219
|
+
...body,
|
|
1220
|
+
blocks: [
|
|
1221
|
+
{
|
|
1222
|
+
type: "section",
|
|
1223
|
+
block_id: marker,
|
|
1224
|
+
text: { type: "mrkdwn", text: String(body.text ?? "") },
|
|
1225
|
+
},
|
|
1226
|
+
],
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
async function reconcileSlackDeliveryChunks(
|
|
1231
|
+
token: string,
|
|
1232
|
+
channelId: string,
|
|
1233
|
+
threadTs: string | undefined,
|
|
1234
|
+
key: string,
|
|
1235
|
+
chunkIndexes: number[],
|
|
1236
|
+
reconcileAfter?: number,
|
|
1237
|
+
signal?: AbortSignal,
|
|
1238
|
+
): Promise<Map<number, string>> {
|
|
1239
|
+
if (!threadTs) {
|
|
1240
|
+
throw new Error(
|
|
1241
|
+
"Cannot reconcile an idempotent Slack delivery without a thread timestamp",
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
const refs = new Map<number, string>();
|
|
1245
|
+
const expectedMarkers = new Map(
|
|
1246
|
+
chunkIndexes.map((chunkIndex) => [
|
|
1247
|
+
slackDeliveryMarker(key, chunkIndex),
|
|
1248
|
+
chunkIndex,
|
|
1249
|
+
]),
|
|
1250
|
+
);
|
|
1251
|
+
let cursor = "";
|
|
1252
|
+
for (
|
|
1253
|
+
let page = 0;
|
|
1254
|
+
page < SLACK_DELIVERY_RECONCILIATION_MAX_PAGES;
|
|
1255
|
+
page += 1
|
|
1256
|
+
) {
|
|
1257
|
+
const url = new URL("https://slack.com/api/conversations.replies");
|
|
1258
|
+
url.searchParams.set("channel", channelId);
|
|
1259
|
+
url.searchParams.set("ts", threadTs);
|
|
1260
|
+
url.searchParams.set(
|
|
1261
|
+
"limit",
|
|
1262
|
+
String(SLACK_DELIVERY_RECONCILIATION_PAGE_LIMIT),
|
|
1263
|
+
);
|
|
1264
|
+
if (cursor) url.searchParams.set("cursor", cursor);
|
|
1265
|
+
if (typeof reconcileAfter === "number" && reconcileAfter > 0) {
|
|
1266
|
+
url.searchParams.set("oldest", String(Math.floor(reconcileAfter / 1000)));
|
|
1267
|
+
url.searchParams.set("inclusive", "true");
|
|
1268
|
+
}
|
|
1269
|
+
const body = (await slackApiJson(url.toString(), {
|
|
1270
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1271
|
+
signal,
|
|
1272
|
+
})) as {
|
|
1273
|
+
ok: boolean;
|
|
1274
|
+
error?: string;
|
|
1275
|
+
messages?: Array<{
|
|
1276
|
+
ts?: string;
|
|
1277
|
+
blocks?: Array<{ block_id?: string }>;
|
|
1278
|
+
}>;
|
|
1279
|
+
response_metadata?: { next_cursor?: string };
|
|
1280
|
+
};
|
|
1281
|
+
if (!body.ok) {
|
|
1282
|
+
throw new Error(body.error || "conversations.replies failed");
|
|
1283
|
+
}
|
|
1284
|
+
for (const message of body.messages ?? []) {
|
|
1285
|
+
if (typeof message.ts !== "string") continue;
|
|
1286
|
+
for (const block of message.blocks ?? []) {
|
|
1287
|
+
if (typeof block.block_id !== "string") continue;
|
|
1288
|
+
const chunkIndex = expectedMarkers.get(block.block_id);
|
|
1289
|
+
if (chunkIndex !== undefined) refs.set(chunkIndex, message.ts);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (refs.size === expectedMarkers.size) return refs;
|
|
1293
|
+
cursor = body.response_metadata?.next_cursor?.trim() ?? "";
|
|
1294
|
+
if (!cursor) return refs;
|
|
1295
|
+
}
|
|
1296
|
+
throw new Error(
|
|
1297
|
+
"Slack delivery reconciliation exceeded the bounded thread scan",
|
|
1298
|
+
);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
async function slackApiJson(
|
|
1134
1302
|
url: string,
|
|
1135
1303
|
init: RequestInit,
|
|
1136
1304
|
timeoutMs = SLACK_API_TIMEOUT_MS,
|
|
1137
|
-
): Promise<
|
|
1305
|
+
): Promise<Record<string, any>> {
|
|
1138
1306
|
const controller =
|
|
1139
1307
|
typeof AbortController !== "undefined" ? new AbortController() : undefined;
|
|
1308
|
+
const externalSignal = init.signal;
|
|
1309
|
+
const abortFromExternal = () => controller?.abort(externalSignal?.reason);
|
|
1310
|
+
if (controller && externalSignal) {
|
|
1311
|
+
if (externalSignal.aborted) abortFromExternal();
|
|
1312
|
+
else {
|
|
1313
|
+
externalSignal.addEventListener("abort", abortFromExternal, {
|
|
1314
|
+
once: true,
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1140
1318
|
const timer = controller
|
|
1141
1319
|
? setTimeout(() => controller.abort(), timeoutMs)
|
|
1142
1320
|
: undefined;
|
|
1143
1321
|
try {
|
|
1144
|
-
|
|
1322
|
+
const response = await fetch(url, {
|
|
1145
1323
|
...init,
|
|
1146
1324
|
signal: controller?.signal ?? init.signal,
|
|
1147
1325
|
});
|
|
1326
|
+
return (await response.json()) as Record<string, any>;
|
|
1148
1327
|
} finally {
|
|
1149
1328
|
if (timer) clearTimeout(timer);
|
|
1329
|
+
externalSignal?.removeEventListener("abort", abortFromExternal);
|
|
1150
1330
|
}
|
|
1151
1331
|
}
|
|
1152
1332
|
|
|
@@ -1165,12 +1345,11 @@ async function resolveSlackThreadPermalink(
|
|
|
1165
1345
|
const url = new URL("https://slack.com/api/chat.getPermalink");
|
|
1166
1346
|
url.searchParams.set("channel", channelId);
|
|
1167
1347
|
url.searchParams.set("message_ts", threadTs);
|
|
1168
|
-
const
|
|
1348
|
+
const data = (await slackApiJson(
|
|
1169
1349
|
url.toString(),
|
|
1170
1350
|
{ headers: { Authorization: `Bearer ${token}` } },
|
|
1171
1351
|
SLACK_PERMALINK_TIMEOUT_MS,
|
|
1172
|
-
)
|
|
1173
|
-
const data = (await res.json()) as {
|
|
1352
|
+
)) as {
|
|
1174
1353
|
ok?: boolean;
|
|
1175
1354
|
permalink?: unknown;
|
|
1176
1355
|
};
|
|
@@ -1260,12 +1439,11 @@ async function slackJson(
|
|
|
1260
1439
|
for (const [key, value] of Object.entries(params)) {
|
|
1261
1440
|
url.searchParams.set(key, value);
|
|
1262
1441
|
}
|
|
1263
|
-
const
|
|
1442
|
+
const body = await slackApiJson(
|
|
1264
1443
|
url.toString(),
|
|
1265
1444
|
{ headers: { Authorization: `Bearer ${token}` } },
|
|
1266
1445
|
timeoutMs,
|
|
1267
1446
|
);
|
|
1268
|
-
const body = (await response.json()) as Record<string, any>;
|
|
1269
1447
|
return body.ok ? body : null;
|
|
1270
1448
|
} catch {
|
|
1271
1449
|
return null;
|
|
@@ -1618,16 +1796,17 @@ async function postSlackJson(
|
|
|
1618
1796
|
token: string,
|
|
1619
1797
|
method: string,
|
|
1620
1798
|
body: Record<string, unknown>,
|
|
1799
|
+
signal?: AbortSignal,
|
|
1621
1800
|
): Promise<Record<string, any>> {
|
|
1622
|
-
const
|
|
1801
|
+
const data = await slackApiJson(`https://slack.com/api/${method}`, {
|
|
1623
1802
|
method: "POST",
|
|
1624
1803
|
headers: {
|
|
1625
1804
|
Authorization: `Bearer ${token}`,
|
|
1626
1805
|
"Content-Type": "application/json",
|
|
1627
1806
|
},
|
|
1628
1807
|
body: JSON.stringify(body),
|
|
1808
|
+
signal,
|
|
1629
1809
|
});
|
|
1630
|
-
const data = (await response.json()) as Record<string, any>;
|
|
1631
1810
|
if (!data.ok) throw new Error(data.error || `${method} failed`);
|
|
1632
1811
|
return data;
|
|
1633
1812
|
}
|
|
@@ -1775,6 +1954,7 @@ function createSlackRunProgress(
|
|
|
1775
1954
|
|
|
1776
1955
|
return {
|
|
1777
1956
|
ref: { kind: "slack-stream", streamTs },
|
|
1957
|
+
responseTargetRef: streamTs,
|
|
1778
1958
|
async onEvent(event) {
|
|
1779
1959
|
if (!cancelControl) {
|
|
1780
1960
|
const context = getIntegrationRequestContext();
|
|
@@ -1933,7 +2113,7 @@ function createSlackRunProgress(
|
|
|
1933
2113
|
});
|
|
1934
2114
|
}
|
|
1935
2115
|
},
|
|
1936
|
-
async complete(message) {
|
|
2116
|
+
async complete(message, opts) {
|
|
1937
2117
|
if (pendingTimer) clearTimeout(pendingTimer);
|
|
1938
2118
|
const finalChunks = [...tasks.entries()].map(([id, task]) => ({
|
|
1939
2119
|
type: "task_update",
|
|
@@ -1968,25 +2148,35 @@ function createSlackRunProgress(
|
|
|
1968
2148
|
},
|
|
1969
2149
|
]
|
|
1970
2150
|
: [];
|
|
1971
|
-
await postSlackJson(
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
: {}),
|
|
1979
|
-
|
|
2151
|
+
await postSlackJson(
|
|
2152
|
+
token,
|
|
2153
|
+
"chat.stopStream",
|
|
2154
|
+
{
|
|
2155
|
+
channel,
|
|
2156
|
+
ts: streamTs,
|
|
2157
|
+
markdown_text: message.text || "Done.",
|
|
2158
|
+
...(finalChunks.length ? { chunks: finalChunks } : {}),
|
|
2159
|
+
...(messageBlocks.length || controlBlocks.length
|
|
2160
|
+
? { blocks: [...messageBlocks, ...controlBlocks].slice(0, 50) }
|
|
2161
|
+
: {}),
|
|
2162
|
+
},
|
|
2163
|
+
opts?.signal,
|
|
2164
|
+
);
|
|
1980
2165
|
setSlackAssistantStatus(token, channel, threadTs, "");
|
|
1981
2166
|
return { status: "delivered", messageRefs: [streamTs] };
|
|
1982
2167
|
},
|
|
1983
|
-
async fail(message) {
|
|
2168
|
+
async fail(message, opts) {
|
|
1984
2169
|
if (pendingTimer) clearTimeout(pendingTimer);
|
|
1985
|
-
await postSlackJson(
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2170
|
+
await postSlackJson(
|
|
2171
|
+
token,
|
|
2172
|
+
"chat.stopStream",
|
|
2173
|
+
{
|
|
2174
|
+
channel,
|
|
2175
|
+
ts: streamTs,
|
|
2176
|
+
markdown_text: message.slice(0, SLACK_MAX_LENGTH),
|
|
2177
|
+
},
|
|
2178
|
+
opts?.signal,
|
|
2179
|
+
).catch(() => {});
|
|
1990
2180
|
setSlackAssistantStatus(token, channel, threadTs, "");
|
|
1991
2181
|
},
|
|
1992
2182
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getA2AContinuationTaskOutcome } from "./a2a-continuations-store.js";
|
|
1
2
|
import {
|
|
2
3
|
getIntegrationCampaign,
|
|
3
4
|
failDisabledIntegrationCampaignTask,
|
|
@@ -72,7 +73,9 @@ export async function recoverDueIntegrationCampaigns(options: {
|
|
|
72
73
|
: undefined,
|
|
73
74
|
},
|
|
74
75
|
);
|
|
75
|
-
const confirmedReceipt =
|
|
76
|
+
const confirmedReceipt =
|
|
77
|
+
hasConfirmedDeliveryReceipt(task.payload) ||
|
|
78
|
+
(await getA2AContinuationTaskOutcome(task.id)) === "terminal-delivered";
|
|
76
79
|
if (!durableDispatchEnabled && !confirmedReceipt) {
|
|
77
80
|
await failDisabledIntegrationCampaignTask(task.id);
|
|
78
81
|
const nextTask = await getNextPendingTaskForThread(
|
|
@@ -36,12 +36,11 @@ import { runWithRequestContext } from "../server/request-context.js";
|
|
|
36
36
|
import {
|
|
37
37
|
processA2AContinuationById,
|
|
38
38
|
processDueA2AContinuations,
|
|
39
|
+
reconcileTerminalA2AParentIfDisabled,
|
|
40
|
+
recoverA2AContinuationAfterProcessorFailure,
|
|
39
41
|
recoverDueA2AContinuations,
|
|
40
42
|
} from "./a2a-continuation-processor.js";
|
|
41
|
-
import {
|
|
42
|
-
failA2AContinuation,
|
|
43
|
-
hasActiveA2AContinuationsForIntegrationTask,
|
|
44
|
-
} from "./a2a-continuations-store.js";
|
|
43
|
+
import { getA2AContinuationTaskOutcome } from "./a2a-continuations-store.js";
|
|
45
44
|
import { mergeIntegrationAdapters } from "./adapter-overrides.js";
|
|
46
45
|
import { discordAdapter } from "./adapters/discord.js";
|
|
47
46
|
import { emailAdapter } from "./adapters/email.js";
|
|
@@ -1908,13 +1907,18 @@ export function createIntegrationsPlugin(
|
|
|
1908
1907
|
? { channelId: task.dispatchScope }
|
|
1909
1908
|
: undefined,
|
|
1910
1909
|
});
|
|
1910
|
+
const a2aOutcome = campaignContinuation
|
|
1911
|
+
? await getA2AContinuationTaskOutcome(task.id)
|
|
1912
|
+
: null;
|
|
1911
1913
|
const confirmedDeliveryReceipt =
|
|
1912
1914
|
taskPayload.kind === "response-delivery" &&
|
|
1913
1915
|
taskPayload.deliveryReceipt?.status === "delivered";
|
|
1916
|
+
const confirmedDeliveryProof =
|
|
1917
|
+
confirmedDeliveryReceipt || a2aOutcome === "terminal-delivered";
|
|
1914
1918
|
if (
|
|
1915
1919
|
campaignContinuation &&
|
|
1916
1920
|
!durableCampaignEnabled &&
|
|
1917
|
-
!
|
|
1921
|
+
!confirmedDeliveryProof
|
|
1918
1922
|
) {
|
|
1919
1923
|
await failDisabledIntegrationCampaignTask(task.id);
|
|
1920
1924
|
const nextTask = await getNextPendingTaskForThread(
|
|
@@ -1995,6 +1999,17 @@ export function createIntegrationsPlugin(
|
|
|
1995
1999
|
return;
|
|
1996
2000
|
}
|
|
1997
2001
|
if (taskPayload.kind === "response-delivery") {
|
|
2002
|
+
if (
|
|
2003
|
+
campaignContinuation &&
|
|
2004
|
+
taskPayload.awaitingA2ACompletion &&
|
|
2005
|
+
a2aOutcome === "terminal-delivered"
|
|
2006
|
+
) {
|
|
2007
|
+
const completed =
|
|
2008
|
+
await completeIntegrationCampaignTaskAfterA2A(task.id);
|
|
2009
|
+
return completed
|
|
2010
|
+
? ("completed" as const)
|
|
2011
|
+
: ("campaign-active" as const);
|
|
2012
|
+
}
|
|
1998
2013
|
let receipt: void | PlatformDeliveryReceipt =
|
|
1999
2014
|
taskPayload.deliveryReceipt;
|
|
2000
2015
|
let deliveryLease:
|
|
@@ -2114,15 +2129,15 @@ export function createIntegrationsPlugin(
|
|
|
2114
2129
|
if (!waiting) return "campaign-active" as const;
|
|
2115
2130
|
}
|
|
2116
2131
|
if (
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
))
|
|
2132
|
+
a2aOutcome === "terminal-without-delivery" &&
|
|
2133
|
+
(await reconcileTerminalA2AParentIfDisabled(task.id))
|
|
2120
2134
|
) {
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2135
|
+
return "campaign-failed" as const;
|
|
2136
|
+
}
|
|
2137
|
+
if (a2aOutcome !== "active") {
|
|
2138
|
+
console.warn(
|
|
2139
|
+
`[integrations] Waiting campaign ${task.id} has A2A outcome ${a2aOutcome} without terminal delivery proof`,
|
|
2140
|
+
);
|
|
2126
2141
|
}
|
|
2127
2142
|
return "campaign-active" as const;
|
|
2128
2143
|
}
|
|
@@ -2387,15 +2402,18 @@ export function createIntegrationsPlugin(
|
|
|
2387
2402
|
adapters: adapterMap,
|
|
2388
2403
|
});
|
|
2389
2404
|
} catch (err: any) {
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
await
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
).catch(() => {
|
|
2405
|
+
const reason =
|
|
2406
|
+
err?.message?.slice(0, 500) || "continuation processing failed";
|
|
2407
|
+
await recoverA2AContinuationAfterProcessorFailure(continuationId, {
|
|
2408
|
+
adapters: adapterMap,
|
|
2409
|
+
reason,
|
|
2410
|
+
}).catch(() => {
|
|
2411
|
+
console.error(
|
|
2412
|
+
`[integrations] A2A continuation ${continuationId} recovery scheduling failed`,
|
|
2413
|
+
);
|
|
2414
|
+
});
|
|
2396
2415
|
console.error(
|
|
2397
|
-
|
|
2398
|
-
err,
|
|
2416
|
+
`[integrations] process-a2a-continuation failure for ${continuationId}; durable recovery requested`,
|
|
2399
2417
|
);
|
|
2400
2418
|
setResponseStatus(event, 500);
|
|
2401
2419
|
return { error: "Failed to process A2A continuation" };
|