a2a-xmtp 1.4.0 → 1.4.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "a2a-xmtp",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "description": "Decentralized Agent-to-Agent E2EE messaging for OpenClaw via XMTP",
@@ -39,10 +39,16 @@ export class GroupScheduler {
39
39
 
40
40
  /**
41
41
  * 初始化或获取群组状态。成员变化时自动重算。
42
+ * @param agentAddresses 已知的 agent 地址集合,仅 agent 参与轮询;
43
+ * 非 agent(如群主/人类)不参与 speaking order。
42
44
  */
43
- getOrInitGroup(groupId: string, members: string[]): GroupConversationState {
45
+ getOrInitGroup(groupId: string, members: string[], agentAddresses?: Set<string>): GroupConversationState {
44
46
  const existing = this.groups.get(groupId);
45
- const order = this.computeSpeakingOrder(groupId, members);
47
+ // 仅保留 agent 地址参与轮询,非 agent 跳过
48
+ const agentMembers = agentAddresses
49
+ ? members.filter((m) => agentAddresses.has(m.toLowerCase()))
50
+ : members;
51
+ const order = this.computeSpeakingOrder(groupId, agentMembers);
46
52
 
47
53
  // 成员变化 → 重算并重置计数
48
54
  if (existing && !arraysEqual(existing.speakingOrder, order)) {
@@ -4,6 +4,7 @@
4
4
  // ============================================================
5
5
 
6
6
  import type { XmtpBridge } from "../transport/xmtp-bridge.js";
7
+ import type { IdentityRegistry } from "../transport/identity-registry.js";
7
8
  import { formatA2AMessage, type A2AInjectPayload } from "../types.js";
8
9
  import { GroupScheduler } from "./group-scheduler.js";
9
10
  import type { PolicyEngine } from "./policy-engine.js";
@@ -49,6 +50,7 @@ export class MessageOrchestrator {
49
50
  private logger: Logger,
50
51
  private policyEngine: PolicyEngine,
51
52
  groupScheduler: GroupScheduler,
53
+ private registry: IdentityRegistry,
52
54
  ) {
53
55
  this.groupScheduler = groupScheduler;
54
56
  }
@@ -122,15 +124,16 @@ export class MessageOrchestrator {
122
124
  const replyText = this.extractReplyText(messages);
123
125
  if (!replyText) return;
124
126
 
125
- // 记录本 agent turn
126
- this.policyEngine.recordTurn(payload.conversation.id);
127
-
127
+ // recordTurn bridge.sendMessage 内部调用,此处不重复
128
128
  await bridge.sendMessage(payload.from.xmtpAddress, replyText, {
129
129
  conversationId: payload.conversation.id,
130
130
  });
131
131
 
132
- // 群聊:清除 claim 状态(回复已发送)
132
+ // 群聊:计入自己的消息 + 清除 claim
133
133
  if (payload.conversation.isGroup) {
134
+ // 自己发的消息 handleIncoming 不会收到(跳过 self),
135
+ // 手动递增以保持与其他 agent 的 messageCount 同步
136
+ this.groupScheduler.recordMessage(payload.conversation.id);
134
137
  this.groupScheduler.clearClaim(payload.conversation.id);
135
138
  }
136
139
 
@@ -155,8 +158,17 @@ export class MessageOrchestrator {
155
158
  const members = payload.conversation.participants;
156
159
  const myAddress = bridge.address;
157
160
 
158
- // 初始化群组状态
159
- this.groupScheduler.getOrInitGroup(convId, members);
161
+ // 筛选出 agent 地址:自己 + registry 中已知的 agent
162
+ const agentAddresses = new Set<string>();
163
+ agentAddresses.add(myAddress.toLowerCase());
164
+ for (const addr of members) {
165
+ if (this.registry.getAgentId(addr)) {
166
+ agentAddresses.add(addr.toLowerCase());
167
+ }
168
+ }
169
+
170
+ // 初始化群组状态(仅 agent 参与轮询,人类/admin 不参与)
171
+ this.groupScheduler.getOrInitGroup(convId, members, agentAddresses);
160
172
 
161
173
  // 检查 turn budget 是否耗尽
162
174
  if (this.policyEngine.isTurnExhausted(convId)) {
@@ -28,10 +28,10 @@ export class PolicyEngine {
28
28
  this.localAgentIds.add(agentId);
29
29
  }
30
30
 
31
- checkOutgoing(params: PolicyCheckParams): PolicyCheckResult {
32
- return this.check(params);
33
- }
34
-
31
+ /**
32
+ * 入站检查:consent + TTL + turn budget(不检查 cool-down,
33
+ * 否则刚发完消息后收到的回复会被静默丢弃)
34
+ */
35
35
  checkIncoming(params: PolicyCheckParams): PolicyCheckResult {
36
36
  const consent = this.getConsent(params.from);
37
37
  if (consent === "deny") {
@@ -43,10 +43,23 @@ export class PolicyEngine {
43
43
  reason: `Sender ${params.from} not explicitly allowed (consent: ${consent})`,
44
44
  };
45
45
  }
46
- return this.check(params);
46
+ // 仅检查 TTL + turn budget,不检查 cool-down
47
+ const state = this.getOrCreateState(params.conversationId);
48
+ const now = Date.now();
49
+ const ttlMs = this.policy.ttlMinutes * 60 * 1000;
50
+ if (now - state.createdAt > ttlMs) {
51
+ return { allowed: false, reason: `Conversation TTL expired (${this.policy.ttlMinutes} min)` };
52
+ }
53
+ if (state.turn >= this.policy.maxTurns) {
54
+ return { allowed: false, reason: `Turn budget exhausted (${state.turn}/${this.policy.maxTurns})` };
55
+ }
56
+ return { allowed: true };
47
57
  }
48
58
 
49
- private check(params: PolicyCheckParams): PolicyCheckResult {
59
+ /**
60
+ * 出站检查:TTL + turn budget + cool-down
61
+ */
62
+ checkOutgoing(params: PolicyCheckParams): PolicyCheckResult {
50
63
  const state = this.getOrCreateState(params.conversationId);
51
64
  const now = Date.now();
52
65
 
package/src/index.ts CHANGED
@@ -163,6 +163,7 @@ export default definePluginEntry({
163
163
  ctx.logger,
164
164
  policyEngine,
165
165
  groupScheduler,
166
+ registry,
166
167
  );
167
168
 
168
169
  // 初始化主 Agent 的 XMTP Bridge