a2a-xmtp 1.3.0 → 1.4.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 (54) hide show
  1. package/README.md +19 -11
  2. package/openclaw.plugin.json +9 -1
  3. package/package.json +4 -5
  4. package/src/coordination/group-scheduler.ts +192 -0
  5. package/src/coordination/message-orchestrator.ts +338 -0
  6. package/src/coordination/policy-engine.ts +120 -0
  7. package/src/index.ts +216 -0
  8. package/src/tools/xmtp-agents.ts +38 -0
  9. package/src/tools/xmtp-group.ts +153 -0
  10. package/src/tools/xmtp-inbox.ts +47 -0
  11. package/src/tools/xmtp-send.ts +77 -0
  12. package/src/transport/identity-registry.ts +133 -0
  13. package/src/transport/xmtp-bridge.ts +314 -0
  14. package/src/types.ts +181 -0
  15. package/dist/coordination/message-orchestrator.d.ts +0 -55
  16. package/dist/coordination/message-orchestrator.d.ts.map +0 -1
  17. package/dist/coordination/message-orchestrator.js +0 -142
  18. package/dist/coordination/message-orchestrator.js.map +0 -1
  19. package/dist/coordination/policy-engine.d.ts +0 -34
  20. package/dist/coordination/policy-engine.d.ts.map +0 -1
  21. package/dist/coordination/policy-engine.js +0 -92
  22. package/dist/coordination/policy-engine.js.map +0 -1
  23. package/dist/index.d.ts +0 -9
  24. package/dist/index.d.ts.map +0 -1
  25. package/dist/index.js +0 -163
  26. package/dist/index.js.map +0 -1
  27. package/dist/tools/xmtp-agents.d.ts +0 -19
  28. package/dist/tools/xmtp-agents.d.ts.map +0 -1
  29. package/dist/tools/xmtp-agents.js +0 -27
  30. package/dist/tools/xmtp-agents.js.map +0 -1
  31. package/dist/tools/xmtp-group.d.ts +0 -95
  32. package/dist/tools/xmtp-group.d.ts.map +0 -1
  33. package/dist/tools/xmtp-group.js +0 -134
  34. package/dist/tools/xmtp-group.js.map +0 -1
  35. package/dist/tools/xmtp-inbox.d.ts +0 -22
  36. package/dist/tools/xmtp-inbox.d.ts.map +0 -1
  37. package/dist/tools/xmtp-inbox.js +0 -36
  38. package/dist/tools/xmtp-inbox.js.map +0 -1
  39. package/dist/tools/xmtp-send.d.ts +0 -28
  40. package/dist/tools/xmtp-send.d.ts.map +0 -1
  41. package/dist/tools/xmtp-send.js +0 -63
  42. package/dist/tools/xmtp-send.js.map +0 -1
  43. package/dist/transport/identity-registry.d.ts +0 -30
  44. package/dist/transport/identity-registry.d.ts.map +0 -1
  45. package/dist/transport/identity-registry.js +0 -117
  46. package/dist/transport/identity-registry.js.map +0 -1
  47. package/dist/transport/xmtp-bridge.d.ts +0 -55
  48. package/dist/transport/xmtp-bridge.d.ts.map +0 -1
  49. package/dist/transport/xmtp-bridge.js +0 -265
  50. package/dist/transport/xmtp-bridge.js.map +0 -1
  51. package/dist/types.d.ts +0 -100
  52. package/dist/types.d.ts.map +0 -1
  53. package/dist/types.js +0 -51
  54. package/dist/types.js.map +0 -1
@@ -0,0 +1,133 @@
1
+ // ============================================================
2
+ // Module 3: Identity Registry
3
+ // agentId ↔ XMTP wallet address 双向映射,wallet key 生成/加载
4
+ // 使用文件系统持久化(stateDir)
5
+ // ============================================================
6
+
7
+ import { createUser, createSigner } from "@xmtp/agent-sdk";
8
+ import { privateKeyToAccount } from "viem/accounts";
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import type { XmtpWalletConfig, XmtpEnv } from "../types.js";
12
+
13
+ export class IdentityRegistry {
14
+ private agentToConfig = new Map<string, XmtpWalletConfig>();
15
+ private addressToAgent = new Map<string, string>();
16
+ private storePath: string;
17
+
18
+ constructor(
19
+ stateDir: string,
20
+ private env: XmtpEnv = "dev",
21
+ ) {
22
+ this.storePath = join(stateDir, "identities");
23
+ mkdirSync(this.storePath, { recursive: true });
24
+ this.loadFromDisk();
25
+ }
26
+
27
+ /**
28
+ * 为 Agent 初始化 XMTP wallet。
29
+ * 如果已有配置则加载,否则生成新的 wallet。
30
+ */
31
+ async initAgent(agentId: string, existingKey?: string): Promise<XmtpWalletConfig> {
32
+ const cached = this.agentToConfig.get(agentId);
33
+ if (cached) return cached;
34
+
35
+ // 尝试从磁盘加载
36
+ const filePath = join(this.storePath, `${agentId}.json`);
37
+ if (existsSync(filePath)) {
38
+ const config: XmtpWalletConfig = JSON.parse(readFileSync(filePath, "utf-8"));
39
+ this.cacheMapping(agentId, config);
40
+ return config;
41
+ }
42
+
43
+ // 生成新 wallet 或使用提供的 key
44
+ let privateKey: string;
45
+ let address: string;
46
+
47
+ if (existingKey) {
48
+ privateKey = existingKey;
49
+ const account = privateKeyToAccount(existingKey as `0x${string}`);
50
+ address = account.address;
51
+ } else {
52
+ const user = createUser();
53
+ privateKey = user.key; // createUser() returns { key, account, wallet }
54
+ address = user.account.address;
55
+ }
56
+
57
+ const config: XmtpWalletConfig = {
58
+ privateKey,
59
+ address,
60
+ xmtpInboxId: "",
61
+ env: this.env,
62
+ };
63
+
64
+ // 持久化到磁盘
65
+ writeFileSync(filePath, JSON.stringify(config, null, 2));
66
+ this.cacheMapping(agentId, config);
67
+
68
+ return config;
69
+ }
70
+
71
+ /** 更新 inboxId */
72
+ async updateInboxId(agentId: string, inboxId: string): Promise<void> {
73
+ const config = this.agentToConfig.get(agentId);
74
+ if (!config) return;
75
+ config.xmtpInboxId = inboxId;
76
+ const filePath = join(this.storePath, `${agentId}.json`);
77
+ writeFileSync(filePath, JSON.stringify(config, null, 2));
78
+ }
79
+
80
+ getAddress(agentId: string): string | undefined {
81
+ return this.agentToConfig.get(agentId)?.address;
82
+ }
83
+
84
+ getConfig(agentId: string): XmtpWalletConfig | undefined {
85
+ return this.agentToConfig.get(agentId);
86
+ }
87
+
88
+ getAgentId(address: string): string | undefined {
89
+ return this.addressToAgent.get(address.toLowerCase());
90
+ }
91
+
92
+ async resolveAgentId(address: string): Promise<string | null> {
93
+ return this.addressToAgent.get(address.toLowerCase()) ?? null;
94
+ }
95
+
96
+ listAgents(): Array<{ agentId: string; address: string; env: XmtpEnv }> {
97
+ return Array.from(this.agentToConfig.entries()).map(([id, config]) => ({
98
+ agentId: id,
99
+ address: config.address,
100
+ env: config.env,
101
+ }));
102
+ }
103
+
104
+ has(agentId: string): boolean {
105
+ return this.agentToConfig.has(agentId);
106
+ }
107
+
108
+ registerExternal(address: string, agentId: string): void {
109
+ this.addressToAgent.set(address.toLowerCase(), agentId);
110
+ }
111
+
112
+ private cacheMapping(agentId: string, config: XmtpWalletConfig): void {
113
+ this.agentToConfig.set(agentId, config);
114
+ this.addressToAgent.set(config.address.toLowerCase(), agentId);
115
+ }
116
+
117
+ /** 从磁盘加载所有已有 identity */
118
+ private loadFromDisk(): void {
119
+ if (!existsSync(this.storePath)) return;
120
+ for (const file of readdirSync(this.storePath)) {
121
+ if (!file.endsWith(".json")) continue;
122
+ const agentId = file.replace(".json", "");
123
+ try {
124
+ const config: XmtpWalletConfig = JSON.parse(
125
+ readFileSync(join(this.storePath, file), "utf-8"),
126
+ );
127
+ this.cacheMapping(agentId, config);
128
+ } catch {
129
+ // skip corrupt files
130
+ }
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,314 @@
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 "../coordination/policy-engine.js";
9
+ import type { XmtpWalletConfig, InboxMessage, A2AContentType, A2AInjectPayload, GroupInfo } from "../types.js";
10
+ import { createA2APayload, CLAIM_PREFIX } from "../types.js";
11
+ import { GroupScheduler } from "../coordination/group-scheduler.js";
12
+
13
+ /** IdentifierKind.Ethereum = 0 (const enum, 不能在 isolatedModules 下直接访问) */
14
+ const IDENTIFIER_KIND_ETHEREUM = 0;
15
+
16
+ export class XmtpBridge {
17
+ private agent: InstanceType<typeof Agent> | null = null;
18
+ private running = false;
19
+ private syncTimer: ReturnType<typeof setInterval> | null = null;
20
+
21
+ /** 收件箱缓存 */
22
+ private inboxBuffer: InboxMessage[] = [];
23
+ private readonly maxInboxBuffer = 100;
24
+
25
+ /** 消息回调(由 plugin 入口设置,用于触发 subagent 处理) */
26
+ onMessage?: (agentId: string, payload: A2AInjectPayload) => void;
27
+
28
+ /** Claim 消息回调(由 orchestrator 设置,用于群聊调度) */
29
+ onClaim?: (conversationId: string, senderAddress: string, messageId: string) => void;
30
+
31
+ constructor(
32
+ readonly agentId: string,
33
+ private walletConfig: XmtpWalletConfig,
34
+ private policyEngine: PolicyEngine,
35
+ private registry: IdentityRegistry,
36
+ private dbPath: string,
37
+ ) {}
38
+
39
+ async start(): Promise<void> {
40
+ if (this.running) return;
41
+
42
+ // Reconstruct full user from stored key — createUser() accepts an existing private key
43
+ const user = createUser(this.walletConfig.privateKey as `0x${string}`);
44
+ const signer = createSigner(user);
45
+
46
+ this.agent = await Agent.create(signer, {
47
+ env: this.walletConfig.env,
48
+ dbPath: `${this.dbPath}/${this.agentId}`,
49
+ });
50
+
51
+ await this.registry.updateInboxId(this.agentId, this.agent.address!);
52
+
53
+ this.agent.on("text", async (ctx: any) => {
54
+ await this.handleIncoming(ctx, "text");
55
+ });
56
+
57
+ this.agent.on("markdown", async (ctx: any) => {
58
+ await this.handleIncoming(ctx, "markdown");
59
+ });
60
+
61
+ await this.agent.start();
62
+ this.running = true;
63
+
64
+ // 定期同步会话列表,发现被外部添加到的新群聊(每 30 秒)
65
+ this.syncTimer = setInterval(async () => {
66
+ try {
67
+ await this.agent?.client.conversations.sync();
68
+ } catch { /* 同步失败不影响正常运行 */ }
69
+ }, 30_000);
70
+ }
71
+
72
+ async stop(): Promise<void> {
73
+ if (!this.running || !this.agent) return;
74
+ if (this.syncTimer) {
75
+ clearInterval(this.syncTimer);
76
+ this.syncTimer = null;
77
+ }
78
+ await this.agent.stop();
79
+ this.running = false;
80
+ }
81
+
82
+ async sendMessage(
83
+ toAddress: string,
84
+ content: string,
85
+ opts?: { contentType?: "text" | "markdown"; conversationId?: string },
86
+ ): Promise<{ conversationId: string; messageId: string }> {
87
+ if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
88
+
89
+ let conversation: any;
90
+ if (opts?.conversationId) {
91
+ conversation = await this.agent.client.conversations.getConversationById(opts.conversationId);
92
+ if (!conversation) throw new Error(`Conversation ${opts.conversationId} not found`);
93
+ } else {
94
+ conversation = await (this.agent as any).createDmWithAddress(toAddress);
95
+ }
96
+
97
+ const messageId = opts?.contentType === "markdown"
98
+ ? await conversation.sendMarkdown(content)
99
+ : await conversation.sendText(content);
100
+
101
+ this.policyEngine.recordTurn(conversation.id);
102
+
103
+ return { conversationId: conversation.id, messageId: String(messageId) };
104
+ }
105
+
106
+ /**
107
+ * 发送 claim 消息到群聊,表明本 agent 将要回复。
108
+ */
109
+ async sendClaim(conversationId: string, messageId: string): Promise<void> {
110
+ if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
111
+ const conv = await this.agent.client.conversations.getConversationById(conversationId);
112
+ if (!conv) throw new Error(`Conversation ${conversationId} not found`);
113
+ const claimText = GroupScheduler.formatClaimMessage(messageId);
114
+ await conv.sendText(claimText);
115
+ }
116
+
117
+ async getInbox(opts?: { limit?: number; from?: string }): Promise<InboxMessage[]> {
118
+ const limit = opts?.limit ?? 10;
119
+ let messages = [...this.inboxBuffer];
120
+ if (opts?.from) {
121
+ const f = opts.from.toLowerCase();
122
+ messages = messages.filter(
123
+ (m) => m.from.agentId === opts.from || m.from.xmtpAddress.toLowerCase() === f,
124
+ );
125
+ }
126
+ return messages.slice(0, limit);
127
+ }
128
+
129
+ /**
130
+ * 获取指定会话的最近消息(供 coordination 层查询)
131
+ */
132
+ getRecentMessages(conversationId: string, limit: number): InboxMessage[] {
133
+ return this.inboxBuffer
134
+ .filter((m) => m.conversationId === conversationId)
135
+ .slice(0, limit);
136
+ }
137
+
138
+ async createGroup(memberAddresses: string[], name?: string): Promise<{ conversationId: string; name: string }> {
139
+ if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
140
+ const opts = name ? { groupName: name } : undefined;
141
+ const group = await (this.agent as any).createGroupWithAddresses(memberAddresses, opts);
142
+ if (name) {
143
+ try { await group.updateName(name); } catch { /* 部分 SDK 版本 createGroupWithAddresses 不支持 opts */ }
144
+ }
145
+ return { conversationId: group.id, name: group.name ?? name ?? "" };
146
+ }
147
+
148
+ async listGroups(): Promise<GroupInfo[]> {
149
+ if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
150
+ const conversations = await this.agent.client.conversations.list();
151
+ const groups: GroupInfo[] = [];
152
+ for (const conv of conversations) {
153
+ const meta = await conv.metadata();
154
+ if (meta.conversationType !== 1) continue; // 1 = Group, 0 = Dm
155
+ const members = await conv.members();
156
+ groups.push({
157
+ conversationId: conv.id,
158
+ name: (conv as any).name ?? "",
159
+ memberAddresses: members.map((m: any) =>
160
+ m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId,
161
+ ),
162
+ createdAt: conv.createdAt.toISOString(),
163
+ });
164
+ }
165
+ return groups;
166
+ }
167
+
168
+ async getGroupMembers(conversationId: string): Promise<string[]> {
169
+ if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
170
+ const conv = await this.agent.client.conversations.getConversationById(conversationId);
171
+ if (!conv) throw new Error(`Conversation ${conversationId} not found`);
172
+ const members = await conv.members();
173
+ return members.map((m: any) => m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId);
174
+ }
175
+
176
+ async addGroupMembers(conversationId: string, addresses: string[]): Promise<void> {
177
+ if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
178
+ const conv = await this.agent.client.conversations.getConversationById(conversationId);
179
+ if (!conv) throw new Error(`Conversation ${conversationId} not found`);
180
+ const identifiers = addresses.map((addr) => ({
181
+ identifier: addr,
182
+ identifierKind: IDENTIFIER_KIND_ETHEREUM,
183
+ }));
184
+ await (conv as any).addMembersByIdentifiers(identifiers);
185
+ }
186
+
187
+ async removeGroupMembers(conversationId: string, addresses: string[]): Promise<void> {
188
+ if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
189
+ const conv = await this.agent.client.conversations.getConversationById(conversationId);
190
+ if (!conv) throw new Error(`Conversation ${conversationId} not found`);
191
+ const identifiers = addresses.map((addr) => ({
192
+ identifier: addr,
193
+ identifierKind: IDENTIFIER_KIND_ETHEREUM,
194
+ }));
195
+ await (conv as any).removeMembersByIdentifiers(identifiers);
196
+ }
197
+
198
+ /**
199
+ * 检查群聊中在 afterTimestamp 之后是否有新消息(排除自身和指定 sender)
200
+ * 用于防止多 agent 同时回复同一条消息
201
+ */
202
+ async hasNewGroupReplies(
203
+ conversationId: string,
204
+ afterTimestamp: string,
205
+ excludeAddresses: string[],
206
+ ): Promise<boolean> {
207
+ if (!this.agent) return false;
208
+ try {
209
+ const conv = await this.agent.client.conversations.getConversationById(conversationId);
210
+ if (!conv) return false;
211
+ await conv.sync();
212
+ const messages = await conv.messages({ limit: BigInt(10) as any });
213
+ const cutoffMs = new Date(afterTimestamp).getTime();
214
+ const excluded = new Set(excludeAddresses.map((a) => a.toLowerCase()));
215
+ return messages.some((m: any) => {
216
+ const sentMs = Number(BigInt(m.sentAtNs) / 1_000_000n);
217
+ const sender = (m.senderInboxId ?? m.senderAddress ?? "").toLowerCase();
218
+ return sentMs > cutoffMs && !excluded.has(sender);
219
+ });
220
+ } catch {
221
+ return false;
222
+ }
223
+ }
224
+
225
+ async canMessage(address: string): Promise<boolean> {
226
+ if (!this.agent) return false;
227
+ const result = await this.agent.client.canMessage([
228
+ { identifier: address, identifierKind: IDENTIFIER_KIND_ETHEREUM },
229
+ ]);
230
+ return result.get(address.toLowerCase()) ?? false;
231
+ }
232
+
233
+ private async handleIncoming(ctx: any, contentType: A2AContentType): Promise<void> {
234
+ const senderAddress: string = await ctx.getSenderAddress();
235
+
236
+ if (senderAddress.toLowerCase() === this.address.toLowerCase()) return;
237
+
238
+ const content = String(ctx.message.content);
239
+
240
+ // 识别 claim 消息:不计数、不缓存、通知调度器
241
+ if (GroupScheduler.isClaimMessage(content)) {
242
+ const claimedMsgId = GroupScheduler.parseClaimMessageId(content);
243
+ if (this.onClaim && claimedMsgId) {
244
+ this.onClaim(ctx.conversation.id, senderAddress, claimedMsgId);
245
+ }
246
+ return;
247
+ }
248
+
249
+ const senderAgentId = await this.registry.resolveAgentId(senderAddress);
250
+
251
+ const policyResult = this.policyEngine.checkIncoming({
252
+ from: senderAgentId || senderAddress,
253
+ to: this.agentId,
254
+ conversationId: ctx.conversation.id,
255
+ });
256
+ if (!policyResult.allowed) return;
257
+
258
+ const convState = this.policyEngine.getConversationState(ctx.conversation.id);
259
+ const turn = convState?.turn ?? 0;
260
+
261
+ // 群聊时获取参与者列表
262
+ let participants: string[] = [];
263
+ const isGroup = ctx.conversation.isGroup ?? false;
264
+ if (isGroup) {
265
+ try {
266
+ const members = await ctx.conversation.members();
267
+ participants = members.map((m: any) =>
268
+ m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId,
269
+ );
270
+ } catch { /* 获取成员失败时保持空数组 */ }
271
+ }
272
+
273
+ const payload = createA2APayload({
274
+ fromAgentId: senderAgentId,
275
+ fromAddress: senderAddress,
276
+ conversationId: ctx.conversation.id,
277
+ isGroup,
278
+ participants,
279
+ messageId: ctx.message.id ?? crypto.randomUUID(),
280
+ content,
281
+ contentType,
282
+ turn: turn + 1,
283
+ });
284
+
285
+ // 缓存到 inbox
286
+ this.inboxBuffer.unshift({
287
+ id: payload.message.id,
288
+ from: { agentId: senderAgentId, xmtpAddress: senderAddress },
289
+ conversationId: ctx.conversation.id,
290
+ isGroup,
291
+ content,
292
+ contentType,
293
+ timestamp: payload.timestamp,
294
+ });
295
+ if (this.inboxBuffer.length > this.maxInboxBuffer) this.inboxBuffer.pop();
296
+
297
+ // 通知 plugin 层(传递结构化 payload,由调用方决定如何处理)
298
+ if (this.onMessage) {
299
+ this.onMessage(this.agentId, payload);
300
+ }
301
+ }
302
+
303
+ get address(): string {
304
+ return this.agent?.address ?? this.walletConfig.address;
305
+ }
306
+
307
+ get isConnected(): boolean {
308
+ return this.running;
309
+ }
310
+
311
+ get env() {
312
+ return this.walletConfig.env;
313
+ }
314
+ }
package/src/types.ts ADDED
@@ -0,0 +1,181 @@
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
+ replyTo?: string;
34
+ };
35
+ timestamp: string;
36
+ }
37
+
38
+ export type A2AContentType = "text" | "markdown" | "transaction" | "action";
39
+
40
+ /** 防循环策略配置 */
41
+ export interface ConversationPolicy {
42
+ maxTurns: number;
43
+ minIntervalMs: number;
44
+ ttlMinutes: number;
45
+ consentMode: ConsentMode;
46
+ }
47
+
48
+ /** 群聊调度配置 */
49
+ export interface GroupSchedulingConfig {
50
+ /** 指定回复者发送 claim 前的基础延迟。Default: 1000ms */
51
+ baseDelayMs: number;
52
+ /** 等待前一个 slot 发送 claim 的超时时间。Default: 6000ms */
53
+ slotTimeoutMs: number;
54
+ /** 看到 claim 后等待实际回复的最大时间。Default: 30000ms */
55
+ claimExpireMs: number;
56
+ }
57
+
58
+ export type ConsentMode = "auto-allow-local" | "explicit-only";
59
+ export type ConsentState = "allow" | "deny" | "unknown";
60
+
61
+ /** 插件运行时配置(从 openclaw.json entries.a2a-xmtp.config 解析) */
62
+ export interface PluginConfig {
63
+ xmtp: {
64
+ env: XmtpEnv;
65
+ dbPath: string;
66
+ };
67
+ policy: ConversationPolicy;
68
+ groupScheduling: GroupSchedulingConfig;
69
+ walletKey?: string;
70
+ }
71
+
72
+ /** 会话状态(Policy Engine 内部使用) */
73
+ export interface ConversationState {
74
+ turn: number;
75
+ lastSendTime: number;
76
+ createdAt: number;
77
+ }
78
+
79
+ /** 群聊调度状态(per-conversation) */
80
+ export interface GroupConversationState {
81
+ /** 固定发言顺序(首次计算后缓存) */
82
+ speakingOrder: string[];
83
+ /** 已收到的非 claim 消息计数 */
84
+ messageCount: number;
85
+ /** 最近一次 claim 信息 */
86
+ lastClaim: { sender: string; timestamp: number; messageId: string } | null;
87
+ }
88
+
89
+ /** Claim 消息前缀(零宽空格 + [processing]:) */
90
+ export const CLAIM_PREFIX = "\u200B[processing]:";
91
+
92
+ /** 默认群聊调度配置 */
93
+ export const DEFAULT_GROUP_SCHEDULING: GroupSchedulingConfig = {
94
+ baseDelayMs: 1000,
95
+ slotTimeoutMs: 6000,
96
+ claimExpireMs: 30000,
97
+ };
98
+
99
+ /** 收件箱消息 */
100
+ export interface InboxMessage {
101
+ id: string;
102
+ from: {
103
+ agentId: string | null;
104
+ xmtpAddress: string;
105
+ };
106
+ conversationId: string;
107
+ isGroup: boolean;
108
+ content: string;
109
+ contentType: A2AContentType;
110
+ timestamp: string;
111
+ }
112
+
113
+ /** 群聊信息 */
114
+ export interface GroupInfo {
115
+ conversationId: string;
116
+ name: string;
117
+ memberAddresses: string[];
118
+ createdAt: string;
119
+ }
120
+
121
+ /** 默认策略配置 */
122
+ export const DEFAULT_POLICY: ConversationPolicy = {
123
+ maxTurns: 10,
124
+ minIntervalMs: 5000,
125
+ ttlMinutes: 60,
126
+ consentMode: "auto-allow-local",
127
+ };
128
+
129
+ /** 默认插件配置 */
130
+ export const DEFAULT_PLUGIN_CONFIG: PluginConfig = {
131
+ xmtp: {
132
+ env: "dev",
133
+ dbPath: "./xmtp-data",
134
+ },
135
+ policy: DEFAULT_POLICY,
136
+ groupScheduling: DEFAULT_GROUP_SCHEDULING,
137
+ };
138
+
139
+ /** 格式化 A2A 消息(注入 session 时的文本表示) */
140
+ export function formatA2AMessage(payload: A2AInjectPayload): string {
141
+ const sender = payload.from.agentId || payload.from.xmtpAddress;
142
+ const prefix = payload.conversation.isGroup ? "[A2A Group]" : "[A2A]";
143
+ return `${prefix} from ${sender} (turn ${payload.message.turn}): ${payload.message.content}`;
144
+ }
145
+
146
+ /** 创建 A2AInjectPayload */
147
+ export function createA2APayload(params: {
148
+ fromAgentId: string | null;
149
+ fromAddress: string;
150
+ conversationId: string;
151
+ isGroup: boolean;
152
+ participants: string[];
153
+ messageId: string;
154
+ content: string;
155
+ contentType: A2AContentType;
156
+ turn: number;
157
+ replyTo?: string;
158
+ displayName?: string;
159
+ }): A2AInjectPayload {
160
+ return {
161
+ type: "a2a-xmtp",
162
+ from: {
163
+ agentId: params.fromAgentId,
164
+ xmtpAddress: params.fromAddress,
165
+ displayName: params.displayName,
166
+ },
167
+ conversation: {
168
+ id: params.conversationId,
169
+ isGroup: params.isGroup,
170
+ participants: params.participants,
171
+ },
172
+ message: {
173
+ id: params.messageId,
174
+ content: params.content,
175
+ contentType: params.contentType,
176
+ turn: params.turn,
177
+ replyTo: params.replyTo,
178
+ },
179
+ timestamp: new Date().toISOString(),
180
+ };
181
+ }
@@ -1,55 +0,0 @@
1
- import type { XmtpBridge } from "../transport/xmtp-bridge.js";
2
- import { type A2AInjectPayload } from "../types.js";
3
- /** OpenClaw subagent runtime 接口(由 api.runtime.subagent 提供) */
4
- export interface SubagentAPI {
5
- run(params: {
6
- sessionKey: string;
7
- message: string;
8
- extraSystemPrompt: string;
9
- deliver: boolean;
10
- idempotencyKey: string;
11
- }): Promise<{
12
- runId: string;
13
- }>;
14
- waitForRun(params: {
15
- runId: string;
16
- timeoutMs: number;
17
- }): Promise<{
18
- status: "ok" | "error" | "timeout";
19
- error?: string;
20
- }>;
21
- getSessionMessages(params: {
22
- sessionKey: string;
23
- limit: number;
24
- }): Promise<{
25
- messages: any[];
26
- }>;
27
- }
28
- /** 日志接口 */
29
- export interface Logger {
30
- info(msg: string): void;
31
- warn(msg: string): void;
32
- error(msg: string): void;
33
- }
34
- export declare class MessageOrchestrator {
35
- private subagentApi;
36
- private logger;
37
- constructor(subagentApi: SubagentAPI, logger: Logger);
38
- /**
39
- * 处理收到的 XMTP 消息:群聊防抢答 → LLM 推理 → 安全校验 → 回复
40
- */
41
- handleMessage(bridge: XmtpBridge, agentId: string, payload: A2AInjectPayload): Promise<void>;
42
- /**
43
- * 构建安全约束的系统提示词
44
- */
45
- private buildSystemPrompt;
46
- /**
47
- * 检查 LLM 回复中是否有禁止的工具调用
48
- */
49
- private hasForbiddenToolCalls;
50
- /**
51
- * 从 LLM 回复中提取文本内容
52
- */
53
- private extractReplyText;
54
- }
55
- //# sourceMappingURL=message-orchestrator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"message-orchestrator.d.ts","sourceRoot":"","sources":["../../src/coordination/message-orchestrator.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEtE,8DAA8D;AAC9D,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,MAAM,EAAE;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;KACxB,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAE/B,UAAU,CAAC,MAAM,EAAE;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAEpE,kBAAkB,CAAC,MAAM,EAAE;QACzB,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,GAAG,EAAE,CAAA;KAAE,CAAC,CAAC;CAClC;AAED,WAAW;AACX,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAKD,qBAAa,mBAAmB;IAE5B,OAAO,CAAC,WAAW;IACnB,OAAO,CAAC,MAAM;gBADN,WAAW,EAAE,WAAW,EACxB,MAAM,EAAE,MAAM;IAGxB;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,IAAI,CAAC;IAyFhB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA0BzB;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAS7B;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAsBzB"}