news 2026/9/13 7:05:31

qwen-code Daemon 多客户端权限仲裁:四种调解策略、取消哨兵与跨连接投票路由机制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
qwen-code Daemon 多客户端权限仲裁:四种调解策略、取消哨兵与跨连接投票路由机制

qwen-code Daemon 多客户端权限仲裁:四种调解策略、取消哨兵与跨连接投票路由机制

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

qwen-code 的qwen servedaemon 允许同一会话被多个客户端同时连接,当 ACP 子进程内的 agent 发起requestPermission时,"谁有权批准、如何计票、迟到票如何处理"就成了一个必须显式建模的问题。本文基于仓库文档 04-permission-mediation.md 与对应源码,完整讲解MultiClientPermissionMediator的四类仲裁策略(first-responder/designated/consensus/local-only)、取消哨兵(cancel sentinel)的跨策略逃生通道、request()的 N1 同步注册不变量、共识法定人数的默认公式与 M=2 边界、启动期策略校验,以及投票经 ACP 与 REST 两条传输路径到达中介器的完整授权检查链。读完本文,你可以理解该仲裁机制的状态模型与事件契约,并能在多客户端协作、按租户隔离审批、企业变更评审、本地工作站等场景下正确配置policy.permissionStrategypolicy.consensusQuorum

为什么需要多客户端权限仲裁

sessionScope: 'single'模式下,同一个会话内的所有已连接客户端都能"看到" agent 发出的权限请求,且任何客户端都可以响应。如果没有仲裁层,会产生三类典型故障:

  • 迟到票无处安放:请求已被某个客户端解决后,另一个客户端的投票到达,daemon 无法给出结构化答复;
  • 竞态:两个客户端抢答同一个请求,后到者必须得到可预期的拒绝结果;
  • 越权覆盖:一个恶意(或行为异常)的客户端可以覆盖发起 prompt 的"原始客户端"的审批意图。

MultiClientPermissionMediator(permissionMediator.ts)实现了PermissionMediator契约(permission.ts),独占bridge 的全部 pending 与 resolved 权限状态——bridge 层(httpAcpBridge/bridge.ts)不再自己维护pendingPermissionsMap 或 resolved LRU。它通过PermissionPolicy声明的四种策略分派投票,每种策略对应一类部署形态:

策略判定规则典型场景
first-responder第一张有效票胜出;后到的投票方收到permission_already_resolved实时的跨客户端协作体验(默认)。
designated只有 prompt 的originatorClientId可以裁决;其他客户端看到permission_forbidden{designated_mismatch}按租户隔离的 SaaS,UI 界面必须拥有自己的审批权。
consensus在 v1 的 client-id 快照上做 N-of-M 法定人数(quorum);中间过程通过permission_partial_vote事件让 UI 渲染进度。需要两名操作员一致同意的企业变更评审。
local-only拒绝任何非 loopback 投票方;阻塞直到 loopback 客户端裁决。不允许远程控制授予权限提升的工作站。

v1 安全边界X-Qwen-Client-Id是客户端自报的(self-reported)。designatedconsensus在 v1 中尚无持有证明(proof-of-possession):能观察到originatorClientId的客户端可以复用该 id。此外,{outcome:'cancelled'}在策略分派之前就走取消哨兵通道,因此即便是local-only也无法把 cancel 当作"受策略保护的裁决"。若需要强隔离,应把 daemon 绑定到 loopback,或置于带认证的反向代理之后(详见安全说明:v1 客户端身份是自报的一节)。

契约面:接口、策略类型与投票结果

PermissionMediator 公开接口

bridge 与该机制交互只需要三个方法(permission.ts):

interface PermissionMediator { readonly policy: PermissionPolicy; request( record: PermissionRequestRecord, timeoutMs: number, ): Promise<PermissionResolution>; vote(vote: PermissionVote): PermissionVoteOutcome; forgetSession(sessionId: string): void; }

MultiClientPermissionMediator在此之外还提供peekSessionFor(requestId)pendingCount、内部审计发布器(PermissionAuditPublisher)等能力。值得注意的是BridgeClient只依赖request()这一半能力(结构子类型化),即它不需要知道投票如何裁决,只等待 Promise 结果。

核心类型

type PermissionPolicy = | 'first-responder' | 'designated' | 'consensus' | 'local-only'; type PermissionVoteOutcome = | { kind: 'resolved'; resolvedOptionId: string } | { kind: 'recorded'; votesNeeded: number } // consensus 中间态 | { kind: 'already_resolved'; resolvedOptionId: string } | { kind: 'forbidden'; reason: 'designated_mismatch' | 'remote_not_allowed' } | { kind: 'unknown_request' }; type PermissionResolution = | { kind: 'option'; optionId: string } | { kind: 'cancelled'; reason: 'timeout' | 'session_closed' | 'agent_cancelled'; };

两个容易踩坑的契约细节,源码注释里都有明确交代(permission.ts):

  • designated_mismatch是重载的designated策略下表示"投票方不是 prompt 发起者";consensus策略下表示"投票方clientId为 undefined 或不在 issue 时刻的votersAtIssue快照里"。中介器在审计日志中区分两者,但线上(wire)形状只有一个——这是刻意保持契约封闭的设计,未来版本才可能拆分该联合类型。
  • PermissionVote.fromLoopback由 daemon 盖章,永远不是客户端自报的(permission.ts)。local-only依据它拒绝远端投票,无论clientId是什么。

请求记录:PermissionRequestRecord

request()接收的记录(permission.ts)承载了策略分派所需的全部上下文:

interface PermissionRequestRecord { readonly requestId: string; // ACP RequestPermission 请求 id,会话内唯一 readonly sessionId: string; // 权限作用域永远是 per-session(v1 无 workspace 级权限) readonly promptId?: string; // 产生该权限请求的已准入 prompt readonly originatorClientId: string | undefined; // 触发底层 prompt 的客户端 readonly allowedOptionIds: ReadonlySet<string>; // agent 声明的合法选项 readonly issuedAtMs: number; // 请求发出的墙钟时间戳 }

其中allowedOptionIds是投票校验的第一道闸:任何提交了集合之外optionId的投票,在到达策略分派之前就会被抛出InvalidPermissionOptionError(permissionMediator.ts)。

中介器内部状态:Pending 与 Resolved FIFO

每个 pending 请求以requestId为键,其条目MediatorPending(permissionMediator.ts)携带:

  • policy—— 在request()时刻捕获。运行期改变 daemon 全局策略不会影响在途请求
  • record字段集(requestId、sessionId、originatorClientId、allowedOptionIds、issuedAtMs);
  • resolve/reject闭包(用于结算request()返回的 Promise);
  • votersAtIssue(仅 consensus 使用)——issue 时刻会话已注册clientId的快照;不在集合内的后到投票被拒绝;
  • tallies(仅 consensus 使用)——Map<optionId, Set<clientId>>,按选项计票;
  • timeoutHandle—— 在request()内部setTimeout挂起的 Node 定时器(N1 不变量,见下文);
  • 审计轨迹(每次投票都会写审计记录)。

源码中talliesvotersAtIssue对非 consensus 请求也一律以空容器存在,避免对policy做可判别联合类型——内存代价是每个 pending 约 120 字节,相对 per-session pending 上限 64 可忽略(见 permissionMediator.ts 的注释)。

Resolved FIFO用于让重复投票得到结构化的already_resolved而不是unknown_request

  • 容量上限MAX_RESOLVED_PERMISSION_RECORDS = 512(permissionMediator.ts);
  • 淘汰是FIFOrememberResolved通过resolvedOrder.shift()丢弃最老记录,与PermissionAuditRing的 FIFO 修正保持一致(DeepSeek 评审 #4335);
  • 每条记录只存{requestId, sessionId, outcome},512 条在常规 UI 重连/竞态窗口内远小于 100 KB(permissionMediator.ts)。

被挤出 FIFO 之后,同一requestId的迟到票只能得到{unknown_request}

工作流程

request():N1 同步注册不变量

关键在于定时器必须在条目对外可见之前挂起request()的全部注册动作——快照投票者、构造 pending、pending.set、审计、setTimeout——都发生在 Promise 执行器内、没有任何await(permissionMediator.ts)。桥接层的调用序列是publish → mediator.request → await

  • EventBus.publish是同步的(分发到内存订阅者队列,不产生事件循环让出);
  • Promise 执行器对这次调用也是同步的(同步注册不变量)。

由此,forgetSession不可能落在"事件已发布、pending 尚未注册"的窗口里。如果这个窗口存在,forgetSession会漏掉新的 pending,条目将永久挂起——bridge 的 per-sessionpromptQueue会永远阻塞。反之,若注册晚了,一个在pending.setsetTimeout之间到达的forgetSession也会留下一个没有超时的 pending。

此外,request()返回的 Promise一经返回就永不 reject:超时、会话关闭、投票者取消、emit/审计异常全部编码为{kind:'cancelled', reason:...}。唯一的同步抛出是CancelSentinelCollisionError(哨兵冲突,见下节),它在构造 Promise 之前发生,调用方必须自行 try/catch(permissionMediator.ts)。

一个细节:当 consensus 策略下 issue 时刻votersAtIssue为空(会话在 publish 与 request 之间的极窄竞态窗口内被拆除)时,该请求只能经投票者取消、会话取消或启用的permissionTimeoutMs解决;中介器会向 stderr 写一条面包屑(breadcrumb),避免操作员面对一个静默挂起的权限请求(permissionMediator.ts)。

vote()分派

vote()的实现(permissionMediator.ts)按顺序执行:

  1. 查找 pending:不在pending中时,先查 resolved FIFO——命中则重新广播permission_already_resolved(让迟到的 SSE 订阅者看到结论)并返回already_resolved;否则unknown_request。若命中但sessionId不匹配,同样返回unknown_request,不泄露信息;
  2. 哨兵短路optionId === CANCEL_VOTE_SENTINEL时,先写voted审计记录再结算为{cancelled, agent_cancelled},完全绕过策略分派;
  3. 选项校验optionId不在allowedOptionIds内则抛InvalidPermissionOptionError(路由层映射为 400);
  4. 策略 switch:四个 case 各由私有处理器(voteFirstResponder/voteDesignated/voteConsensus/voteLocalOnly)负责,末尾用never穷尽性检查保证未来新增策略字面量而忘记补 case 会在编译期失败(permissionMediator.ts)。

几个值得注意的策略细节:

  • designated的无发起者回退:当originatorClientId === undefined时,voteDesignated直接降级为 first-responder 行为(permissionMediator.ts)。这与 permission.ts 头注释中"无 originator 的 prompt 回退到 first-responder"一致;
  • consensus 不允许改票:若该clientId已在任一选项桶中,重复投票只返回recorded(附votesNeeded),不会把票从旧桶移到新桶;
  • permission_partial_vote只在 consensus 下触发,携带votesReceived/votesNeeded/quorum/optionTallies,UI 可以据此渲染进度;
  • permission_forbidden在 designated / consensus / local-only 下触发first-responder永不触发。

每次拒绝(forbidden)与超时都会写一条 stderr 面包屑(writeForbiddenStderr、超时定时器回调),因为审计环与 SSE 事件都是瞬时观测面——操作员 tail daemon stderr 时若无这些行,就完全看不到投票被拒(permissionMediator.ts)。

结算顺序:resolveEntry 的清理阶梯

所有裁决路径(投票胜出、超时、会话关闭、哨兵取消)都汇聚到resolveEntry,其清理顺序是一条硬化的不变量(permissionMediator.ts):

  1. clearTimeout—— 保证定时器永远不会在半清理的条目上触发;
  2. pending删除 —— 状态迁移前半段,新投票不再可达;
  3. 广播permission_resolved(尽力而为,emit 失败不阻塞 Promise 结算)——刻意放在写 resolved 之前,使 emit 期间同步重入的投票看到"pending 与 resolved 均空"(静默 false),与旧行为逐字节兼容;
  4. 写入 resolved FIFO;
  5. 写审计recordResolved
  6. 最后结算 Promise —— 重入回调看到的是自洽状态。

幂等保护:入口先做this.pending.get(requestId) !== pending身份检查,覆盖"定时器与末票在同一 tick 竞争"和"LRU 淘汰后复用同一 requestId"两种情形。

forgetSession()

在会话关闭、逐出、bridge 关停时被调用。对每个record.sessionId === sessionId的 pending 条目:

  1. 取消超时定时器;
  2. 将 Promise 结算为{kind:'cancelled', reason:'session_closed'}
  3. 追加审计记录;
  4. pending移除。

实现先快照匹配键再逐个结算,避免迭代中修改 Map(permissionMediator.ts)。bridge 的会话拆除路径总是在 channel-kill 窗口之前调用forgetSession,保证 pending 权限不会比会话活得更久。

取消哨兵:一条刻意的跨策略逃生通道

CANCEL_VOTE_SENTINEL = '__cancelled__'(permissionMediator.ts)。桥接层把投票者的{outcome:'cancelled'}在调用mediator.vote之前映射为该哨兵;中介器识别哨兵时先于策略分派处理——无论clientId、loopback 与否、是否在投票者集合内,local-onlyconsensusdaemon 都能被任何投出{outcome:'cancelled'}的投票方取消。源码注释明确这是有意的:投票者 cancel 是 agent 侧的 abort 路径;若威胁模型要求策略门控的 cancel,那属于未来的契约变更,"documented here so a future maintainer doesn't accidentally 'fix' the bypass"(permissionMediator.ts)。

为防止哨兵被注入,有两道守卫

  1. bridge 层拒绝线路票respondToSessionPermission检查response.outcome.outcome === 'selected' && optionId === CANCEL_VOTE_SENTINEL,抛InvalidPermissionOptionError——恶意线路客户端不能通过谎报optionId注入 cancel(bridge.ts);
  2. mediator 层拒绝 agent 侧冲突request()发现allowedOptionIds包含哨兵字符串时,同步抛出CancelSentinelCollisionError——agent 合法地以'__cancelled__'作为选项标签时不能被伪装成取消(permissionMediator.ts)。

两个错误类与PermissionForbiddenErrorInvalidClientIdError一起定义在 bridgeErrors.ts。

审计侧还保留了 wire 无法表达的区分:agent_cancelled(agent 在任何投票者裁决前取消了底层 prompt)与voter_cancelled(投票者投出 cancelled)在线上的PermissionResolution形状相同,但审计日志里的PermissionDecisionReason是不同的类型(permissionMediator.ts)——这是为取证保留的、刻意与冻结契约共存的"重载"。

状态与生命周期要点

  • 策略按请求捕获:未来即使提供 daemon 级策略热切换,在途请求的规则也不会变;
  • votersAtIssue快照语义(consensus):请求发出后才连上的客户端可以投票,但其clientId若未在 issue 时刻注册于会话,投票会被拒绝为designated_mismatch(复用 designated 的原因码以保持契约封闭);
  • resolved 条目最多在 FIFO 中存活 512 条,淘汰后重复投票得到{unknown_request}
  • 事件面:permission_partial_vote仅 consensus;permission_forbidden仅 designated / consensus / local-only;
  • 中介器通过pendingCountgetter 向 bridge 暴露全 daemon 在途 pending 数,供操作员发现卡死的 FIFO。

配置与能力声明

来源开关作用
settings.jsonpolicy.permissionStrategy当前生效的中介器策略。
settings.jsonpolicy.consensusQuorumconsensus 的法定人数 N。
BridgeOptionspermissionPolicypermissionConsensusQuorumpermissionAudit编程式覆盖。
能力标签permission_mediation(恒定存在;modes: ['first-responder', 'designated', 'consensus', 'local-only']本构建支持的策略集合。
能力包络policy.permission该 daemon 当前运行的策略。

能力标签在 capabilities.ts 注册:

permission_mediation: { since: 'v1', modes: ['first-responder', 'designated', 'consensus', 'local-only'], },

/capabilities让 SDK 客户端可以在依赖permission_partial_vote/permission_forbidden事件之前做预检。默认行为:policy.permissionStrategy未显式配置时,daemon 使用first-responderdesignatedconsensuslocal-only只有在settings.json中设置后才生效。

启动期策略校验

runQwenServe.validatePolicyConfig(policyConfig)(run-qwen-serve.ts)在启动时校验合并后的settings.jsonpolicy.*段,对操作失误抛InvalidPolicyConfigError

  • policy.permissionStrategy存在但不在四个受支持字面量内。合法集合在运行期从SERVE_CAPABILITY_REGISTRY.permission_mediation.modes派生——能力广播、settings schema 枚举、PermissionPolicy联合类型与运行期校验通过单一编辑点保持一致;
  • policy.consensusQuorum存在但不是正整数。

还有一个软告警:当consensusQuorum已设置而permissionStrategy !== 'consensus'时,启动时向 stderr 发警告,并且该值被丢弃(返回undefined),使公开契约与警告内容一致。

InvalidPolicyConfigError(run-qwen-serve.ts)导出供instanceof判定:runQwenServe用它区分操作者配置错误(重新抛出、显式启动失败)与 settings 读取 I/O 故障(回退默认值)。测试用例在 run-qwen-serve.test.ts 中锁定了这些契约(合法字面量逐一举例、0/-1/1.5/NaN均被拒、非 consensus 策略下的警告与值丢弃)。

共识法定人数:默认公式与 M=2 边界

启用consensus且未设置policy.consensusQuorum时,中介器用consensusQuorumFor计算N = floor(M/2) + 1(permissionMediator.ts):

Math.max(1, Math.floor(m / 2) + 1);
M(votersAtIssue.size默认 N行为
11单一投票者立即裁决。
22需要全票一致(unanimous)。
32简单多数。
43超过半数(supermajority)。
53简单多数。
64超过半数(supermajority)。

M = 2 的边界:分裂投票(A 选 X、B 选 Y)时没有任何选项能达到一致,请求只能通过投票者取消、会话取消或可选的交互超时解决。permissionResponseTimeoutMs默认禁用;配置后,分裂状态会在该期限以{cancelled, timeout}解决。中介器会对"需要全票"的 consensus 请求向 stderr 写一条每中介器生命周期仅一次的去重面包屑——因为 M=2 时全票一致是常态而非罕见边界,逐请求打印会在繁忙会话里刷屏(permissionMediator.ts)。

若 operator 希望 M = 2 时"先投先赢",可显式设置policy.consensusQuorum: 1;更严格的配置(如 M = 4 要求全票)用同一字段。设置覆盖值时会被封顶到 MMath.min(override, Math.max(m, 1))),防止 N > M 的死配置造成死锁;封顶生效时同样写一次性 stderr 面包屑。

安全说明:v1 客户端身份是自报的

X-Qwen-Client-Id由 HTTP 客户端提供。v1 中 daemon 校验其格式([A-Za-z0-9._:-]{1,128})并在clientIds中跟踪已附加的客户端 id,但不做持有证明。任何能在 SSE 流中观察到originatorClientId的客户端,都可以用同一个 id 注册并在后续请求中冒充该发起者。

对各策略的影响:

  • first-responder:不受影响,因为判定不依赖身份;
  • designated:可被远端客户端复用originatorClientId冒充;
  • consensus:门控在 issue 时刻的votersAtIssue快照上;若被冒充的 id 在请求发出时已附加到会话,它就可以投票;
  • local-only:免疫 id 冒充,因为fromLoopback: boolean由 daemon 从连接远端地址盖章,而非客户端提供。

未来的 pair-token 机制将从POST /session签发 per-session 密钥,并要求designated/consensus投票携带——该机制在 v1 中不存在。

跨连接投票路由

两条投票传输路径

投票可通过两条独立传输路径到达中介器:

  1. ACP 传输(同连接响应)permission_requestbridge 事件以session/request_permissionJSON-RPC 请求的形式投递到所属连接的 session 级 SSE/WS 流。客户端在同一连接上用 JSON-RPC 响应作答。dispatcher 的resolveClientResponse把连接本地 JSON-RPC id 映射回 bridge 的requestId,调用bridge.respondToSessionPermission(dispatch.ts);
  2. REST API(跨连接):任何 HTTP 客户端——包括不同 ACP 连接上的、甚至根本没有 ACP 连接的——都可以通过POST /session/:id/permission/:requestId投票。遗留路由POST /permission/:requestId(URL 中无 session)先用peekSessionFor(requestId)解析出会话,再委托到同一条respondToSessionPermission路径(routes/permission.ts)。

REST 路由在构造 bridge context 时用detectFromLoopback(req)读取内核级的req.socket.remoteAddress盖章 loopback 位(routes/permission.ts),两条路径都不从可伪造的头部(X-Forwarded-ForForwarded等)推导 loopback

连接本地的两级 ID 方案

ACP 传输用两级 id 映射线路与 bridge:

ID 格式作用域用途
JSON-RPC 消息 id_qwen_perm_N(字符串,每连接单调)连接本地在 session 流上关联 JSON-RPC 请求-响应对。
Bridge 请求 id不透明字符串(agent/中介器生成的 UUID)daemon 全局在所有路由与中介器的 pending/resolved Map 中标识该权限请求。

bridge 请求 id 通过_meta厂商扩展透传,客户端走 REST 投票时可以携带它:

{ "method": "session/request_permission", "id": "_qwen_perm_3", "params": { "sessionId": "<session-id>", "toolCall": { "name": "shell" }, "options": [{ "optionId": "allow", "name": "Allow" }], "_meta": { "qwen": { "requestId": "<bridge-request-id>" } } } }

连接把映射存于conn.pending: Map<jsonRpcId, PendingClientRequest>,其中PendingClientRequest.bridgeRequestId就是 bridge 级 id(见 connection-registry.ts)。

投票授权检查链

respondToSessionPermission(sessionId, requestId, response, context)严格顺序执行以下检查(bridge.ts):

  1. 会话存在性——sessionId指向的会话必须存活(byId.has(sessionId)),否则SessionNotFoundError
  2. 跨会话拒绝——peekSessionFor(requestId)解析该请求实际所属的会话;若属于另一个会话,投票被拒(返回false/ 404),且不暴露会话成员信息;
  3. 未知请求护栏——peekSessionFor返回undefined(请求已超时、被 FIFO 淘汰或从未存在)时,在任何clientId校验之前拒绝(false/ 404)。这是防 oracle 攻击的:没有它,用伪造clientId的探测就能区分"该会话有此客户端"(通过校验 → 404)与"客户端未知"(InvalidClientIdError→ 400);
  4. 客户端身份校验——resolveTrustedClientId(entry, context?.clientId)验证 REST 携带的X-Qwen-Client-Id(或 ACP 的 bridge 盖章 clientId)已注册在该会话的clientIds中。匿名投票(clientId === undefined)放行,交给策略分派处理;未注册 id 抛InvalidClientIdError(路由映射为 400);
  5. 取消哨兵强制——线路票{outcome:'selected', optionId:'__cancelled__'}InvalidPermissionOptionError拒绝,防止哨兵注入;
  6. 中介器vote()分派——校验通过的投票转入permissionMediator.vote(...),按当前策略处理。

ACP 传输的投票响应格式

客户端对session/request_permission的响应是标准 JSON-RPC:

接受(选择选项)

{ "jsonrpc": "2.0", "id": "_qwen_perm_3", "result": { "outcome": { "outcome": "selected", "optionId": "allow" } } }

取消

{ "jsonrpc": "2.0", "id": "_qwen_perm_3", "result": { "outcome": { "outcome": "cancelled" } } }

错误响应(dispatcher 映射为 cancel):

{ "jsonrpc": "2.0", "id": "_qwen_perm_3", "error": { "code": -32000, "message": "user declined" } }

resolveClientResponse中的失败恢复

bridge.respondToSessionPermission抛出(例如投票体畸形)时,dispatcher 回退到显式取消(cancelAbandonedPermission),保证中介器不会永久卡住(dispatch.ts)。若投票与取消双双抛出(双重失败),pending条目被保留,留待连接拆除时的abandonPendingForSession重试。此外,若respondToSessionPermission返回false(无对应 pending——重复或竞态投票),registry 条目同样不删除:删除会把"没有 pending"与"bridge 拒绝了一个存在的投票"混为一谈,并使另一条连接上的合法重试收到误导性的 "no pending" 错误(dispatch.ts 中该分支的注释明确了此契约)。

注意事项与已知限制

  • 取消哨兵在策略分派之前路由,这是设计——local-onlyconsensusdaemon 都能被任何投{outcome:'cancelled'}的投票方取消。该逃生通道在 permissionMediator.ts 中有文档说明,是 agent 侧的 abort 路径;若部署需要策略门控的 cancel,应等待未来契约变更,不要用路由级检查打补丁掩盖;
  • designatedconsensusPermissionVoteOutcome中重载designated_mismatch:中介器写的是分开的审计记录,但线上形状只有一个,未来协议版本可能拆分该联合;
  • **匿名投票者(无X-Qwen-Client-Id)**只在first-responderlocal-only(loopback)下被接受;designatedconsensus拒绝匿名投票(consensus 路径中clientId === undefined直接落入designated_mismatch分支);
  • votersAtIssue快照语义意味着客户端集合频繁变动的 consensus 部署中,合法客户端可能因为在请求发出之后才连接而被拒。operator 应在发出变更评审 prompt 之前预注册协作者 client id。

相关文档与源码索引

  • 依赖文档:03-acp-bridge.md(bridge 如何把BridgeClient.requestPermission接到mediator.request)、10-event-bus.md(partial-vote 与 forbidden 帧如何到达客户端)、09-event-schema.md(permission_*事件载荷契约)、08-session-lifecycle.md(forgetSession()在每次会话终止时被调用)、02-serve-runtime.md(PermissionAuditRing,512 条审计记录的 FIFO);
  • 契约冻结文件:permission.ts;
  • 中介器实现与测试:permissionMediator.ts、permissionMediator.test.ts;
  • 桥接层投票路由与授权:bridge.ts;
  • 错误类型:bridgeErrors.ts(CancelSentinelCollisionErrorInvalidPermissionOptionErrorPermissionForbiddenErrorInvalidClientIdError);
  • ACP 传输投票路径:acp-http/dispatch.ts(resolveClientResponse)、acp-http/connection-registry.ts(AcpConnection.pending连接本地请求映射);
  • REST 投票路由:routes/permission.ts;审计环与发布器:permission-audit.ts;
  • 启动期校验:run-qwen-serve.ts 及其测试 run-qwen-serve.test.ts。

上述机制对应 issue #4175 的 F3 系列工作;从源码结构看,中介器以"单一类 +switch(entry.policy)"取代了策略子类,注释中说明每个策略分支只有 5–15 行,子类化只会增加样板代码而无实质收益。

【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 7:04:43

微电网VSG控制策略与Simulink建模实践

1. 微电网逆变并网系统概述微电网作为分布式能源接入的重要形式&#xff0c;其核心挑战在于如何实现逆变器与电网的稳定并联运行。传统PQ控制策略在电网强度较弱时存在稳定性问题&#xff0c;而VSG&#xff08;Virtual Synchronous Generator&#xff0c;虚拟同步机&#xff09…

作者头像 李华
网站建设 2026/9/13 6:59:49

HBase+MapReduce共享单车数据分析实战

简介&#xff1a;这是一份面向计算机相关专业学生、教师及初学者的高分毕业设计级共享单车大数据分析项目&#xff0c;基于Java与Hadoop生态实现数据采集、存储、统计与可视化全流程。项目将CSV格式的单车使用记录&#xff08;含起止时间、起终点等&#xff09;导入HBase&#…

作者头像 李华
网站建设 2026/9/13 6:58:37

LabVIEW在海洋气象观测中的创新应用与实践

1. 项目概述&#xff1a;当LabVIEW遇上海洋气象观测十年前我第一次接触海洋气象观测项目时&#xff0c;还在用传统的数据采集卡配合C语言写控制程序。直到某次台风监测任务中&#xff0c;设备在甲板剧烈摇晃下出现数据丢包&#xff0c;我才意识到需要更可靠的解决方案。LabVIEW…

作者头像 李华
网站建设 2026/9/13 6:57:26

text-to-CAD:从自然语言到可制造STEP模型的工业级映射

1. 什么是text-to-CAD&#xff1f;它不是“让AI画CAD”&#xff0c;而是重构设计工作流的底层入口text-to-CAD这个标题乍看像AI绘图的延伸——输入“一个带M6螺纹孔的铝制支架&#xff0c;长120mm宽60mm厚10mm&#xff0c;底部有4个Φ8安装孔”&#xff0c;就自动生成DWG文件。…

作者头像 李华
网站建设 2026/9/13 6:56:57

Jenkins Pipeline与Kubernetes实现云原生CI/CD实践

1. 项目概述在云原生技术栈中&#xff0c;Jenkins Pipeline与Kubernetes的结合已经成为现代CI/CD流水线的标准实践。这个项目展示了如何利用Jenkins的声明式Pipeline来自动化Kubernetes工作负载的更新过程&#xff0c;实现从代码提交到生产部署的完整自动化流程。我最近在客户现…

作者头像 李华