a2a-xmtp 1.2.4 → 1.3.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.
Files changed (51) hide show
  1. package/README.md +8 -7
  2. package/dist/coordination/message-orchestrator.d.ts +55 -0
  3. package/dist/coordination/message-orchestrator.d.ts.map +1 -0
  4. package/dist/coordination/message-orchestrator.js +142 -0
  5. package/dist/coordination/message-orchestrator.js.map +1 -0
  6. package/dist/coordination/policy-engine.d.ts +34 -0
  7. package/dist/coordination/policy-engine.d.ts.map +1 -0
  8. package/dist/coordination/policy-engine.js +92 -0
  9. package/dist/coordination/policy-engine.js.map +1 -0
  10. package/dist/index.d.ts +9 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +163 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/tools/xmtp-agents.d.ts +19 -0
  15. package/dist/tools/xmtp-agents.d.ts.map +1 -0
  16. package/dist/tools/xmtp-agents.js +27 -0
  17. package/dist/tools/xmtp-agents.js.map +1 -0
  18. package/dist/tools/xmtp-group.d.ts +95 -0
  19. package/dist/tools/xmtp-group.d.ts.map +1 -0
  20. package/dist/tools/xmtp-group.js +134 -0
  21. package/dist/tools/xmtp-group.js.map +1 -0
  22. package/dist/tools/xmtp-inbox.d.ts +22 -0
  23. package/dist/tools/xmtp-inbox.d.ts.map +1 -0
  24. package/dist/tools/xmtp-inbox.js +36 -0
  25. package/dist/tools/xmtp-inbox.js.map +1 -0
  26. package/dist/tools/xmtp-send.d.ts +28 -0
  27. package/dist/tools/xmtp-send.d.ts.map +1 -0
  28. package/dist/tools/xmtp-send.js +63 -0
  29. package/dist/tools/xmtp-send.js.map +1 -0
  30. package/dist/transport/identity-registry.d.ts +30 -0
  31. package/dist/transport/identity-registry.d.ts.map +1 -0
  32. package/dist/transport/identity-registry.js +117 -0
  33. package/dist/transport/identity-registry.js.map +1 -0
  34. package/dist/transport/xmtp-bridge.d.ts +55 -0
  35. package/dist/transport/xmtp-bridge.d.ts.map +1 -0
  36. package/dist/transport/xmtp-bridge.js +265 -0
  37. package/dist/transport/xmtp-bridge.js.map +1 -0
  38. package/dist/types.d.ts +100 -0
  39. package/dist/types.d.ts.map +1 -0
  40. package/dist/types.js +51 -0
  41. package/dist/types.js.map +1 -0
  42. package/package.json +27 -16
  43. package/src/identity-registry.ts +0 -134
  44. package/src/index.ts +0 -327
  45. package/src/policy-engine.ts +0 -121
  46. package/src/tools/xmtp-agents.ts +0 -38
  47. package/src/tools/xmtp-group.ts +0 -153
  48. package/src/tools/xmtp-inbox.ts +0 -47
  49. package/src/tools/xmtp-send.ts +0 -77
  50. package/src/types.ts +0 -155
  51. package/src/xmtp-bridge.ts +0 -283
@@ -1,77 +0,0 @@
1
- // ============================================================
2
- // Tool: xmtp_send — 发送 E2EE 消息到另一个 Agent
3
- // ============================================================
4
-
5
- import type { XmtpBridge } from "../xmtp-bridge.js";
6
- import type { IdentityRegistry } from "../identity-registry.js";
7
- import type { PolicyEngine } from "../policy-engine.js";
8
-
9
- export async function handleXmtpSend(
10
- bridges: Map<string, XmtpBridge>,
11
- registry: IdentityRegistry,
12
- policyEngine: PolicyEngine,
13
- params: { to?: string; message: string; conversationId?: string; contentType?: "text" | "markdown" },
14
- senderAgentId: string,
15
- ) {
16
- // 找到发送者 bridge(使用第一个可用的 bridge)
17
- const bridge = bridges.get(senderAgentId) ?? bridges.values().next().value;
18
- if (!bridge) {
19
- return { content: [{ type: "text" as const, text: "No XMTP bridge available. Plugin not initialized." }], details: null };
20
- }
21
-
22
- // 当提供 conversationId 时,to 可省略(直接向已有会话发送)
23
- if (!params.to && !params.conversationId) {
24
- return { content: [{ type: "text" as const, text: "Either 'to' or 'conversationId' must be provided." }], details: null };
25
- }
26
-
27
- // 解析目标地址(仅在提供 to 时需要)
28
- let targetAddress: string | undefined;
29
- if (params.to) {
30
- if (params.to.startsWith("0x")) {
31
- targetAddress = params.to;
32
- } else {
33
- const addr = registry.getAddress(params.to);
34
- if (!addr) {
35
- return {
36
- content: [{ type: "text" as const, text: `Agent "${params.to}" not found. Use xmtp_agents to discover available agents.` }],
37
- details: null,
38
- };
39
- }
40
- targetAddress = addr;
41
- }
42
-
43
- if (targetAddress.toLowerCase() === bridge.address.toLowerCase()) {
44
- return { content: [{ type: "text" as const, text: "Cannot send message to yourself." }], details: null };
45
- }
46
- }
47
-
48
- // Policy 检查
49
- const toLabel = params.to ?? params.conversationId!;
50
- const convId = params.conversationId ?? `dm:${senderAgentId}:${params.to}`;
51
- const check = policyEngine.checkOutgoing({ from: senderAgentId, to: toLabel, conversationId: convId });
52
- if (!check.allowed) {
53
- return { content: [{ type: "text" as const, text: `Blocked: ${check.reason}` }], details: null };
54
- }
55
-
56
- try {
57
- const result = await bridge.sendMessage(targetAddress ?? "", params.message, {
58
- contentType: params.contentType,
59
- conversationId: params.conversationId,
60
- });
61
-
62
- const recipientLabel = params.to ?? `conversation ${result.conversationId}`;
63
- return {
64
- content: [{ type: "text" as const, text: `Message sent to ${recipientLabel}.` }],
65
- details: {
66
- status: "sent",
67
- conversationId: result.conversationId,
68
- messageId: result.messageId,
69
- to: params.to,
70
- toAddress: targetAddress,
71
- },
72
- };
73
- } catch (err: unknown) {
74
- const msg = err instanceof Error ? err.message : String(err);
75
- return { content: [{ type: "text" as const, text: `Failed to send: ${msg}` }], details: null };
76
- }
77
- }
package/src/types.ts DELETED
@@ -1,155 +0,0 @@
1
- // ============================================================
2
- // Module 6: 类型定义 & 消息协议适配
3
- // ============================================================
4
-
5
- /** Agent 的 XMTP 钱包配置 */
6
- export interface XmtpWalletConfig {
7
- privateKey: string;
8
- address: string;
9
- xmtpInboxId: string;
10
- env: XmtpEnv;
11
- }
12
-
13
- export type XmtpEnv = "dev" | "production";
14
-
15
- /** 注入 Agent session 的消息格式 */
16
- export interface A2AInjectPayload {
17
- type: "a2a-xmtp";
18
- from: {
19
- agentId: string | null;
20
- xmtpAddress: string;
21
- displayName?: string;
22
- };
23
- conversation: {
24
- id: string;
25
- isGroup: boolean;
26
- participants: string[];
27
- };
28
- message: {
29
- id: string;
30
- content: string;
31
- contentType: A2AContentType;
32
- turn: number;
33
- depth: number;
34
- replyTo?: string;
35
- };
36
- timestamp: string;
37
- }
38
-
39
- export type A2AContentType = "text" | "markdown" | "transaction" | "action";
40
-
41
- /** 防循环策略配置 */
42
- export interface ConversationPolicy {
43
- maxTurns: number;
44
- maxDepth: number;
45
- minIntervalMs: number;
46
- ttlMinutes: number;
47
- consentMode: ConsentMode;
48
- }
49
-
50
- export type ConsentMode = "auto-allow-local" | "explicit-only";
51
- export type ConsentState = "allow" | "deny" | "unknown";
52
-
53
- /** 插件运行时配置(从 openclaw.json entries.a2a-xmtp.config 解析) */
54
- export interface PluginConfig {
55
- xmtp: {
56
- env: XmtpEnv;
57
- dbPath: string;
58
- };
59
- policy: ConversationPolicy;
60
- walletKey?: string;
61
- }
62
-
63
- /** 会话状态(Policy Engine 内部使用) */
64
- export interface ConversationState {
65
- turn: number;
66
- depth: number;
67
- lastSendTime: number;
68
- createdAt: number;
69
- }
70
-
71
- /** 收件箱消息 */
72
- export interface InboxMessage {
73
- id: string;
74
- from: {
75
- agentId: string | null;
76
- xmtpAddress: string;
77
- };
78
- conversationId: string;
79
- isGroup: boolean;
80
- content: string;
81
- contentType: A2AContentType;
82
- timestamp: string;
83
- }
84
-
85
- /** 群聊信息 */
86
- export interface GroupInfo {
87
- conversationId: string;
88
- name: string;
89
- memberAddresses: string[];
90
- createdAt: string;
91
- }
92
-
93
- /** 默认策略配置 */
94
- export const DEFAULT_POLICY: ConversationPolicy = {
95
- maxTurns: 10,
96
- maxDepth: 5,
97
- minIntervalMs: 5000,
98
- ttlMinutes: 60,
99
- consentMode: "auto-allow-local",
100
- };
101
-
102
- /** 默认插件配置 */
103
- export const DEFAULT_PLUGIN_CONFIG: PluginConfig = {
104
- xmtp: {
105
- env: "dev",
106
- dbPath: "./xmtp-data",
107
- },
108
- policy: DEFAULT_POLICY,
109
- };
110
-
111
- /** 格式化 A2A 消息(注入 session 时的文本表示) */
112
- export function formatA2AMessage(payload: A2AInjectPayload): string {
113
- const sender = payload.from.agentId || payload.from.xmtpAddress;
114
- const prefix = payload.conversation.isGroup ? "[A2A Group]" : "[A2A]";
115
- return `${prefix} from ${sender} (turn ${payload.message.turn}): ${payload.message.content}`;
116
- }
117
-
118
- /** 创建 A2AInjectPayload */
119
- export function createA2APayload(params: {
120
- fromAgentId: string | null;
121
- fromAddress: string;
122
- conversationId: string;
123
- isGroup: boolean;
124
- participants: string[];
125
- messageId: string;
126
- content: string;
127
- contentType: A2AContentType;
128
- turn: number;
129
- depth: number;
130
- replyTo?: string;
131
- displayName?: string;
132
- }): A2AInjectPayload {
133
- return {
134
- type: "a2a-xmtp",
135
- from: {
136
- agentId: params.fromAgentId,
137
- xmtpAddress: params.fromAddress,
138
- displayName: params.displayName,
139
- },
140
- conversation: {
141
- id: params.conversationId,
142
- isGroup: params.isGroup,
143
- participants: params.participants,
144
- },
145
- message: {
146
- id: params.messageId,
147
- content: params.content,
148
- contentType: params.contentType,
149
- turn: params.turn,
150
- depth: params.depth,
151
- replyTo: params.replyTo,
152
- },
153
- timestamp: new Date().toISOString(),
154
- };
155
- }
@@ -1,283 +0,0 @@
1
- // ============================================================
2
- // Module 2: XMTP Bridge Service
3
- // 每个 Agent 对应一个 XMTP Client,消息 stream + 收件箱缓存
4
- // ============================================================
5
-
6
- import { Agent, createUser, createSigner } from "@xmtp/agent-sdk";
7
- import type { IdentityRegistry } from "./identity-registry.js";
8
- import { PolicyEngine } from "./policy-engine.js";
9
- import type { XmtpWalletConfig, InboxMessage, A2AContentType, A2AInjectPayload, GroupInfo } from "./types.js";
10
- import { createA2APayload } from "./types.js";
11
-
12
- /** IdentifierKind.Ethereum = 0 (const enum, 不能在 isolatedModules 下直接访问) */
13
- const IDENTIFIER_KIND_ETHEREUM = 0;
14
-
15
- export class XmtpBridge {
16
- private agent: InstanceType<typeof Agent> | null = null;
17
- private running = false;
18
- private syncTimer: ReturnType<typeof setInterval> | null = null;
19
-
20
- /** 收件箱缓存 */
21
- private inboxBuffer: InboxMessage[] = [];
22
- private readonly maxInboxBuffer = 100;
23
-
24
- /** 消息回调(由 plugin 入口设置,用于触发 subagent 处理) */
25
- onMessage?: (agentId: string, payload: A2AInjectPayload) => void;
26
-
27
- constructor(
28
- readonly agentId: string,
29
- private walletConfig: XmtpWalletConfig,
30
- private policyEngine: PolicyEngine,
31
- private registry: IdentityRegistry,
32
- private dbPath: string,
33
- ) {}
34
-
35
- async start(): Promise<void> {
36
- if (this.running) return;
37
-
38
- // Reconstruct full user from stored key — createUser() accepts an existing private key
39
- const user = createUser(this.walletConfig.privateKey as `0x${string}`);
40
- const signer = createSigner(user);
41
-
42
- this.agent = await Agent.create(signer, {
43
- env: this.walletConfig.env,
44
- dbPath: `${this.dbPath}/${this.agentId}`,
45
- });
46
-
47
- await this.registry.updateInboxId(this.agentId, this.agent.address!);
48
-
49
- this.agent.on("text", async (ctx: any) => {
50
- await this.handleIncoming(ctx, "text");
51
- });
52
-
53
- this.agent.on("markdown", async (ctx: any) => {
54
- await this.handleIncoming(ctx, "markdown");
55
- });
56
-
57
- await this.agent.start();
58
- this.running = true;
59
-
60
- // 定期同步会话列表,发现被外部添加到的新群聊(每 30 秒)
61
- this.syncTimer = setInterval(async () => {
62
- try {
63
- await this.agent?.client.conversations.sync();
64
- } catch { /* 同步失败不影响正常运行 */ }
65
- }, 30_000);
66
- }
67
-
68
- async stop(): Promise<void> {
69
- if (!this.running || !this.agent) return;
70
- if (this.syncTimer) {
71
- clearInterval(this.syncTimer);
72
- this.syncTimer = null;
73
- }
74
- await this.agent.stop();
75
- this.running = false;
76
- }
77
-
78
- async sendMessage(
79
- toAddress: string,
80
- content: string,
81
- opts?: { contentType?: "text" | "markdown"; conversationId?: string },
82
- ): Promise<{ conversationId: string; messageId: string }> {
83
- if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
84
-
85
- let conversation: any;
86
- if (opts?.conversationId) {
87
- conversation = await this.agent.client.conversations.getConversationById(opts.conversationId);
88
- if (!conversation) throw new Error(`Conversation ${opts.conversationId} not found`);
89
- } else {
90
- conversation = await (this.agent as any).createDmWithAddress(toAddress);
91
- }
92
-
93
- const messageId = opts?.contentType === "markdown"
94
- ? await conversation.sendMarkdown(content)
95
- : await conversation.sendText(content);
96
-
97
- this.policyEngine.recordTurn(conversation.id);
98
-
99
- return { conversationId: conversation.id, messageId: String(messageId) };
100
- }
101
-
102
- async getInbox(opts?: { limit?: number; from?: string }): Promise<InboxMessage[]> {
103
- const limit = opts?.limit ?? 10;
104
- let messages = [...this.inboxBuffer];
105
- if (opts?.from) {
106
- const f = opts.from.toLowerCase();
107
- messages = messages.filter(
108
- (m) => m.from.agentId === opts.from || m.from.xmtpAddress.toLowerCase() === f,
109
- );
110
- }
111
- return messages.slice(0, limit);
112
- }
113
-
114
- async createGroup(memberAddresses: string[], name?: string): Promise<{ conversationId: string; name: string }> {
115
- if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
116
- const opts = name ? { groupName: name } : undefined;
117
- const group = await (this.agent as any).createGroupWithAddresses(memberAddresses, opts);
118
- if (name) {
119
- try { await group.updateName(name); } catch { /* 部分 SDK 版本 createGroupWithAddresses 不支持 opts */ }
120
- }
121
- return { conversationId: group.id, name: group.name ?? name ?? "" };
122
- }
123
-
124
- async listGroups(): Promise<GroupInfo[]> {
125
- if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
126
- const conversations = await this.agent.client.conversations.list();
127
- const groups: GroupInfo[] = [];
128
- for (const conv of conversations) {
129
- const meta = await conv.metadata();
130
- if (meta.conversationType !== 1) continue; // 1 = Group, 0 = Dm
131
- const members = await conv.members();
132
- groups.push({
133
- conversationId: conv.id,
134
- name: (conv as any).name ?? "",
135
- memberAddresses: members.map((m: any) =>
136
- m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId,
137
- ),
138
- createdAt: conv.createdAt.toISOString(),
139
- });
140
- }
141
- return groups;
142
- }
143
-
144
- async getGroupMembers(conversationId: string): Promise<string[]> {
145
- if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
146
- const conv = await this.agent.client.conversations.getConversationById(conversationId);
147
- if (!conv) throw new Error(`Conversation ${conversationId} not found`);
148
- const members = await conv.members();
149
- return members.map((m: any) => m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId);
150
- }
151
-
152
- async addGroupMembers(conversationId: string, addresses: string[]): Promise<void> {
153
- if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
154
- const conv = await this.agent.client.conversations.getConversationById(conversationId);
155
- if (!conv) throw new Error(`Conversation ${conversationId} not found`);
156
- const identifiers = addresses.map((addr) => ({
157
- identifier: addr,
158
- identifierKind: IDENTIFIER_KIND_ETHEREUM,
159
- }));
160
- await (conv as any).addMembersByIdentifiers(identifiers);
161
- }
162
-
163
- async removeGroupMembers(conversationId: string, addresses: string[]): Promise<void> {
164
- if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
165
- const conv = await this.agent.client.conversations.getConversationById(conversationId);
166
- if (!conv) throw new Error(`Conversation ${conversationId} not found`);
167
- const identifiers = addresses.map((addr) => ({
168
- identifier: addr,
169
- identifierKind: IDENTIFIER_KIND_ETHEREUM,
170
- }));
171
- await (conv as any).removeMembersByIdentifiers(identifiers);
172
- }
173
-
174
- /**
175
- * 检查群聊中在 afterTimestamp 之后是否有新消息(排除自身和指定 sender)
176
- * 用于防止多 agent 同时回复同一条消息
177
- */
178
- async hasNewGroupReplies(
179
- conversationId: string,
180
- afterTimestamp: string,
181
- excludeAddresses: string[],
182
- ): Promise<boolean> {
183
- if (!this.agent) return false;
184
- try {
185
- const conv = await this.agent.client.conversations.getConversationById(conversationId);
186
- if (!conv) return false;
187
- await conv.sync();
188
- const messages = await conv.messages({ limit: BigInt(10) as any });
189
- const cutoffMs = new Date(afterTimestamp).getTime();
190
- const excluded = new Set(excludeAddresses.map((a) => a.toLowerCase()));
191
- return messages.some((m: any) => {
192
- const sentMs = Number(BigInt(m.sentAtNs) / 1_000_000n);
193
- const sender = (m.senderInboxId ?? m.senderAddress ?? "").toLowerCase();
194
- return sentMs > cutoffMs && !excluded.has(sender);
195
- });
196
- } catch {
197
- return false;
198
- }
199
- }
200
-
201
- async canMessage(address: string): Promise<boolean> {
202
- if (!this.agent) return false;
203
- const result = await this.agent.client.canMessage([
204
- { identifier: address, identifierKind: IDENTIFIER_KIND_ETHEREUM },
205
- ]);
206
- return result.get(address.toLowerCase()) ?? false;
207
- }
208
-
209
- private async handleIncoming(ctx: any, contentType: A2AContentType): Promise<void> {
210
- const senderAddress: string = await ctx.getSenderAddress();
211
-
212
- if (senderAddress.toLowerCase() === this.address.toLowerCase()) return;
213
-
214
- const senderAgentId = await this.registry.resolveAgentId(senderAddress);
215
-
216
- const policyResult = this.policyEngine.checkIncoming({
217
- from: senderAgentId || senderAddress,
218
- to: this.agentId,
219
- conversationId: ctx.conversation.id,
220
- });
221
- if (!policyResult.allowed) return;
222
-
223
- const convState = this.policyEngine.getConversationState(ctx.conversation.id);
224
- const turn = convState?.turn ?? 0;
225
- const depth = convState?.depth ?? 0;
226
-
227
- this.policyEngine.recordTurn(ctx.conversation.id, depth + 1);
228
-
229
- // 群聊时获取参与者列表
230
- let participants: string[] = [];
231
- const isGroup = ctx.conversation.isGroup ?? false;
232
- if (isGroup) {
233
- try {
234
- const members = await ctx.conversation.members();
235
- participants = members.map((m: any) =>
236
- m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId,
237
- );
238
- } catch { /* 获取成员失败时保持空数组 */ }
239
- }
240
-
241
- const payload = createA2APayload({
242
- fromAgentId: senderAgentId,
243
- fromAddress: senderAddress,
244
- conversationId: ctx.conversation.id,
245
- isGroup,
246
- participants,
247
- messageId: ctx.message.id ?? crypto.randomUUID(),
248
- content: String(ctx.message.content),
249
- contentType,
250
- turn: turn + 1,
251
- depth: depth + 1,
252
- });
253
-
254
- // 缓存到 inbox
255
- this.inboxBuffer.unshift({
256
- id: payload.message.id,
257
- from: { agentId: senderAgentId, xmtpAddress: senderAddress },
258
- conversationId: ctx.conversation.id,
259
- isGroup,
260
- content: String(ctx.message.content),
261
- contentType,
262
- timestamp: payload.timestamp,
263
- });
264
- if (this.inboxBuffer.length > this.maxInboxBuffer) this.inboxBuffer.pop();
265
-
266
- // 通知 plugin 层(传递结构化 payload,由调用方决定如何处理)
267
- if (this.onMessage) {
268
- this.onMessage(this.agentId, payload);
269
- }
270
- }
271
-
272
- get address(): string {
273
- return this.agent?.address ?? this.walletConfig.address;
274
- }
275
-
276
- get isConnected(): boolean {
277
- return this.running;
278
- }
279
-
280
- get env() {
281
- return this.walletConfig.env;
282
- }
283
- }