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
|
@@ -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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|