跳到正文
FunCoding

搜索

搜索文档、Skill 和 MCP

审批标识与跨连接投票

区分 ACP 连接内 JSON-RPC ID、bridge requestId 和会话绑定。

审批可以通过当前 ACP transport 回答,也可以由另一个有权限的 HTTP 客户端使用 REST 回答。两条路径最终进入相同的 session-bound mediator 处理。

两套 request ID

标识范围用途
_qwen_perm_N当前 ACP 连接内递增JSON-RPC 请求与响应关联
bridge requestIdbridge 审批标识在 REST 与 mediator 中定位请求

ACP session/request_permission 的 params._meta.qwen.requestId 携带 bridge 标识。REST 投票使用 POST /session/:id/permission/:requestId;不要把连接内 _qwen_perm_N 填进该 URL。

旧 POST /permission/:requestId 通过内部查找确定 session,再委派给同一路径。新客户端优先使用明确 session 的接口,不把旧全局路由当作跨工作区寻址保证。

检查顺序

  1. 地址中的 session 必须仍 live。
  2. requestId 若属于另一 session,拒绝且不披露成员关系。
  3. 未知 requestId 在 clientId 验证前返回未找到,避免通过不同错误探测会话成员。
  4. 显式 clientId 必须已登记到该 session;未提供时交给策略判断。
  5. 拒绝通过 selected optionId 注入内部取消 sentinel。
  6. 按请求创建时的 policy 处理投票。

匿名普通投票仅可能被 first-responder 或 loopback 的 local-only 接受;designated、consensus 拒绝匿名选择。已注册 ID 仍是 v1 自报身份,不具有持有证明。

回答 ACP 请求

在收到的同一 JSON-RPC id 上选择实际公布的 optionId:

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

allow 仅代表该请求实际提供这一选项时的示例。取消使用 outcome.outcome 为 cancelled;JSON-RPC error 响应也被 dispatcher 映射为取消。

内部 CANCEL_VOTE_SENTINEL 值为 cancelled,供 bridge 将显式取消转为 mediator 输入。wire 的 selected optionId 使用该值会拒绝;agent 公布允许选项时包含该 sentinel 也会触发 CancelSentinelCollisionError。

Loopback 按投票请求判断

REST 和 ACP 的 fromLoopback 都取自该次请求 socket remoteAddress,不使用 X-Forwarded-For、Forwarded 等 header。ACP initialize 来自本机,不代表后续从另一连接地址提交的投票也自动被认作本机。

这也意味着通过本地代理到达的请求与直接远端请求有不同 socket 来源;部署方不能仅用自报 clientId 或代理 header 推断操作者身份。

失败恢复

ACP dispatcher 回答审批时若 bridge 抛错,会尝试显式 cancelAbandonedPermission,避免留下无限等待。若原投票和取消都失败,保留 pending 映射供连接最终 teardown 再清理,而不是丢掉最后的恢复线索。

session close、eviction 和 bridge shutdown 都应调用 forgetSession,在 child 的关闭宽限期之前结算该 session 的待审批。不要把 HTTP 响应失败简单视为后台请求已经取消。