a2a-xmtp 1.4.5 → 1.4.6
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/package.json +4 -1
- package/src/transport/xmtp-bridge.ts +41 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "a2a-xmtp",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"description": "Decentralized Agent-to-Agent E2EE messaging for OpenClaw via XMTP",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"@xmtp/agent-sdk": "^2.3.0",
|
|
26
26
|
"@sinclair/typebox": "^0.34.0"
|
|
27
27
|
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"openclaw": "*"
|
|
30
|
+
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"typescript": "^5.7.0",
|
|
30
33
|
"vitest": "^3.0.0",
|
|
@@ -22,6 +22,14 @@ export class XmtpBridge {
|
|
|
22
22
|
private inboxBuffer: InboxMessage[] = [];
|
|
23
23
|
private readonly maxInboxBuffer = 100;
|
|
24
24
|
|
|
25
|
+
/** 已处理消息 ID 去重集合(防止 SDK 双投或 sync 重放) */
|
|
26
|
+
private processedMsgIds = new Set<string>();
|
|
27
|
+
private readonly maxProcessedIds = 200;
|
|
28
|
+
|
|
29
|
+
/** 出站去重:conversationId+contentHash → 发送结果(10s TTL) */
|
|
30
|
+
private recentSends = new Map<string, { conversationId: string; messageId: string }>();
|
|
31
|
+
private readonly sendDedupWindowMs = 3_000;
|
|
32
|
+
|
|
25
33
|
/** 消息回调(由 plugin 入口设置,用于触发 subagent 处理) */
|
|
26
34
|
onMessage?: (agentId: string, payload: A2AInjectPayload) => void;
|
|
27
35
|
|
|
@@ -97,13 +105,22 @@ export class XmtpBridge {
|
|
|
97
105
|
conversation = await (this.agent as any).createDmWithAddress(toAddress);
|
|
98
106
|
}
|
|
99
107
|
|
|
108
|
+
// 出站去重:同一会话 + 相同内容在 10s 内只发一次
|
|
109
|
+
const dedupKey = `${conversation.id}:${simpleHash(content)}`;
|
|
110
|
+
const cached = this.recentSends.get(dedupKey);
|
|
111
|
+
if (cached) return cached;
|
|
112
|
+
|
|
100
113
|
const messageId = opts?.contentType === "markdown"
|
|
101
114
|
? await conversation.sendMarkdown(content)
|
|
102
115
|
: await conversation.sendText(content);
|
|
103
116
|
|
|
104
117
|
this.policyEngine.recordTurn(conversation.id);
|
|
105
118
|
|
|
106
|
-
|
|
119
|
+
const result = { conversationId: conversation.id, messageId: String(messageId) };
|
|
120
|
+
this.recentSends.set(dedupKey, result);
|
|
121
|
+
setTimeout(() => this.recentSends.delete(dedupKey), this.sendDedupWindowMs);
|
|
122
|
+
|
|
123
|
+
return result;
|
|
107
124
|
}
|
|
108
125
|
|
|
109
126
|
/**
|
|
@@ -235,6 +252,19 @@ export class XmtpBridge {
|
|
|
235
252
|
}
|
|
236
253
|
|
|
237
254
|
private async handleIncoming(ctx: any, contentType: A2AContentType): Promise<void> {
|
|
255
|
+
// 消息去重:同一 XMTP 消息 ID 只处理一次
|
|
256
|
+
// 防止 SDK 双投(text+markdown 同时触发)或 conversations.sync() 重放
|
|
257
|
+
const rawMsgId: string | undefined = ctx.message.id;
|
|
258
|
+
if (rawMsgId) {
|
|
259
|
+
if (this.processedMsgIds.has(rawMsgId)) return;
|
|
260
|
+
this.processedMsgIds.add(rawMsgId);
|
|
261
|
+
if (this.processedMsgIds.size > this.maxProcessedIds) {
|
|
262
|
+
// 淘汰最旧的条目
|
|
263
|
+
const first = this.processedMsgIds.values().next().value!;
|
|
264
|
+
this.processedMsgIds.delete(first);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
238
268
|
const senderAddress: string = await ctx.getSenderAddress();
|
|
239
269
|
const isSelf = senderAddress.toLowerCase() === this.address.toLowerCase();
|
|
240
270
|
const content = String(ctx.message.content);
|
|
@@ -328,3 +358,13 @@ export class XmtpBridge {
|
|
|
328
358
|
return this.walletConfig.env;
|
|
329
359
|
}
|
|
330
360
|
}
|
|
361
|
+
|
|
362
|
+
/** FNV-1a 32-bit hash — fast, no crypto dependency */
|
|
363
|
+
function simpleHash(str: string): string {
|
|
364
|
+
let h = 0x811c9dc5;
|
|
365
|
+
for (let i = 0; i < str.length; i++) {
|
|
366
|
+
h ^= str.charCodeAt(i);
|
|
367
|
+
h = Math.imul(h, 0x01000193);
|
|
368
|
+
}
|
|
369
|
+
return (h >>> 0).toString(16);
|
|
370
|
+
}
|