a2a-xmtp 1.4.5 → 2.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/index.d.ts +11 -0
- package/dist/index.js +13 -0
- package/package.json +16 -10
- package/src/coordination/group-scheduler.ts +0 -193
- package/src/coordination/message-orchestrator.ts +0 -383
- package/src/coordination/policy-engine.ts +0 -134
- package/src/index.ts +0 -230
- package/src/tools/xmtp-agents.ts +0 -38
- package/src/tools/xmtp-group.ts +0 -153
- package/src/tools/xmtp-inbox.ts +0 -47
- package/src/tools/xmtp-send.ts +0 -77
- package/src/transport/identity-registry.ts +0 -133
- package/src/transport/xmtp-bridge.ts +0 -330
- package/src/types.ts +0 -192
|
@@ -1,330 +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 "../coordination/policy-engine.js";
|
|
9
|
-
import type { XmtpWalletConfig, InboxMessage, A2AContentType, A2AInjectPayload, GroupInfo } from "../types.js";
|
|
10
|
-
import { createA2APayload, CLAIM_PREFIX, type GroupParticipant } 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
|
-
/** 群聊 self 消息计数回调(由 orchestrator 设置,用于 round-robin 同步) */
|
|
32
|
-
onSelfGroupMessage?: (conversationId: string) => void;
|
|
33
|
-
|
|
34
|
-
constructor(
|
|
35
|
-
readonly agentId: string,
|
|
36
|
-
private walletConfig: XmtpWalletConfig,
|
|
37
|
-
private policyEngine: PolicyEngine,
|
|
38
|
-
private registry: IdentityRegistry,
|
|
39
|
-
private dbPath: string,
|
|
40
|
-
) {}
|
|
41
|
-
|
|
42
|
-
async start(): Promise<void> {
|
|
43
|
-
if (this.running) return;
|
|
44
|
-
|
|
45
|
-
// Reconstruct full user from stored key — createUser() accepts an existing private key
|
|
46
|
-
const user = createUser(this.walletConfig.privateKey as `0x${string}`);
|
|
47
|
-
const signer = createSigner(user);
|
|
48
|
-
|
|
49
|
-
this.agent = await Agent.create(signer, {
|
|
50
|
-
env: this.walletConfig.env,
|
|
51
|
-
dbPath: `${this.dbPath}/${this.agentId}`,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await this.registry.updateInboxId(this.agentId, this.agent.address!);
|
|
55
|
-
|
|
56
|
-
this.agent.on("text", async (ctx: any) => {
|
|
57
|
-
await this.handleIncoming(ctx, "text");
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
this.agent.on("markdown", async (ctx: any) => {
|
|
61
|
-
await this.handleIncoming(ctx, "markdown");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
await this.agent.start();
|
|
65
|
-
this.running = true;
|
|
66
|
-
|
|
67
|
-
// 定期同步会话列表,发现被外部添加到的新群聊(每 30 秒)
|
|
68
|
-
this.syncTimer = setInterval(async () => {
|
|
69
|
-
try {
|
|
70
|
-
await this.agent?.client.conversations.sync();
|
|
71
|
-
} catch { /* 同步失败不影响正常运行 */ }
|
|
72
|
-
}, 30_000);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async stop(): Promise<void> {
|
|
76
|
-
if (!this.running || !this.agent) return;
|
|
77
|
-
if (this.syncTimer) {
|
|
78
|
-
clearInterval(this.syncTimer);
|
|
79
|
-
this.syncTimer = null;
|
|
80
|
-
}
|
|
81
|
-
await this.agent.stop();
|
|
82
|
-
this.running = false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async sendMessage(
|
|
86
|
-
toAddress: string,
|
|
87
|
-
content: string,
|
|
88
|
-
opts?: { contentType?: "text" | "markdown"; conversationId?: string },
|
|
89
|
-
): Promise<{ conversationId: string; messageId: string }> {
|
|
90
|
-
if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
|
|
91
|
-
|
|
92
|
-
let conversation: any;
|
|
93
|
-
if (opts?.conversationId) {
|
|
94
|
-
conversation = await this.agent.client.conversations.getConversationById(opts.conversationId);
|
|
95
|
-
if (!conversation) throw new Error(`Conversation ${opts.conversationId} not found`);
|
|
96
|
-
} else {
|
|
97
|
-
conversation = await (this.agent as any).createDmWithAddress(toAddress);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const messageId = opts?.contentType === "markdown"
|
|
101
|
-
? await conversation.sendMarkdown(content)
|
|
102
|
-
: await conversation.sendText(content);
|
|
103
|
-
|
|
104
|
-
this.policyEngine.recordTurn(conversation.id);
|
|
105
|
-
|
|
106
|
-
return { conversationId: conversation.id, messageId: String(messageId) };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 发送 claim 消息到群聊,表明本 agent 将要回复。
|
|
111
|
-
*/
|
|
112
|
-
async sendClaim(conversationId: string, messageId: string): Promise<string> {
|
|
113
|
-
if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
|
|
114
|
-
const conv = await this.agent.client.conversations.getConversationById(conversationId);
|
|
115
|
-
if (!conv) throw new Error(`Conversation ${conversationId} not found`);
|
|
116
|
-
const claimText = GroupScheduler.formatClaimMessage(messageId);
|
|
117
|
-
const claimMsgId = await conv.sendText(claimText);
|
|
118
|
-
return String(claimMsgId);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async getInbox(opts?: { limit?: number; from?: string }): Promise<InboxMessage[]> {
|
|
122
|
-
const limit = opts?.limit ?? 10;
|
|
123
|
-
let messages = [...this.inboxBuffer];
|
|
124
|
-
if (opts?.from) {
|
|
125
|
-
const f = opts.from.toLowerCase();
|
|
126
|
-
messages = messages.filter(
|
|
127
|
-
(m) => m.from.agentId === opts.from || m.from.xmtpAddress.toLowerCase() === f,
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
return messages.slice(0, limit);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* 获取指定会话的最近消息(供 coordination 层查询)
|
|
135
|
-
*/
|
|
136
|
-
getRecentMessages(conversationId: string, limit: number): InboxMessage[] {
|
|
137
|
-
return this.inboxBuffer
|
|
138
|
-
.filter((m) => m.conversationId === conversationId)
|
|
139
|
-
.slice(0, limit);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async createGroup(memberAddresses: string[], name?: string): Promise<{ conversationId: string; name: string }> {
|
|
143
|
-
if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
|
|
144
|
-
const opts = name ? { groupName: name } : undefined;
|
|
145
|
-
const group = await (this.agent as any).createGroupWithAddresses(memberAddresses, opts);
|
|
146
|
-
if (name) {
|
|
147
|
-
try { await group.updateName(name); } catch { /* 部分 SDK 版本 createGroupWithAddresses 不支持 opts */ }
|
|
148
|
-
}
|
|
149
|
-
return { conversationId: group.id, name: group.name ?? name ?? "" };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async listGroups(): Promise<GroupInfo[]> {
|
|
153
|
-
if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
|
|
154
|
-
const conversations = await this.agent.client.conversations.list();
|
|
155
|
-
const groups: GroupInfo[] = [];
|
|
156
|
-
for (const conv of conversations) {
|
|
157
|
-
const meta = await conv.metadata();
|
|
158
|
-
if (meta.conversationType !== 1) continue; // 1 = Group, 0 = Dm
|
|
159
|
-
const members = await conv.members();
|
|
160
|
-
groups.push({
|
|
161
|
-
conversationId: conv.id,
|
|
162
|
-
name: (conv as any).name ?? "",
|
|
163
|
-
memberAddresses: members.map((m: any) =>
|
|
164
|
-
m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId,
|
|
165
|
-
),
|
|
166
|
-
createdAt: conv.createdAt.toISOString(),
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
return groups;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async getGroupMembers(conversationId: string): Promise<string[]> {
|
|
173
|
-
if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
|
|
174
|
-
const conv = await this.agent.client.conversations.getConversationById(conversationId);
|
|
175
|
-
if (!conv) throw new Error(`Conversation ${conversationId} not found`);
|
|
176
|
-
const members = await conv.members();
|
|
177
|
-
return members.map((m: any) => m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async addGroupMembers(conversationId: string, addresses: string[]): Promise<void> {
|
|
181
|
-
if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
|
|
182
|
-
const conv = await this.agent.client.conversations.getConversationById(conversationId);
|
|
183
|
-
if (!conv) throw new Error(`Conversation ${conversationId} not found`);
|
|
184
|
-
const identifiers = addresses.map((addr) => ({
|
|
185
|
-
identifier: addr,
|
|
186
|
-
identifierKind: IDENTIFIER_KIND_ETHEREUM,
|
|
187
|
-
}));
|
|
188
|
-
await (conv as any).addMembersByIdentifiers(identifiers);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async removeGroupMembers(conversationId: string, addresses: string[]): Promise<void> {
|
|
192
|
-
if (!this.agent) throw new Error(`Bridge for ${this.agentId} not started`);
|
|
193
|
-
const conv = await this.agent.client.conversations.getConversationById(conversationId);
|
|
194
|
-
if (!conv) throw new Error(`Conversation ${conversationId} not found`);
|
|
195
|
-
const identifiers = addresses.map((addr) => ({
|
|
196
|
-
identifier: addr,
|
|
197
|
-
identifierKind: IDENTIFIER_KIND_ETHEREUM,
|
|
198
|
-
}));
|
|
199
|
-
await (conv as any).removeMembersByIdentifiers(identifiers);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* 检查群聊中在 afterTimestamp 之后是否有新消息(排除自身和指定 sender)
|
|
204
|
-
* 用于防止多 agent 同时回复同一条消息
|
|
205
|
-
*/
|
|
206
|
-
async hasNewGroupReplies(
|
|
207
|
-
conversationId: string,
|
|
208
|
-
afterTimestamp: string,
|
|
209
|
-
excludeAddresses: string[],
|
|
210
|
-
): Promise<boolean> {
|
|
211
|
-
if (!this.agent) return false;
|
|
212
|
-
try {
|
|
213
|
-
const conv = await this.agent.client.conversations.getConversationById(conversationId);
|
|
214
|
-
if (!conv) return false;
|
|
215
|
-
await conv.sync();
|
|
216
|
-
const messages = await conv.messages({ limit: BigInt(10) as any });
|
|
217
|
-
const cutoffMs = new Date(afterTimestamp).getTime();
|
|
218
|
-
const excluded = new Set(excludeAddresses.map((a) => a.toLowerCase()));
|
|
219
|
-
return messages.some((m: any) => {
|
|
220
|
-
const sentMs = Number(BigInt(m.sentAtNs) / 1_000_000n);
|
|
221
|
-
const sender = (m.senderInboxId ?? m.senderAddress ?? "").toLowerCase();
|
|
222
|
-
return sentMs > cutoffMs && !excluded.has(sender);
|
|
223
|
-
});
|
|
224
|
-
} catch {
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
async canMessage(address: string): Promise<boolean> {
|
|
230
|
-
if (!this.agent) return false;
|
|
231
|
-
const result = await this.agent.client.canMessage([
|
|
232
|
-
{ identifier: address, identifierKind: IDENTIFIER_KIND_ETHEREUM },
|
|
233
|
-
]);
|
|
234
|
-
return result.get(address.toLowerCase()) ?? false;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
private async handleIncoming(ctx: any, contentType: A2AContentType): Promise<void> {
|
|
238
|
-
const senderAddress: string = await ctx.getSenderAddress();
|
|
239
|
-
const isSelf = senderAddress.toLowerCase() === this.address.toLowerCase();
|
|
240
|
-
const content = String(ctx.message.content);
|
|
241
|
-
|
|
242
|
-
// 识别 claim 消息:不计数、不缓存、通知调度器
|
|
243
|
-
if (GroupScheduler.isClaimMessage(content)) {
|
|
244
|
-
const claimedMsgId = GroupScheduler.parseClaimMessageId(content);
|
|
245
|
-
if (this.onClaim && claimedMsgId) {
|
|
246
|
-
this.onClaim(ctx.conversation.id, senderAddress, claimedMsgId);
|
|
247
|
-
}
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// 群聊 self 消息:仅计数(保持 messageCount 同步),不触发响应
|
|
252
|
-
const isGroup = ctx.conversation.isGroup ?? false;
|
|
253
|
-
if (isSelf) {
|
|
254
|
-
if (isGroup && this.onSelfGroupMessage) {
|
|
255
|
-
this.onSelfGroupMessage(ctx.conversation.id);
|
|
256
|
-
}
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const senderAgentId = await this.registry.resolveAgentId(senderAddress);
|
|
261
|
-
|
|
262
|
-
const policyResult = this.policyEngine.checkIncoming({
|
|
263
|
-
from: senderAgentId || senderAddress,
|
|
264
|
-
to: this.agentId,
|
|
265
|
-
conversationId: ctx.conversation.id,
|
|
266
|
-
});
|
|
267
|
-
if (!policyResult.allowed) return;
|
|
268
|
-
|
|
269
|
-
const convState = this.policyEngine.getConversationState(ctx.conversation.id);
|
|
270
|
-
const turn = convState?.turn ?? 0;
|
|
271
|
-
|
|
272
|
-
// 群聊时获取参与者列表(含角色信息)
|
|
273
|
-
let participants: string[] = [];
|
|
274
|
-
let participantDetails: GroupParticipant[] | undefined;
|
|
275
|
-
if (isGroup) {
|
|
276
|
-
try {
|
|
277
|
-
const members = await ctx.conversation.members();
|
|
278
|
-
participants = members.map((m: any) =>
|
|
279
|
-
m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId,
|
|
280
|
-
);
|
|
281
|
-
participantDetails = members.map((m: any) => ({
|
|
282
|
-
address: (m.accountAddresses?.[0]?.toLowerCase() ?? m.inboxId) as string,
|
|
283
|
-
permissionLevel: (m.permissionLevel ?? 0) as number,
|
|
284
|
-
}));
|
|
285
|
-
} catch { /* 获取成员失败时保持空数组 */ }
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const payload = createA2APayload({
|
|
289
|
-
fromAgentId: senderAgentId,
|
|
290
|
-
fromAddress: senderAddress,
|
|
291
|
-
conversationId: ctx.conversation.id,
|
|
292
|
-
isGroup,
|
|
293
|
-
participants,
|
|
294
|
-
participantDetails,
|
|
295
|
-
messageId: ctx.message.id ?? crypto.randomUUID(),
|
|
296
|
-
content,
|
|
297
|
-
contentType,
|
|
298
|
-
turn: turn + 1,
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
// 缓存到 inbox
|
|
302
|
-
this.inboxBuffer.unshift({
|
|
303
|
-
id: payload.message.id,
|
|
304
|
-
from: { agentId: senderAgentId, xmtpAddress: senderAddress },
|
|
305
|
-
conversationId: ctx.conversation.id,
|
|
306
|
-
isGroup,
|
|
307
|
-
content,
|
|
308
|
-
contentType,
|
|
309
|
-
timestamp: payload.timestamp,
|
|
310
|
-
});
|
|
311
|
-
if (this.inboxBuffer.length > this.maxInboxBuffer) this.inboxBuffer.pop();
|
|
312
|
-
|
|
313
|
-
// 通知 plugin 层(传递结构化 payload,由调用方决定如何处理)
|
|
314
|
-
if (this.onMessage) {
|
|
315
|
-
this.onMessage(this.agentId, payload);
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
get address(): string {
|
|
320
|
-
return this.agent?.address ?? this.walletConfig.address;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
get isConnected(): boolean {
|
|
324
|
-
return this.running;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
get env() {
|
|
328
|
-
return this.walletConfig.env;
|
|
329
|
-
}
|
|
330
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,192 +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
|
-
/** 群成员信息(含角色) */
|
|
16
|
-
export interface GroupParticipant {
|
|
17
|
-
address: string;
|
|
18
|
-
/** XMTP PermissionLevel: 0=Member, 1=Admin, 2=SuperAdmin */
|
|
19
|
-
permissionLevel: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** 注入 Agent session 的消息格式 */
|
|
23
|
-
export interface A2AInjectPayload {
|
|
24
|
-
type: "a2a-xmtp";
|
|
25
|
-
from: {
|
|
26
|
-
agentId: string | null;
|
|
27
|
-
xmtpAddress: string;
|
|
28
|
-
displayName?: string;
|
|
29
|
-
};
|
|
30
|
-
conversation: {
|
|
31
|
-
id: string;
|
|
32
|
-
isGroup: boolean;
|
|
33
|
-
participants: string[];
|
|
34
|
-
/** 群成员详细信息(含角色),仅群聊时有值 */
|
|
35
|
-
participantDetails?: GroupParticipant[];
|
|
36
|
-
};
|
|
37
|
-
message: {
|
|
38
|
-
id: string;
|
|
39
|
-
content: string;
|
|
40
|
-
contentType: A2AContentType;
|
|
41
|
-
turn: number;
|
|
42
|
-
replyTo?: string;
|
|
43
|
-
};
|
|
44
|
-
timestamp: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export type A2AContentType = "text" | "markdown" | "transaction" | "action";
|
|
48
|
-
|
|
49
|
-
/** 防循环策略配置 */
|
|
50
|
-
export interface ConversationPolicy {
|
|
51
|
-
maxTurns: number;
|
|
52
|
-
minIntervalMs: number;
|
|
53
|
-
ttlMinutes: number;
|
|
54
|
-
consentMode: ConsentMode;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** 群聊调度配置 */
|
|
58
|
-
export interface GroupSchedulingConfig {
|
|
59
|
-
/** 指定回复者发送 claim 前的基础延迟。Default: 1000ms */
|
|
60
|
-
baseDelayMs: number;
|
|
61
|
-
/** 等待前一个 slot 发送 claim 的超时时间。Default: 6000ms */
|
|
62
|
-
slotTimeoutMs: number;
|
|
63
|
-
/** 看到 claim 后等待实际回复的最大时间。Default: 30000ms */
|
|
64
|
-
claimExpireMs: number;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export type ConsentMode = "auto-allow-local" | "explicit-only";
|
|
68
|
-
export type ConsentState = "allow" | "deny" | "unknown";
|
|
69
|
-
|
|
70
|
-
/** 插件运行时配置(从 openclaw.json entries.a2a-xmtp.config 解析) */
|
|
71
|
-
export interface PluginConfig {
|
|
72
|
-
xmtp: {
|
|
73
|
-
env: XmtpEnv;
|
|
74
|
-
dbPath: string;
|
|
75
|
-
};
|
|
76
|
-
policy: ConversationPolicy;
|
|
77
|
-
groupScheduling: GroupSchedulingConfig;
|
|
78
|
-
walletKey?: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 会话状态(Policy Engine 内部使用) */
|
|
82
|
-
export interface ConversationState {
|
|
83
|
-
turn: number;
|
|
84
|
-
lastSendTime: number;
|
|
85
|
-
createdAt: number;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/** 群聊调度状态(per-conversation) */
|
|
89
|
-
export interface GroupConversationState {
|
|
90
|
-
/** 固定发言顺序(首次计算后缓存) */
|
|
91
|
-
speakingOrder: string[];
|
|
92
|
-
/** 已收到的非 claim 消息计数 */
|
|
93
|
-
messageCount: number;
|
|
94
|
-
/** 最近一次 claim 信息 */
|
|
95
|
-
lastClaim: { sender: string; timestamp: number; messageId: string } | null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** Claim 消息前缀(可见标记,避免 XMTP 传输时被 strip) */
|
|
99
|
-
export const CLAIM_PREFIX = "__CLAIM__:";
|
|
100
|
-
|
|
101
|
-
/** 默认群聊调度配置 */
|
|
102
|
-
export const DEFAULT_GROUP_SCHEDULING: GroupSchedulingConfig = {
|
|
103
|
-
baseDelayMs: 1000,
|
|
104
|
-
slotTimeoutMs: 6000,
|
|
105
|
-
claimExpireMs: 30000,
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/** 收件箱消息 */
|
|
109
|
-
export interface InboxMessage {
|
|
110
|
-
id: string;
|
|
111
|
-
from: {
|
|
112
|
-
agentId: string | null;
|
|
113
|
-
xmtpAddress: string;
|
|
114
|
-
};
|
|
115
|
-
conversationId: string;
|
|
116
|
-
isGroup: boolean;
|
|
117
|
-
content: string;
|
|
118
|
-
contentType: A2AContentType;
|
|
119
|
-
timestamp: string;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** 群聊信息 */
|
|
123
|
-
export interface GroupInfo {
|
|
124
|
-
conversationId: string;
|
|
125
|
-
name: string;
|
|
126
|
-
memberAddresses: string[];
|
|
127
|
-
createdAt: string;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/** 默认策略配置 */
|
|
131
|
-
export const DEFAULT_POLICY: ConversationPolicy = {
|
|
132
|
-
maxTurns: 10,
|
|
133
|
-
minIntervalMs: 5000,
|
|
134
|
-
ttlMinutes: 60,
|
|
135
|
-
consentMode: "auto-allow-local",
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
/** 默认插件配置 */
|
|
139
|
-
export const DEFAULT_PLUGIN_CONFIG: PluginConfig = {
|
|
140
|
-
xmtp: {
|
|
141
|
-
env: "dev",
|
|
142
|
-
dbPath: "./xmtp-data",
|
|
143
|
-
},
|
|
144
|
-
policy: DEFAULT_POLICY,
|
|
145
|
-
groupScheduling: DEFAULT_GROUP_SCHEDULING,
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/** 格式化 A2A 消息(注入 session 时的文本表示) */
|
|
149
|
-
export function formatA2AMessage(payload: A2AInjectPayload): string {
|
|
150
|
-
const sender = payload.from.agentId || payload.from.xmtpAddress;
|
|
151
|
-
const prefix = payload.conversation.isGroup ? "[A2A Group]" : "[A2A]";
|
|
152
|
-
return `${prefix} from ${sender} (turn ${payload.message.turn}): ${payload.message.content}`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** 创建 A2AInjectPayload */
|
|
156
|
-
export function createA2APayload(params: {
|
|
157
|
-
fromAgentId: string | null;
|
|
158
|
-
fromAddress: string;
|
|
159
|
-
conversationId: string;
|
|
160
|
-
isGroup: boolean;
|
|
161
|
-
participants: string[];
|
|
162
|
-
participantDetails?: GroupParticipant[];
|
|
163
|
-
messageId: string;
|
|
164
|
-
content: string;
|
|
165
|
-
contentType: A2AContentType;
|
|
166
|
-
turn: number;
|
|
167
|
-
replyTo?: string;
|
|
168
|
-
displayName?: string;
|
|
169
|
-
}): A2AInjectPayload {
|
|
170
|
-
return {
|
|
171
|
-
type: "a2a-xmtp",
|
|
172
|
-
from: {
|
|
173
|
-
agentId: params.fromAgentId,
|
|
174
|
-
xmtpAddress: params.fromAddress,
|
|
175
|
-
displayName: params.displayName,
|
|
176
|
-
},
|
|
177
|
-
conversation: {
|
|
178
|
-
id: params.conversationId,
|
|
179
|
-
isGroup: params.isGroup,
|
|
180
|
-
participants: params.participants,
|
|
181
|
-
participantDetails: params.participantDetails,
|
|
182
|
-
},
|
|
183
|
-
message: {
|
|
184
|
-
id: params.messageId,
|
|
185
|
-
content: params.content,
|
|
186
|
-
contentType: params.contentType,
|
|
187
|
-
turn: params.turn,
|
|
188
|
-
replyTo: params.replyTo,
|
|
189
|
-
},
|
|
190
|
-
timestamp: new Date().toISOString(),
|
|
191
|
-
};
|
|
192
|
-
}
|