@relaycast/engine 8.15.0 → 9.0.0
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/dist/db/migrations/0062_direct_dm_idempotency.sql +20 -0
- package/dist/db/migrations/0063_node_rotation_recovery.sql +13 -0
- package/dist/db/schema.d.ts +200 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +29 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/engine/a2aEgress.d.ts +10 -0
- package/dist/engine/a2aEgress.d.ts.map +1 -1
- package/dist/engine/a2aEgress.js +35 -3
- package/dist/engine/a2aEgress.js.map +1 -1
- package/dist/engine/attachments.d.ts.map +1 -1
- package/dist/engine/attachments.js +5 -3
- package/dist/engine/attachments.js.map +1 -1
- package/dist/engine/dm.d.ts +5 -1
- package/dist/engine/dm.d.ts.map +1 -1
- package/dist/engine/dm.js +131 -20
- package/dist/engine/dm.js.map +1 -1
- package/dist/engine/dmAll.d.ts +6 -0
- package/dist/engine/dmAll.d.ts.map +1 -1
- package/dist/engine/dmAll.js +51 -2
- package/dist/engine/dmAll.js.map +1 -1
- package/dist/engine/inboundWebhook.d.ts +4 -0
- package/dist/engine/inboundWebhook.d.ts.map +1 -1
- package/dist/engine/inboundWebhook.js +5 -0
- package/dist/engine/inboundWebhook.js.map +1 -1
- package/dist/engine/messageMetadata.d.ts +4 -0
- package/dist/engine/messageMetadata.d.ts.map +1 -1
- package/dist/engine/messageMetadata.js +7 -1
- package/dist/engine/messageMetadata.js.map +1 -1
- package/dist/engine/node.d.ts +37 -7
- package/dist/engine/node.d.ts.map +1 -1
- package/dist/engine/node.js +107 -19
- package/dist/engine/node.js.map +1 -1
- package/dist/engine/sessionEvent.d.ts.map +1 -1
- package/dist/engine/sessionEvent.js +2 -0
- package/dist/engine/sessionEvent.js.map +1 -1
- package/dist/engine/workspace.d.ts +3 -0
- package/dist/engine/workspace.d.ts.map +1 -1
- package/dist/engine/workspace.js +45 -1
- package/dist/engine/workspace.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/serverTelemetry.d.ts +13 -2
- package/dist/lib/serverTelemetry.d.ts.map +1 -1
- package/dist/lib/serverTelemetry.js +4 -0
- package/dist/lib/serverTelemetry.js.map +1 -1
- package/dist/middleware/idempotency.d.ts +15 -9
- package/dist/middleware/idempotency.d.ts.map +1 -1
- package/dist/middleware/idempotency.js +104 -58
- package/dist/middleware/idempotency.js.map +1 -1
- package/dist/routes/a2a.js +6 -1
- package/dist/routes/a2a.js.map +1 -1
- package/dist/routes/agent.js +1 -1
- package/dist/routes/agent.js.map +1 -1
- package/dist/routes/dm.d.ts.map +1 -1
- package/dist/routes/dm.js +70 -42
- package/dist/routes/dm.js.map +1 -1
- package/dist/routes/node.d.ts.map +1 -1
- package/dist/routes/node.js +38 -3
- package/dist/routes/node.js.map +1 -1
- package/dist/routes/webhookOutbox.d.ts +1 -0
- package/dist/routes/webhookOutbox.d.ts.map +1 -1
- package/dist/routes/webhookOutbox.js +9 -5
- package/dist/routes/webhookOutbox.js.map +1 -1
- package/dist/routes/workspace.d.ts.map +1 -1
- package/dist/routes/workspace.js +46 -17
- package/dist/routes/workspace.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type EngineDb } from '../ports/database.js';
|
|
2
2
|
/** Same finite retry horizon as HTTP idempotency; payloads never dispatch after it. */
|
|
3
3
|
export declare const A2A_EGRESS_RETRY_WINDOW_MS: number;
|
|
4
|
+
export declare const DIRECT_DM_IDEMPOTENCY_CLEANUP_BATCH_SIZE = 500;
|
|
4
5
|
/** Durable admission and a lease exclude concurrent senders. A crash after remote
|
|
5
6
|
* acceptance can replay the same message_id and HTTP Idempotency-Key; receivers
|
|
6
7
|
* must deduplicate them. Arbitrary receivers still have at-least-once delivery.
|
|
@@ -11,6 +12,15 @@ export declare function dispatchA2aEgress(db: EngineDb, id: string): Promise<voi
|
|
|
11
12
|
* After deletion a key is fresh, as with the existing HTTP idempotency contract.
|
|
12
13
|
*/
|
|
13
14
|
export declare function cleanupA2aEgress(db: EngineDb, limit?: number): Promise<number>;
|
|
15
|
+
/** Remove one expired request identity before the caller admits a fresh use of
|
|
16
|
+
* its key. A live transport lease wins the race and keeps the row until a
|
|
17
|
+
* later retry or maintenance pass.
|
|
18
|
+
*/
|
|
19
|
+
export declare function expireA2aEgressForReuse(db: EngineDb, id: string, now?: Date): Promise<boolean>;
|
|
20
|
+
/** Bounded cleanup for transport-independent DM request claims. Once the
|
|
21
|
+
* 24-hour replay window ends, the same caller key is intentionally fresh.
|
|
22
|
+
*/
|
|
23
|
+
export declare function cleanupDirectDmIdempotency(db: EngineDb, limit?: number, now?: Date): Promise<number>;
|
|
14
24
|
/** Existing Node/HOST maintenance entrypoint includes bounded retention cleanup. */
|
|
15
25
|
export declare function sweepPendingA2aEgress(db: EngineDb, limit?: number): Promise<{
|
|
16
26
|
attempted: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2aEgress.d.ts","sourceRoot":"","sources":["../../src/engine/a2aEgress.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKtE,uFAAuF;AACvF,eAAO,MAAM,0BAA0B,QAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"a2aEgress.d.ts","sourceRoot":"","sources":["../../src/engine/a2aEgress.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAKtE,uFAAuF;AACvF,eAAO,MAAM,0BAA0B,QAAsB,CAAC;AAC9D,eAAO,MAAM,wCAAwC,MAAM,CAAC;AA2B5D;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8D/E;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAKhF;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,EAAE,EAAE,QAAQ,EACZ,EAAE,EAAE,MAAM,EACV,GAAG,OAAa,GACf,OAAO,CAAC,OAAO,CAAC,CAKlB;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,QAAQ,EACZ,KAAK,SAA2C,EAChD,GAAG,OAAa,GACf,OAAO,CAAC,MAAM,CAAC,CAUjB;AAED,oFAAoF;AACpF,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAmBpH"}
|
package/dist/engine/a2aEgress.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { and, eq, or, sql, lte, inArray, asc } from 'drizzle-orm';
|
|
2
|
-
import { a2aAgents, a2aEgress, a2aInbound, messages, agents } from '../db/schema.js';
|
|
2
|
+
import { a2aAgents, a2aEgress, a2aInbound, directDmIdempotency, messages, agents } from '../db/schema.js';
|
|
3
3
|
import { runAtomicWrites } from '../ports/database.js';
|
|
4
4
|
import { randomUuid } from '../lib/crypto.js';
|
|
5
5
|
import { codedError } from '../lib/httpError.js';
|
|
6
6
|
import { sendToExternalAgent } from './a2a.js';
|
|
7
7
|
/** Same finite retry horizon as HTTP idempotency; payloads never dispatch after it. */
|
|
8
8
|
export const A2A_EGRESS_RETRY_WINDOW_MS = 24 * 60 * 60 * 1000;
|
|
9
|
+
export const DIRECT_DM_IDEMPOTENCY_CLEANUP_BATCH_SIZE = 500;
|
|
9
10
|
const batchLimit = (limit) => Number.isFinite(limit) ? Math.max(1, Math.min(100, Math.floor(limit))) : 20;
|
|
11
|
+
const directClaimBatchLimit = (limit) => Number.isFinite(limit)
|
|
12
|
+
? Math.max(1, Math.min(1_000, Math.floor(limit)))
|
|
13
|
+
: DIRECT_DM_IDEMPOTENCY_CLEANUP_BATCH_SIZE;
|
|
10
14
|
const expired = (intent) => intent.createdAt.getTime() + A2A_EGRESS_RETRY_WINDOW_MS <= Date.now();
|
|
11
15
|
const notRetained = () => codedError('Accepted A2A message is no longer retained', 'a2a_message_not_retained', 410);
|
|
12
16
|
const targetGone = () => codedError('Accepted A2A target no longer registered at its original endpoint', 'a2a_target_gone', 410);
|
|
13
17
|
const windowExpired = () => codedError('Accepted A2A retry window expired', 'a2a_egress_expired', 410);
|
|
18
|
+
const cleanupEligible = (now) => and(lte(a2aEgress.createdAt, new Date(now.getTime() - A2A_EGRESS_RETRY_WINDOW_MS)), or(sql `${a2aEgress.leaseUntil} IS NULL`, lte(a2aEgress.leaseUntil, now), sql `${a2aEgress.status} != 'sending'`));
|
|
14
19
|
async function validateSource(db, intent) {
|
|
15
20
|
if (expired(intent))
|
|
16
21
|
throw windowExpired();
|
|
@@ -90,12 +95,35 @@ export async function dispatchA2aEgress(db, id) {
|
|
|
90
95
|
* After deletion a key is fresh, as with the existing HTTP idempotency contract.
|
|
91
96
|
*/
|
|
92
97
|
export async function cleanupA2aEgress(db, limit = 20) {
|
|
93
|
-
const
|
|
94
|
-
const candidates = db.select({ id: a2aEgress.id }).from(a2aEgress).where(eligible)
|
|
98
|
+
const candidates = db.select({ id: a2aEgress.id }).from(a2aEgress).where(cleanupEligible(new Date()))
|
|
95
99
|
.orderBy(asc(a2aEgress.createdAt), asc(a2aEgress.id)).limit(batchLimit(limit));
|
|
96
100
|
const deleted = await db.delete(a2aEgress).where(inArray(a2aEgress.id, candidates)).returning({ id: a2aEgress.id });
|
|
97
101
|
return deleted.length;
|
|
98
102
|
}
|
|
103
|
+
/** Remove one expired request identity before the caller admits a fresh use of
|
|
104
|
+
* its key. A live transport lease wins the race and keeps the row until a
|
|
105
|
+
* later retry or maintenance pass.
|
|
106
|
+
*/
|
|
107
|
+
export async function expireA2aEgressForReuse(db, id, now = new Date()) {
|
|
108
|
+
const deleted = await db.delete(a2aEgress)
|
|
109
|
+
.where(and(eq(a2aEgress.id, id), cleanupEligible(now)))
|
|
110
|
+
.returning({ id: a2aEgress.id });
|
|
111
|
+
return deleted.length > 0;
|
|
112
|
+
}
|
|
113
|
+
/** Bounded cleanup for transport-independent DM request claims. Once the
|
|
114
|
+
* 24-hour replay window ends, the same caller key is intentionally fresh.
|
|
115
|
+
*/
|
|
116
|
+
export async function cleanupDirectDmIdempotency(db, limit = DIRECT_DM_IDEMPOTENCY_CLEANUP_BATCH_SIZE, now = new Date()) {
|
|
117
|
+
const expired = db.select({ id: directDmIdempotency.id })
|
|
118
|
+
.from(directDmIdempotency)
|
|
119
|
+
.where(lte(directDmIdempotency.createdAt, new Date(now.getTime() - A2A_EGRESS_RETRY_WINDOW_MS)))
|
|
120
|
+
.orderBy(asc(directDmIdempotency.createdAt), asc(directDmIdempotency.id))
|
|
121
|
+
.limit(directClaimBatchLimit(limit));
|
|
122
|
+
const deleted = await db.delete(directDmIdempotency)
|
|
123
|
+
.where(inArray(directDmIdempotency.id, expired))
|
|
124
|
+
.returning({ id: directDmIdempotency.id });
|
|
125
|
+
return deleted.length;
|
|
126
|
+
}
|
|
99
127
|
/** Existing Node/HOST maintenance entrypoint includes bounded retention cleanup. */
|
|
100
128
|
export async function sweepPendingA2aEgress(db, limit = 20) {
|
|
101
129
|
const due = await db.select({ id: a2aEgress.id }).from(a2aEgress).where(and(or(eq(a2aEgress.status, 'pending'), eq(a2aEgress.status, 'sending')), or(sql `${a2aEgress.leaseUntil} IS NULL`, lte(a2aEgress.leaseUntil, new Date())))).limit(batchLimit(limit));
|
|
@@ -109,6 +137,10 @@ export async function sweepPendingA2aEgress(db, limit = 20) {
|
|
|
109
137
|
}
|
|
110
138
|
}
|
|
111
139
|
await cleanupA2aEgress(db, limit);
|
|
140
|
+
// Direct request claims arrive on every keyed /v1/dm call and are unrelated
|
|
141
|
+
// to the small transport retry budget. Give them their own bounded batch so
|
|
142
|
+
// a rotating hosted cron cannot accumulate snapshots beyond message retention.
|
|
143
|
+
await cleanupDirectDmIdempotency(db, DIRECT_DM_IDEMPOTENCY_CLEANUP_BATCH_SIZE);
|
|
112
144
|
const inboundExpired = db.select({ id: a2aInbound.id }).from(a2aInbound)
|
|
113
145
|
.where(lte(a2aInbound.createdAt, new Date(Date.now() - A2A_EGRESS_RETRY_WINDOW_MS)))
|
|
114
146
|
.orderBy(asc(a2aInbound.createdAt), asc(a2aInbound.id)).limit(batchLimit(limit));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"a2aEgress.js","sourceRoot":"","sources":["../../src/engine/a2aEgress.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"a2aEgress.js","sourceRoot":"","sources":["../../src/engine/a2aEgress.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/engine/attachments.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/engine/attachments.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAK5C,KAAK,EAAE,GAAG,UAAU,CAAC,OAAO,KAAK,CAAC,CAAC;AAEnC,mEAAmE;AACnE,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,aAAa,EAAE,CAAC,CAuC1B;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,EAAE,EACN,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EAAE,GACf,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC,CA6BvC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { and, asc, eq, inArray } from 'drizzle-orm';
|
|
2
2
|
import { messageAttachments, files } from '../db/schema.js';
|
|
3
3
|
import { codedError } from '../lib/httpError.js';
|
|
4
|
+
import { queryInChunks } from '../lib/queryChunks.js';
|
|
4
5
|
/**
|
|
5
6
|
* Validate attachment ids for a send path and hydrate the details needed for
|
|
6
7
|
* response payloads and logs. Unknown, foreign-workspace, incomplete, and
|
|
@@ -49,7 +50,8 @@ export async function fetchAttachmentsBatch(db, workspaceId, msgIds) {
|
|
|
49
50
|
const map = new Map();
|
|
50
51
|
if (msgIds.length === 0)
|
|
51
52
|
return map;
|
|
52
|
-
const
|
|
53
|
+
const uniqueMsgIds = [...new Set(msgIds)];
|
|
54
|
+
const rows = await queryInChunks(uniqueMsgIds, (chunk) => db
|
|
53
55
|
.select({
|
|
54
56
|
messageId: messageAttachments.messageId,
|
|
55
57
|
fileId: messageAttachments.fileId,
|
|
@@ -59,8 +61,8 @@ export async function fetchAttachmentsBatch(db, workspaceId, msgIds) {
|
|
|
59
61
|
})
|
|
60
62
|
.from(messageAttachments)
|
|
61
63
|
.innerJoin(files, eq(messageAttachments.fileId, files.id))
|
|
62
|
-
.where(and(inArray(messageAttachments.messageId,
|
|
63
|
-
.orderBy(asc(messageAttachments.messageId), asc(messageAttachments.position));
|
|
64
|
+
.where(and(inArray(messageAttachments.messageId, chunk), eq(files.workspaceId, workspaceId)))
|
|
65
|
+
.orderBy(asc(messageAttachments.messageId), asc(messageAttachments.position)));
|
|
64
66
|
for (const row of rows) {
|
|
65
67
|
const list = map.get(row.messageId) || [];
|
|
66
68
|
list.push({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../src/engine/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEpD,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../src/engine/attachments.ts"],"names":[],"mappings":"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"}
|
package/dist/engine/dm.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ interface SendDmOptions {
|
|
|
10
10
|
skipA2aIntercept?: boolean;
|
|
11
11
|
/** Stable request identity for resuming already admitted outbound A2A. */
|
|
12
12
|
idempotencyKey?: string;
|
|
13
|
+
/** Whether durable webhook fanout has any subscriber to receive it. */
|
|
14
|
+
hasWebhookSubscriptions?: boolean;
|
|
13
15
|
/** Count an authenticated inbound peer in the same transaction as its DM. */
|
|
14
16
|
receivedA2aAgentId?: string;
|
|
15
17
|
/** Token hash authenticated by the route; checked with registration at SQL admission. */
|
|
@@ -28,7 +30,7 @@ interface SendDmOptions {
|
|
|
28
30
|
seq: number;
|
|
29
31
|
payload: Record<string, unknown>;
|
|
30
32
|
data: Record<string, unknown>;
|
|
31
|
-
outboxId
|
|
33
|
+
outboxId?: string;
|
|
32
34
|
}) => void;
|
|
33
35
|
mailbox?: MailboxConfig;
|
|
34
36
|
/** Server-resolved workspace growth policy; absent => no workspace guard. */
|
|
@@ -105,6 +107,8 @@ export type SendDmResult = AcceptedDmResult & {
|
|
|
105
107
|
_delivery: DeliveryOutcomeRecords['deliveries'][number] | null;
|
|
106
108
|
_delivery_rejections: DeliveryOutcomeRecords['rejections'];
|
|
107
109
|
_notifications_durable?: boolean;
|
|
110
|
+
_idempotency_replayed?: boolean;
|
|
111
|
+
_idempotency_ttl_seconds?: number;
|
|
108
112
|
};
|
|
109
113
|
export declare function sendDm(db: Db, workspaceId: string, fromAgentId: string, data: {
|
|
110
114
|
to: string;
|
package/dist/engine/dm.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/engine/dm.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EACL,QAAQ,
|
|
1
|
+
{"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/engine/dm.ts"],"names":[],"mappings":"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"}
|
package/dist/engine/dm.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { eq, and, sql, lt, lte, gt, isNull, inArray, desc } from 'drizzle-orm';
|
|
3
|
-
import { messages, channels, agents, dmConversations, dmConversationReservations, dmParticipants, messageAttachments, a2aEgress, a2aEgressContext, a2aInbound, a2aAgents, pendingEvents, messageLogs, nodes, } from '../db/schema.js';
|
|
3
|
+
import { messages, channels, agents, dmConversations, dmConversationReservations, dmParticipants, messageAttachments, a2aEgress, a2aEgressContext, a2aInbound, a2aAgents, pendingEvents, messageLogs, nodes, directDmIdempotency, } from '../db/schema.js';
|
|
4
4
|
import { sha256Hex } from '../lib/crypto.js';
|
|
5
5
|
import { runAtomicWrites, databaseConstraintKind } from '../ports/database.js';
|
|
6
6
|
import { generateId } from './snowflake.js';
|
|
@@ -8,7 +8,7 @@ import * as a2aEngine from './a2a.js';
|
|
|
8
8
|
import { buildMessageLogWrite } from './console.js';
|
|
9
9
|
import { buildDirectDeliveryWrite, fetchDirectDeliveryOutcomes, } from './deliveryWrites.js';
|
|
10
10
|
import { DEFAULT_MAILBOX_DEPTH_CAP, DEFAULT_MAILBOX_TTL_MS } from './mailboxConfig.js';
|
|
11
|
-
import { dispatchA2aEgress } from './a2aEgress.js';
|
|
11
|
+
import { A2A_EGRESS_RETRY_WINDOW_MS, dispatchA2aEgress, expireA2aEgressForReuse } from './a2aEgress.js';
|
|
12
12
|
import { buildDmReceivedEventData } from './deliveryWire.js';
|
|
13
13
|
import { buildWorkspaceEventWrite } from './workspaceEvents.js';
|
|
14
14
|
import { transformForClient } from './wsTransform.js';
|
|
@@ -16,8 +16,9 @@ import { codedError } from '../lib/httpError.js';
|
|
|
16
16
|
import { addressNodeSelection, addressNotFound, addressSplits, formatAgentAddress, selectAddressedRecipient, SENDER_ADDRESS_METADATA_KEY, senderAddressField, } from './address.js';
|
|
17
17
|
import { buildMessageSessionWrite, requireSessionRefFromMetadata } from './sessionMessages.js';
|
|
18
18
|
import { fetchAttachmentsBatch, resolveSendAttachments } from './attachments.js';
|
|
19
|
-
import { canonicalUserMessageMetadata, publicMessageMetadata, sanitizeUserMessageMetadata } from './messageMetadata.js';
|
|
19
|
+
import { canonicalJson, canonicalUserMessageMetadata, publicMessageMetadata, sanitizeUserMessageMetadata } from './messageMetadata.js';
|
|
20
20
|
import { queryInChunks } from '../lib/queryChunks.js';
|
|
21
|
+
const DIRECT_DM_IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1000;
|
|
21
22
|
/**
|
|
22
23
|
* Derivation only: this digest does not resolve or claim a conversation.
|
|
23
24
|
* Callers must atomically reserve the derived id before using it.
|
|
@@ -336,19 +337,90 @@ async function promoteLegacyInbound(db, workspaceId, fromAgentId, inboundId, fin
|
|
|
336
337
|
throw codedError('Accepted A2A message is no longer retained', 'a2a_message_not_retained', 410);
|
|
337
338
|
return { ...retained.response, _delivery: null, _delivery_rejections: [], _notifications_durable: true };
|
|
338
339
|
}
|
|
340
|
+
async function replayAcceptedA2aEgress(db, id) {
|
|
341
|
+
try {
|
|
342
|
+
await dispatchA2aEgress(db, id);
|
|
343
|
+
}
|
|
344
|
+
catch (error) {
|
|
345
|
+
// The durable receipt is stable only while another sender or retry lease
|
|
346
|
+
// owns transport. Retention, target, expiry, and upstream errors still
|
|
347
|
+
// preserve their original typed replay contract.
|
|
348
|
+
if (error.code !== 'a2a_egress_in_progress')
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
339
352
|
export async function sendDm(db, workspaceId, fromAgentId, data, options = {}) {
|
|
340
353
|
const startedAtMs = Date.now();
|
|
341
|
-
|
|
342
|
-
|
|
354
|
+
const directRequestId = options.idempotencyKey
|
|
355
|
+
? `dmid_${await sha256Hex(JSON.stringify([workspaceId, fromAgentId, options.idempotencyKey]))}`
|
|
356
|
+
: null;
|
|
357
|
+
const directFingerprint = directRequestId
|
|
358
|
+
? await sha256Hex(canonicalJson({ data, address: options.address ?? null }))
|
|
359
|
+
: '';
|
|
360
|
+
// The request claim is transport-independent. If the recipient changes
|
|
361
|
+
// between direct and A2A routing, both attempts still contend on one ID.
|
|
343
362
|
const requestEgressId = !options.skipA2aIntercept && options.idempotencyKey
|
|
344
363
|
? `a2ae_${await sha256Hex(JSON.stringify([workspaceId, fromAgentId, options.idempotencyKey]))}`
|
|
345
364
|
: null;
|
|
346
|
-
const
|
|
365
|
+
const requestEgressFingerprint = requestEgressId
|
|
366
|
+
? await sha256Hex(canonicalJson(data))
|
|
367
|
+
: '';
|
|
368
|
+
const legacyRequestEgressFingerprint = requestEgressId
|
|
369
|
+
? await sha256Hex(JSON.stringify(data))
|
|
370
|
+
: '';
|
|
371
|
+
const idempotencyNow = new Date();
|
|
372
|
+
let [acceptedDirect] = directRequestId
|
|
373
|
+
? await db.select().from(directDmIdempotency).where(eq(directDmIdempotency.id, directRequestId))
|
|
374
|
+
: [];
|
|
375
|
+
if (acceptedDirect
|
|
376
|
+
&& acceptedDirect.createdAt.getTime() + DIRECT_DM_IDEMPOTENCY_TTL_MS <= idempotencyNow.getTime()) {
|
|
377
|
+
await db.delete(directDmIdempotency).where(and(eq(directDmIdempotency.id, acceptedDirect.id), lte(directDmIdempotency.createdAt, new Date(idempotencyNow.getTime() - DIRECT_DM_IDEMPOTENCY_TTL_MS))));
|
|
378
|
+
[acceptedDirect] = await db
|
|
379
|
+
.select()
|
|
380
|
+
.from(directDmIdempotency)
|
|
381
|
+
.where(eq(directDmIdempotency.id, directRequestId));
|
|
382
|
+
}
|
|
383
|
+
if (acceptedDirect) {
|
|
384
|
+
if (acceptedDirect.fingerprint !== directFingerprint) {
|
|
385
|
+
throw codedError('Idempotency-Key was reused with a different request payload', 'idempotency_key_reused', 409);
|
|
386
|
+
}
|
|
387
|
+
const [acceptedEgress] = requestEgressId
|
|
388
|
+
? await db.select().from(a2aEgress).where(eq(a2aEgress.id, requestEgressId))
|
|
389
|
+
: [];
|
|
390
|
+
if (acceptedEgress)
|
|
391
|
+
await replayAcceptedA2aEgress(db, acceptedEgress.id);
|
|
392
|
+
return {
|
|
393
|
+
...acceptedDirect.response,
|
|
394
|
+
_delivery: null,
|
|
395
|
+
_delivery_rejections: [],
|
|
396
|
+
_notifications_durable: true,
|
|
397
|
+
_idempotency_replayed: true,
|
|
398
|
+
_idempotency_ttl_seconds: Math.max(0, Math.floor((acceptedDirect.createdAt.getTime() + DIRECT_DM_IDEMPOTENCY_TTL_MS - Date.now()) / 1000)),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
// Resolve durable request identity before mutable recipient/attachment metadata.
|
|
402
|
+
// A removed/recreated target must never turn an accepted retry into a new send.
|
|
403
|
+
let accepted = requestEgressId
|
|
404
|
+
? (await db.select().from(a2aEgress).where(eq(a2aEgress.id, requestEgressId)))[0]
|
|
405
|
+
: undefined;
|
|
406
|
+
if (accepted
|
|
407
|
+
&& accepted.createdAt.getTime() + A2A_EGRESS_RETRY_WINDOW_MS <= idempotencyNow.getTime()) {
|
|
408
|
+
const deleted = await expireA2aEgressForReuse(db, accepted.id, idempotencyNow);
|
|
409
|
+
if (deleted) {
|
|
410
|
+
accepted = undefined;
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
// A live transport lease may have won between the read and conditional
|
|
414
|
+
// delete. Re-read the row rather than admitting a competing identity.
|
|
415
|
+
[accepted] = await db.select().from(a2aEgress).where(eq(a2aEgress.id, requestEgressId));
|
|
416
|
+
}
|
|
417
|
+
}
|
|
347
418
|
if (accepted) {
|
|
348
|
-
if (accepted.fingerprint !==
|
|
419
|
+
if (accepted.fingerprint !== requestEgressFingerprint
|
|
420
|
+
&& accepted.fingerprint !== legacyRequestEgressFingerprint) {
|
|
349
421
|
throw codedError('Idempotency-Key was reused with a different request payload', 'idempotency_key_reused', 409);
|
|
350
422
|
}
|
|
351
|
-
await
|
|
423
|
+
await replayAcceptedA2aEgress(db, accepted.id);
|
|
352
424
|
let [context] = await db.select().from(a2aEgressContext).where(eq(a2aEgressContext.id, accepted.id));
|
|
353
425
|
if (!context) {
|
|
354
426
|
// Upgrade compatibility for admissions predating 0058: recover only from
|
|
@@ -430,7 +502,9 @@ export async function sendDm(db, workspaceId, fromAgentId, data, options = {}) {
|
|
|
430
502
|
const egressId = a2aTarget
|
|
431
503
|
? `a2ae_${await sha256Hex(JSON.stringify([workspaceId, fromAgentId, options.idempotencyKey ?? generateId()]))}`
|
|
432
504
|
: null;
|
|
433
|
-
const fingerprint = egressId
|
|
505
|
+
const fingerprint = egressId
|
|
506
|
+
? requestEgressFingerprint || await sha256Hex(canonicalJson(data))
|
|
507
|
+
: '';
|
|
434
508
|
const messageId = generateId();
|
|
435
509
|
// Match SQLite timestamp precision so live, retained response and delivery replay agree.
|
|
436
510
|
const createdAt = new Date(Math.floor(Date.now() / 1000) * 1000);
|
|
@@ -471,10 +545,26 @@ export async function sendDm(db, workspaceId, fromAgentId, data, options = {}) {
|
|
|
471
545
|
const workspacePayload = transformForClient({
|
|
472
546
|
type: 'dm.received', workspace_id: workspaceId, data: eventData, timestamp: createdAt.toISOString(),
|
|
473
547
|
});
|
|
474
|
-
|
|
475
|
-
|
|
548
|
+
const directClaimId = directRequestId;
|
|
549
|
+
const notificationsDurable = Boolean(egressId || options.receivedA2aAgentId || inboundId || directClaimId);
|
|
550
|
+
const persistWebhookOutbox = notificationsDurable && options.hasWebhookSubscriptions !== false;
|
|
551
|
+
// The optional webhook outbox, observer cursor log, response context and
|
|
552
|
+
// delivery share admission. None can escape a capacity rollback, or depend
|
|
553
|
+
// on external transport success.
|
|
476
554
|
const persist = () => runAtomicWrites(db, (writeDb) => {
|
|
477
555
|
const writes = buildDmMessageWrites(writeDb, workspaceId, fromAgentId, conv.channelId, data, attachments, messageId, createdAt, options.receivedA2aAgentId ? { id: options.receivedA2aAgentId, tokenHash: options.receivedA2aTokenHash } : undefined, senderAddress, addressedRecipient);
|
|
556
|
+
if (directClaimId) {
|
|
557
|
+
// The unique claim is the first statement in the atomic admission. A
|
|
558
|
+
// concurrent loser rolls back before any message or delivery can escape.
|
|
559
|
+
writes.unshift(writeDb.insert(directDmIdempotency).values({
|
|
560
|
+
id: directClaimId,
|
|
561
|
+
workspaceId,
|
|
562
|
+
messageId,
|
|
563
|
+
fingerprint: directFingerprint,
|
|
564
|
+
response: publicResult,
|
|
565
|
+
createdAt,
|
|
566
|
+
}));
|
|
567
|
+
}
|
|
478
568
|
if (egressId && a2aTarget && egressPayload) {
|
|
479
569
|
// First statement owns the request identity; a competing attempt rolls
|
|
480
570
|
// back before it can consume capacity or invoke the external transport.
|
|
@@ -522,15 +612,18 @@ export async function sendDm(db, workspaceId, fromAgentId, data, options = {}) {
|
|
|
522
612
|
writes.push(writeDb.insert(a2aInbound).values({ id: inboundId, workspaceId, messageId, fingerprint: inboundFingerprint, response: publicResult }));
|
|
523
613
|
if (egressId)
|
|
524
614
|
writes.push(writeDb.insert(a2aEgressContext).values({ id: egressId, messageId, response: publicResult }));
|
|
525
|
-
if (
|
|
526
|
-
writes.push(writeDb.insert(pendingEvents).values({ id: messageId, workspaceId, eventType: 'dm.received', payload: eventData })
|
|
615
|
+
if (persistWebhookOutbox) {
|
|
616
|
+
writes.push(writeDb.insert(pendingEvents).values({ id: messageId, workspaceId, eventType: 'dm.received', payload: eventData }));
|
|
617
|
+
}
|
|
618
|
+
if (notificationsDurable) {
|
|
619
|
+
writes.push(buildWorkspaceEventWrite(writeDb, workspaceId, { type: 'dm.received', payload: workspacePayload }));
|
|
527
620
|
}
|
|
528
621
|
return writes;
|
|
529
|
-
}, { requireAtomic: Boolean(workspacePolicy || a2aTarget || options.receivedA2aAgentId || inboundId) });
|
|
622
|
+
}, { requireAtomic: Boolean(workspacePolicy || a2aTarget || options.receivedA2aAgentId || inboundId || directClaimId) });
|
|
530
623
|
let admittedEventSeq;
|
|
531
624
|
try {
|
|
532
625
|
const results = await persist();
|
|
533
|
-
if (
|
|
626
|
+
if (notificationsDurable)
|
|
534
627
|
admittedEventSeq = results[results.length - 1][0].seq;
|
|
535
628
|
}
|
|
536
629
|
catch (error) {
|
|
@@ -548,11 +641,21 @@ export async function sendDm(db, workspaceId, fromAgentId, data, options = {}) {
|
|
|
548
641
|
return sendDm(db, workspaceId, fromAgentId, data, options);
|
|
549
642
|
}
|
|
550
643
|
}
|
|
644
|
+
if (directClaimId) {
|
|
645
|
+
const [winner] = await db.select().from(directDmIdempotency).where(eq(directDmIdempotency.id, directClaimId));
|
|
646
|
+
if (winner) {
|
|
647
|
+
if (winner.fingerprint !== directFingerprint) {
|
|
648
|
+
throw codedError('Idempotency-Key was reused with a different request payload', 'idempotency_key_reused', 409);
|
|
649
|
+
}
|
|
650
|
+
return sendDm(db, workspaceId, fromAgentId, data, options);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
551
653
|
// Inspect the actual committed winner after the losing atomic batch rolls back.
|
|
552
654
|
const [winner] = egressId ? await db.select().from(a2aEgress).where(eq(a2aEgress.id, egressId)) : [];
|
|
553
655
|
if (!winner)
|
|
554
656
|
throw error;
|
|
555
|
-
if (winner.fingerprint !== fingerprint
|
|
657
|
+
if (winner.fingerprint !== fingerprint
|
|
658
|
+
&& winner.fingerprint !== legacyRequestEgressFingerprint) {
|
|
556
659
|
throw codedError('Idempotency-Key was reused with a different request payload', 'idempotency_key_reused', 409);
|
|
557
660
|
}
|
|
558
661
|
return sendDm(db, workspaceId, fromAgentId, data, options);
|
|
@@ -565,15 +668,23 @@ export async function sendDm(db, workspaceId, fromAgentId, data, options = {}) {
|
|
|
565
668
|
...publicResult,
|
|
566
669
|
_delivery: dmDelivery,
|
|
567
670
|
_delivery_rejections: deliveryOutcomes.rejections,
|
|
568
|
-
...(
|
|
671
|
+
...(notificationsDurable ? { _notifications_durable: true } : {}),
|
|
569
672
|
};
|
|
570
|
-
if (
|
|
673
|
+
if (notificationsDurable) {
|
|
571
674
|
// Local fast paths run independently of transport. A crash here still leaves
|
|
572
|
-
//
|
|
573
|
-
options.afterAdmission?.(result, {
|
|
675
|
+
// any needed webhook outbox, workspace cursor log and queued delivery recoverable.
|
|
676
|
+
options.afterAdmission?.(result, {
|
|
677
|
+
seq: admittedEventSeq, payload: workspacePayload, data: eventData,
|
|
678
|
+
...(persistWebhookOutbox ? { outboxId: messageId } : {}),
|
|
679
|
+
});
|
|
574
680
|
}
|
|
575
681
|
if (egressId)
|
|
576
682
|
await dispatchA2aEgress(db, egressId);
|
|
683
|
+
if (directClaimId) {
|
|
684
|
+
// Compute this after transport work so a slow A2A dispatch cannot refill KV
|
|
685
|
+
// beyond the authoritative claim's absolute 24-hour lifetime.
|
|
686
|
+
result._idempotency_ttl_seconds = Math.max(0, Math.floor((createdAt.getTime() + DIRECT_DM_IDEMPOTENCY_TTL_MS - Date.now()) / 1000));
|
|
687
|
+
}
|
|
577
688
|
return result;
|
|
578
689
|
}
|
|
579
690
|
export async function listConversations(db, workspaceId, agentId, opts = {}) {
|