Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

对等组

**对等组(peer group)**声明了代理在某个通道上接受来自哪些方的入站消息,以及它可以在该通道上与哪些其他代理交换消息。它是聊天通道的入站网关,也是跨代理分发的路由原语。在配置中,它位于 [peer_groups.<name>]。关于对等组如何融入代理的连接配置,请参阅 Agents。

入站发送者会根据为该渠道实例解析出的对等集合进行门控。该集合取自所有 channel 与渠道类型或其点分别名匹配的 [peer_groups.<name>] 代码块。匹配时会去除开头的 @,并且不区分大小写,与渠道原生的发送者标识符进行比对。空集合会拒绝所有人;包含 "*" 的集合会接受任何人;否则仅接受列出的 external_peers(以及对等代理)。这与网关配对([gateway] require_pairing)是分开的,后者用于验证 HTTP/WebSocket 客户端,而非聊天渠道的发送者。

字段

[peer_groups.<name>] 块包含:

字段含义
channel频道类型("telegram",应用于该类型的每个别名)或带点的别名("telegram.work",仅作用于该单个实例)。
agents按别名划分的成员代理。只有当两个代理同时出现在同一组中时,它们才互为对等代理;成员关系是相互的。
external_peers按渠道的原生用户名/ID 列出的非客服成员。["*"] 表示接受任何人;为空则不接受任何人。
ignore按组的黑名单;随解析后的对等节点集合一同传递,并覆盖任何授权(包括通配符授权)。由通道依据该通道的身份规则执行。
output_modality群组的首选回复方式:mirror(由输入驱动,默认值)、voice(在支持音频的频道上始终以 TTS 语音形式回复并主动推送消息)或 text(始终使用文本)。
admin_for_agent_scope当为 true 时,组的 external_peers 被授权在绑定的代理上发出 /model --agent <model>。默认 false(默认拒绝)。请参阅 Admin agent-scope authorization。

分辨率

外部发件人授权以通道为范围:运行时会合并所有与通道类型或点分别名匹配的分组中的 external_peers,并将每个匹配分组的 ignore 列表以保留的 !name 条目形式一并携带。两部分都会传递到通道,由通道先应用拒绝规则,再应用授权规则。

拒绝规则由通道执行,而不是由解析器执行。 两个条目是否指向同一账户,只有通道本身能判断:Reddit 将 u/alice 与 alice 视为同一用户,WhatsApp 将 +1555… 与 1555… 视为同一号码,Nostr 会把 npub 转换为 hex。如果解析器自行移除被忽略的条目,就必须对此进行猜测;一旦猜错,拒绝规则会在通道应用之前就被丢弃,从而放行了运营者明确写下要拒绝的发送者。因此,ignore 不会在任何地方被减去,只会在理解身份的那个通道中处理。

ignore 的优先级高于授权规则,包括通配符授权。当一个匹配组设置了 external_peers = ["*"],另一个匹配组设置了 ignore = ["alice"] 时,除 alice 之外的所有发送者都会被授权。ignore = ["*"] 则正好相反,会拒绝所有发送者。拒绝规则会匹配该频道上同一字符串原本会授权的所有对象,并且还会忽略大小写和开头的 @,因为黑名单应当宁可误拒,也不应漏拒。在一个为同一账户接受多种标识符的频道上(Bluesky 接受 handle 或 DID),忽略其中任意一个标识符都会拒绝该账户。

通配符在识别时会去除首尾空白,因此 [" * "] 和 ["*"] 对规则的两部分而言含义相同。

! 因此在解析列表中开启一个拒绝标记。根据 RFC 5322,标识符可以合法地以 ! 开头,因为它允许出现在电子邮件的本地部分(local-part)中,所以决定结果的是开头标记的_数量_,而不仅仅是它们是否存在。一个以 k 个标记开头的标识符,授权时输出 2k,拒绝时输出 2k + 1,因此偶数个标记表示授权,奇数个标记表示拒绝,将标记数除以二即可还原出标识符:

身份作为 external_peers作为 ignore
alicealice!alice
!user@example.com!!user@example.com!!!user@example.com

不以 ! 开头的标识符,其编码方式与以往完全相同,因此现有配置的含义不会有任何变化。运维人员所写的内容在两个方向上都不会被静默取反。你按原样书写地址即可;这种重复仅发生在解析后的列表内部。

一个在 channel 中以多种写法出现的渠道(WeCom WebSocket 同时响应 wecom-ws 和 wecom_ws)会被作为一个整体集合进行解析。若分别解析每种写法再合并结果,则会导致一种写法下的通配符与另一种写法下的 ignore 相互不可见。所有启动路径都必须使用相同的解析逻辑:守护进程的常规渠道构建与单渠道一次性运行路径都通过同一个具名辅助函数来完成,正是出于这一原因。

由于拒绝规则随解析后的列表一同传递,频道通过 allowlist::is_user_allowed、is_user_allowed_by 授权发送者;若发送者有多个标识符,则使用 is_identity_allowed。直接检查列表是否包含 "*",或逐个检查标识符,会放行操作员本意忽略的发送者。

解析后的列表是授权输入,而不是地址列表:它包含通配符和拒绝标记。需要发送消息目标的代码会调用 Config::channel_addressable_peers,该方法只返回指定了一个可达账户的条目。

出于同样的原因,“列表是否为空”并不等同于“是否有人已被授权”。仅配置了 ignore 而未配置 external_peers 的组,解析结果是一个非空列表,但并不授权任何人,因此判断自身是否仍未配对的频道应调用 allowlist::grants_anyone,而不是检查列表是否为空。被自身 ignore 抵消的授权同样不计入:external_peers = ["alice"] 搭配 ignore = ["alice"],或 ["*"] 搭配 ignore = ["*"],都不授权任何人,因此依赖该结果来控制配对的频道会继续展示其绑定码。

配对随后会写入 external_peers,这意味着同一个 ignore 条目可能遮蔽配对刚刚持久化的内容。与其报告一个无法生效的绑定,不如直接拒绝写入,并告知操作者先删除该 ignore 条目。显式的黑名单条目对配对尝试具有绝对效力。这一规则适用于所有写入方:会话内的 /bind 交互、zeroclaw channel bind-telegram 以及 POST /api/channels/bind,三者都会拒绝此类写入。拒绝范围也包括已在授权列表(grant)中的身份,因为被自身 ignore 遮蔽的授权同样不是可用的绑定。

跨代理路由以代理为范围:对于给定的代理,运行时会遍历该代理所在的每个群组,合并该群组频道上其他成员的别名,然后减去 ignore。为避免自环,代理自身的别名会被防御性地移除。不属于任何对等群组的代理将单独运行,不进行跨代理分发。

每个通道用于匹配的发送者标识符因平台而异(Telegram 用户 ID、Matrix @user:server、E.164 号码、UUID……)。每个通道页面都会说明它所期望的标识符格式。

示例

一个名为例如 my_discord_group 的 discord 对等组设置 channel = "discord",在 external_peers 中允许 111111111111111111,将对等代理命名为 researcher、summariser,并通过 ignore 屏蔽 222222222222222222。可通过网关仪表板、zerocode 或 zeroclaw config set 进行设置。

每个通道页面都会展示带有该通道发送方标识符形态的指令形式。

管理员代理范围授权

admin_for_agent_scope = true 扩展了组的权限边界:除了作为可路由对等端之外,每个 external_peers 成员还被允许在已绑定的 agent 上发出 /model --agent <model>,也就是将该 agent 的 binding 切换到默认值以外的模型。该标志默认是拒绝的:每个未显式设置为 true 的组都会拒绝此能力,包括 external_peers 非空的组。/model --user <model>(仅会话覆盖,不重新绑定 agent)不受此标志限制,对每个被接受的对等端都可用。

协调器会直接从 Config::channel_agent_scope_admins 实时解析已授权的管理员集合,但派发门控读取的是运行时启动时所使用的配置快照。因此,对 admin_for_agent_scope(或某个组的 external_peers / channel / agents)所做的操作员修改,只会在下一次守护进程重启时生效,而不会影响正在运行的进程。仅在运行中的守护进程上切换该标志,本身不会在当前会话中授权任何新的发送方。

这是有意为之:授权是针对启动时不可变的配置计算的,因此在运行过程中被_添加_到管理员组的对等方,无法在当前进程生命周期内提升权限。该集合只有在完整配置重新加载时才会重新解析,而这本身就是一种重启路径。如果一次 /model --agent 调用对你预期应在范围内的发送方报告“not authorized”,请在编辑 admin_for_agent_scope 后重启守护进程,并在新的客户端会话中重新发出该命令,然后再得出进一步结论。