:无本地托管的所有权邀请、发布授权与草稿恢复机制详解)
Buzz 远程提及路由Remote Mention Routing无本地托管的所有权邀请、发布授权与草稿恢复机制详解【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz导读在 Buzz一个 hive mind 通信平台中用户可以通过提及并邀请由自己拥有但运行在远端中继relay上的 Agent参与对话而无需在本地创建、托管或启动任何 Agent 运行时。本文围绕 docs/remote-mention-routing.md 展开系统讲解远程提及准备与发布Remote mention preparation and publication阶段的完整契约选择器picker与准备阶段如何评估策略、发布阶段如何对最终目的地重新授权、邀请意图Invitation intent如何与取消信号绑定、以及草稿与精确提及引用如何在取消后可恢复。读完本文你将理解 Buzz 中所有权 ≠ 成员身份、缓存的选择器证据 ≠ 发布授权等核心安全边界并掌握对应回归测试的组织方式与关键实现位置。前置阅读本文所依赖的经认证的拥有型 Agent 发现机制由 docs/owned-agent-discovery.mdPR6定义建议先阅读该文档以理解 kind 30177 坐标、NIP-OA 认证标签与 kind 39002 成员快照等发现层概念。一、远程提及的整体设计原则远程提及路由的核心前提是拥有型中继 Agentowned relay agents可以被选中并邀请而不必由本地运行时托管without local runtime custody。整个流程被划分为两个阶段选择器与准备阶段picker preparation评估策略policy但允许接受一个拥有但尚未加入频道owned nonmember的 Agent 进入流程发布阶段publication针对最终目的地单独刷新授权包括新创建的私信DM场景。这条设计蕴含三条关键的安全不变量所有权不是成员身份Ownership is not membership某个 Agent 是你的不代表它已加入目标频道缓存的选择器证据永远不是发布授权cached picker evidence is never publication authorization准备阶段通过的检查到发布时必须重新验证选择即意图Selections are intent如果一个被选中的 Agent 密钥已消失或被吊销流程必须显式失败、保留草稿、什么都不发送绝不能静默丢弃收件人。对应实现上准备与发布两个阶段的区分可以直接在 desktop/src/features/messages/lib/agentMentionRevalidation.ts 中看到MentionRevalidationOptions.phase的取值只有prepare | publish两种且revalidateAgentMentionPubkeys默认按publish处理。1.1 捕获的密钥在异步流程中的存活被选中的 Agent 密钥captured agent keys一旦被捕获将跨越以下事件持续有效编辑器清空composer clearing、媒体上传media upload、编辑edits以及异步准备asynchronous preparation。这意味着清空输入框或上传附件不会导致收件人丢失——密钥已经被冻结为本次发送意图的一部分。1.2 本地清单读取失败的处理失败的本地清单读取不能否决已通过认证的中继身份本地清单读取失败也不能引入过期的本地运行时记录本地托管的运行时就绪状态仍然走独立的既有路径远端身份永远不会获得合成的本地托管记录。该约束在 desktop/src/features/messages/lib/agentMentionRevalidation.test.mjs 中有直接对应用例当refetchManagedAgents返回{ data: undefined, error: new Error(local unavailable) }时只要fetchRelayAgents返回合法的拥有型中继 AgentrespondTo: owner-only重新验证依然放行相反用例stale local data is not authority when its refresh failsagentMentionRevalidation.test.mjs证明本地数据刷新失败时过期本地记录不构成权威混合证据会抛出AgentMentionAuthorizationError。1.3 聊天与论坛的行为差异聊天Chat明确提供两种发送方式——显式Invite邀请或仅引用发送而不邀请reference-only send失败的新增failed adds、被吊销的策略revoked policy、最终授权失败以及取消都必须保留可恢复的草稿独立论坛Standalone forum发送时报告授权失败而独立论坛的邀请功能属于后续变更将复用本阶段的契约。1.4 与 NIP-OA 的边界NIP-OA 确立的是所有权而不是物理托管、可用性或生命周期控制。最终的原生native查询不提供与消息发布捆绑的原子中继事务。因此文档明确要求在合入前必须对原生边界与发布边界分别进行独立评审。二、邀请意图与取消Invitation intent and cancellation2.1 同步 pending 闩锁与中止信号每个聊天 Invite 拥有一个同步的 pending 闩锁latch与中止abort信号其生命周期覆盖准备preparation、清单inventory、新增成员adds以及最终的发布延续publication continuation。以下事件会使本次尝试失效按 Escape 键导航/卸载navigation/unmount选择仅引用发送reference-only selection替换提示词replacement prompt。需要特别注意将提示词提升为发送时清空提示词不是取消。信号会保持存活一直穿过最终校验与排队媒体完成queued media completion。取消检查必须执行两次异步准备之后、任何后续变更包括嵌套的本地就绪检查之前发布之前。2.2 已完成新增与迟到响应的语义一次已完成的新增无法撤销但其迟到的响应不能复活已取消的发送意图完成新增失败的情况下事件计数为 0见下文测试。2.3 pending 状态的 UI 表现pending 状态覆盖整个准备阶段在此期间Invite 与仅引用两个按钮都会被禁用。在乐观清空optimistic clearing之后发生取消时会恢复捕获的草稿与精确提及引用exact mention refs且不会覆盖更新的编辑内容。2.4 测试驱动验证取消语义由两条测试覆盖desktop/src/features/messages/ui/useMentionSendFlow.cancellation.test.mjs使用 React StrictMode 与延迟依赖deferred dependencies驱动真实 hooksdesktop/tests/e2e/remote-owned-mentions.spec.ts在延迟 IPC 边界上覆盖可见的 pending、Escape/重试与路由导航。关键测试用例的断言逻辑非常直观// useMentionSendFlow.cancellation.test.mjs节选语义 for (const stage of [prepare, inventory, add]) { test(dismiss during delayed ${stage} cannot add further or send, retains draft, async () { // 在阶段门控gate延迟期间 dismiss // - add 事件数prepare/inventory 阶段为 0add 阶段为 1已完成的新增无法撤销 // - SEND 事件数始终为 0 // - contentRef 保持原文草稿可恢复 }); }另外两个值得注意的用例normal Invite survives promotion/render and synchronous double click sends exactly onceuseMentionSendFlow.cancellation.test.mjs清空提示词不是取消连续两次调用onInvite也只产生一次add与一次publish最终恰好发送一次late cancelled failure cannot reset a newer pending attemptuseMentionSendFlow.cancellation.test.mjs一个过期尝试obsolete add的迟到失败不会重置新一次尝试的 pending 状态新尝试依然能成功发送。三、源草稿所有权Source draft ownership3.1 邀请意图属于一次访问邀请意图不属于挂载中的编辑器或目标频道而属于有效持久化键effective persistence key与频道的一次访问one visit。以下情况会使原始访问失效同频道的线程thread变更A → B → A 的频道往返切换。**可见所有者visible owner**门控以下内容乐观清空、编辑器恢复与 pending 状态。3.2 存储恢复与共享的创作意图与可见所有者不同存储恢复storage recovery在退出、重进或卸载之后仍然会查询源草稿键对应的共享创作意图shared authored intent。草稿存储独立于存储值之外额外保留一个修订号revision一个权威为空authoritative-empty标记。后续的同键创作文本/删除或新一次发送会吊销较早的恢复与已发送草稿清理而编辑 B不会改变 A 的权威。一个针对该访问的访问器visit-specific accessor会读取该共享权威同时保留独立的可见所有者身份。3.3 失效的持久化时机失效invalidation会同步地使恢复变为持久化状态并且发生在后续访问可以加载或编辑源草稿之前迟到的异步完成不能复活该恢复也不能释放新一次尝试的闩锁。3.4 创作修订与乐观空编辑器的区分编辑器的创作修订authored revision用于区分以下两种情况有意的编辑 → 清空intentional edit → clear乐观清空的空编辑器optimistic empty composer。程序化的发送清空/恢复运行在草稿生命周期的恢复边界restoration boundary内部因此不会将源标记为权威删除。3.5 剪贴板身份验证与选择捕获的顺序待处理的剪贴板身份验证pending clipboard identity verification必须先落定之后才会捕获精确选中的提及引用源访问与创作修订在等待验证之前就被捕获在此期间发生的编辑、导航包括 A → B → A或卸载会取消流程而不是读取新草稿的映射随后的异步准备阶段消费已捕获的选择。3.6 Persona 准备的同源约束Persona 准备同样消费已捕获的选择并返回解析后的引用resolved refs但只有在原始访问/修订仍然有效时才会把引用写回编辑器。以下既有行为保持不变正常的 Persona 创建/复用普通的面向目的地的后台发送ordinary destination-bound background sends已接受的成员身份永远不会回滚accepted membership is never rolled back。3.7 恢复前的比较与语义变更来源恢复recovery在替换既有记录之前会比较存储的内容/媒体/精确引用。需要区分不改变语义意图的程序化持久化programmatic persistence本身会改变语义意图的显式的收件箱删除与替换explicit inbox deletion and replacement会使旧句柄失效的作用域重置scope reset。3.8 同窗口权威的边界这套机制是同窗口same-window的存活延续权威不是跨窗口同步也不是版本化存储协议。因此页面重载reload会销毁所有延续但创作删除authored deletion此时已经移除了持久化值。四、发布阶段的最终边界与残留风险文档在最后明确列出了仍然存在的边界最终的成员/策略读取与发送不是原子的final membership/policy reads are not atomic with send取消无法撤回已经派发的发布cancellation cannot retract an already dispatched publication独立论坛的传输失败绑定恢复transport-failure binding recovery以及原生兼容性属于后续独立评审/跟进边界。这也解释了为什么整个流程在发布前必须做第二次重新授权准备阶段可能放行拥有型非成员但发布阶段必须对精确的最终目的地包括新建 DM重新验证成员身份。测试 agentMentionRevalidation.test.mjs 用for (const type of [channel, owned])双通道验证了这一点// 语义摘要准备阶段允许拥有型非成员进入发布阶段必须验证实际成员身份 // phase: prepare → 返回 [HUMAN, AGENT]放行 // phase: publish默认且 channelIds[] → 抛 AgentMentionAuthorizationError // phase: publish 且 channelIds[target] → 返回 [HUMAN, AGENT]放行 // phase: publish 且 channelIds[other] → 抛 AgentMentionAuthorizationError同样地agentMentionRevalidation.test.mjs 覆盖了发布不能授权一个尚无目的地的 DMeligibilityScope: { type: owned, channelId: null }时拒绝。五、选择器资格Autocomplete eligibility与辅助逻辑5.1 自动补全资格过滤desktop/src/features/agents/lib/agentAutocompleteEligibility.test.mjs 集中测试了选择器层的资格判断核心函数包括函数职责要点isAgentDirectoryReady需要成功的缓存目录证据data: undefined或带error均视为未就绪fail closedgetSharedChannelIds只包含活跃已加入的频道已归档或未加入的频道被排除relayAgentIsSharedWithUser接受respondTo: anyone的共享 Agentowner-only要求验证过的同所有者allowlist要求当前用户在允许列表中pubkey 大小写会归一化relayAgentCanRespondInChannel要求精确的频道成员身份与查看者访问权getMentionableAgentPubkeys保留受管 Agent 与共享中继 Agent按eligibilityScopecommunity/channel/managed-only/owned收缩shouldHideAgentFromMentions非 Agent 永不隐藏非成员但可调用的 Agent 显示成员 Agent 需要明确的目录授权目录加载期间保持隐藏getAgentMentionAdmission返回allow / deny / unknown目录未就绪时保持 unknowncoalesceAgentAutocompleteCandidates对同一 pubkey 的重复来源行做合并但不合并不同 personaId、不同 owner、或同所有者不同 pubkey 的同名 Agent其中几个与本文主题强相关的用例owned discovery does not require a shared channel, but sending doesagentAutocompleteEligibility.test.mjsowner-only/allowlist/anyone三种策略下拥有型 Agent 在无共享频道时依然可被发现relayAgentIsSharedWithUser为真但发送relayAgentCanRespondInChannel为假——正是所有权 ≠ 成员身份的代码级体现DM ownership is independent of local configuration and still requires membershipagentAutocompleteEligibility.test.mjseligibilityScope: { type: owned }配合phase: prepare时即使channelId: null也放行拥有型 Agent为后续新建 DM的发布授权留下空间。5.2 发送流辅助函数desktop/src/features/messages/ui/useMentionSendFlow.helpers.test.mjs 验证了发送流辅助逻辑formatMessageSendError/getErrorMessage保留发布失败原因如relay rejected voice note、Tauri 字符串错误用户可见的错误信息格式为Message failed to send: 原因mergeMentionRecipients将显式提及与地址锁定address-locked的 Agent 合并为去重的收件人列表pubkey 大小写归一化mentionRevalidationOptions独立于已清空的编辑器携带捕获与已准备的 Agent 密钥——inlineAgentMentionPubkeys与addressedAgentPubkeys合并为intendedAgentPubkeys准备阶段prepare之后发布阶段publish可再追加新密钥这正是捕获的密钥跨越编辑器清空存活的辅助层实现。六、端到端回归覆盖E2E与实现位置6.1 指定的回归测试清单本文档指定的回归覆盖包括以下文件测试文件覆盖层次desktop/src/features/agents/lib/agentAutocompleteEligibility.test.mjs选择器资格单元测试desktop/src/features/messages/lib/agentMentionRevalidation.test.mjs提及重新授权单元测试prepare/publish 两阶段desktop/src/features/messages/ui/useMentionSendFlow.helpers.test.mjs发送流辅助函数单元测试desktop/src/features/messages/ui/useMentionSendFlow.cancellation.test.mjs邀请/取消的 hooks 级测试React StrictModedesktop/src/features/messages/ui/submitMessageEdit.test.mjs编辑提交路径测试desktop/tests/e2e/mentions.spec.ts提及 E2Edesktop/tests/e2e/remote-owned-mentions.spec.ts远程拥有型提及 E2E6.2 新的远程浏览器夹具使用单字名称文档特别注明新的远程浏览器夹具fixtures刻意使用单字名称如RemoteScout因为提及分隔符mention separator行为属于独立的 PR1避免跨变更耦合。6.3 E2E 的核心断言desktop/tests/e2e/remote-owned-mentions.spec.ts 中install通过 mock bridge 注入一个ownerOnlyAccessBuild: true、本地managedAgents: []的远端 AgentownerPubkey为当前用户、respondTo: allowlist随后member 与 bot 两种角色下发送RemoteScout hello后签名事件的p标签精确等于[REMOTE]且不出现 Invite 按钮非成员场景下点击发送后 Invite 按钮可见走授权新增 → 精确发布路径assertNoLocalLifecycle断言命令日志中绝不出现start_managed_agent/create_managed_agent/attach_managed_agent——这是无本地运行时托管no local runtime custody的端到端铁证。6.4 核心实现入口发送流 hooksdesktop/src/features/messages/ui/useMentionSendFlow.ts 的useMentionSendFlow维护pendingNonMemberSend状态、draft.invitationSignal中止信号与completeSend发布延续发布路径上多处检查signal?.aborted || isSendCancelled()见 useMentionSendFlow.ts提及重新授权desktop/src/features/messages/lib/agentMentionRevalidation.ts 的revalidateAgentMentionPubkeysphase参数区分 prepare/publish与AgentMentionAuthorizationError异常类型useAgentMentionRevalidationhook同文件第 98 行负责在 UI 层接入资格过滤desktop/src/features/agents/lib/agentAutocompleteEligibility.ts与测试同目录。七、合入前的评审要求与后续边界本文档作为设计契约明确要求由于最终的原生查询不提供与发布捆绑的原子事务在合入之前必须对原生边界与发布边界分别进行独立安全评审。同时以下内容被显式标记为后续跟进边界独立论坛的邀请功能复用本阶段契约独立论坛的传输失败绑定恢复原生native兼容性提及分隔符行为PR1。这也提醒使用者远程提及路由是一个分阶段落地的安全敏感功能发布前重新授权、取消信号的两次检查、以及草稿的可恢复性共同构成失败显式、不静默丢收件人、不误发的三道防线。任何在该区域修改代码的开发者都应同步更新上表中的回归测试并遵循所有权 ≠ 成员身份、缓存证据 ≠ 发布授权两条不变量。【免费下载链接】buzzA hive mind communication platform项目地址: https://gitcode.com/GitHub_Trending/buzz14/buzz创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考