Files
deepseek-harness/.agents/notes/implemented/feature/2026-07-23-web-permission-and-approval.zh.md
Turtle f0410d592d feat(web): permission presets and approval answering for the web UI
The web host now composes the sandboxed product path (sandbox-local +
sandbox-policy behind bash-sandbox/fs-sandbox, with user-approval and
permission on top); BootHostOptions.sandbox carries the deployment
defaults (workspace-write + ask).

createApiProxy owns the approval pending registry: a ctx.approval ask
becomes an answerable approval/requested mux frame with a stable rpcId,
replayed verbatim on every mux open until settled; respond routes by the
echoed rpcId, validates the ApprovalResponsePayload audit correlation,
and broadcasts approval/resolved; the ask's abort signal withdraws the
question as cancelled.

session.permissions / session.setPermission project ctx.permission into
a protocol-owned PermissionOption select; idle switches are held
last-write-wins and
flushed into the next prompted turn (the ACP bridge's anchoring
pattern). The shared hasOpenTurn fold moved to dsh-session,
deduplicating the private copies in user-approval, the ACP bridge, and
the proxy.

Client, per the designer draft: a pending approval takes over the
composer (ApprovalPanel replaces the InputBar — amber strip,
justification headline, paired command, one-shot refuse/allow, keyed by
rpcId so a queued second approval remounts live; the resolved frame
restores the composer); the sidebar session row shows an amber
waiting-approval dot that outranks the running ring (manager-tracked
approvalId set, idempotent under mux-open replays, cleared per
connection generation, lit for uninstantiated sessions too); the
permission selector is a composer bottom-row chip over an invisible
native select, with a presentation-only title-case transform
(workspace-write renders as Workspace Write; wire names untouched). Question placeholders stay in the message flow. The
connection fixture mirrors the host behavior for keyless browser
acceptance.
2026-07-24 19:15:04 +08:00

5.7 KiB
Raw Blame History

Agent Note: Web UI 权限预设与审批应答

Status: implemented

English | 中文

问题

Web 承载层启动的是一个不受限的 agent智能体bootHost 组合了 dsh-bash-localdsh-fs-local,因此每个 Web 会话都以完整文件访问权限运行,既无审批通道,也无权限管控——而 ACP 组合早在数月前就已交付完整的沙箱化产品路径(沙箱提供方 + 策略归属 + 受限的 bash/fs + 审批 + 预设。Web 协议契约其实早已预留了对应位置——approval/requested/approval/resolved 的 mux 帧、携带 ApprovalResponsePayloadPOST /api/respond、client 侧的 pendingBuffers——但 host 的 respond 只是一个 stub没有应答者把 ctx.approval 桥接到流上,没有 RPC 暴露权限选择PendingCard 把审批渲染成可见却无法应答的样子。

决策

Web 承载层组合与 acp-agent 相同的沙箱化产品路径:dsh-sandbox-localdsh-sandbox-policydsh-bash-sandboxdsh-fs-sandboxdsh-user-approvaldsh-permission,由 BootHostOptions.sandbox 提供部署默认值(mode,默认 workspace-writeapprovalPolicy,默认 ask)。

createApiProxy 拥有审批 pending 注册表。它的 approval/request waterfall瀑布式事件应答者从会话刚追加的 approval/asked 审计事件中读取审批 id没有审计事件的 ask 属于外部通道,予以委托),为每个问题 mint 一个稳定的 rpcId向每个打开的 mux 流广播可应答的 approval/requested 帧,并在每次 mux 打开时原样重放仍处于 pending 的帧——这正是契约早已承诺的刷新恢复基线。respond 按回显的 rpcId 路由,用既有的 zod schema 校验 ApprovalResponsePayload,将载荷的审计关联与所路由的条目交叉核对,解析应答者,并广播 approval/resolvedask 的中断信号会以 cancelled 撤回该问题。

权限选择依托两个新的一元 RPCsession.permissionssession.setPermission,把 ctx.permission 投影为一个由协议拥有的 PermissionOption DTO沿用 ACP bridge 的先例每个协议拥有自己的呈现形状。无权限的组合提供空的选择项client 隐藏该控件。空闲期的切换以后写胜出last-write-wins的方式保存在 proxy 侧的 pending map 中,并在 agent/prompt-submit 时冲刷,因为旋钮事件必须保持轮次内闭合以支持持久回放;共享的 hasOpenTurn 折叠迁入 dsh-session,取代了 dsh-user-approval、ACP bridge 与 proxy 中各自的私有副本。

在 client 侧,Session 新增了 permissionssetPermission,审批应答则依托运行时的 PendingWait 载体。按照设计师草稿,处于 pending 的审批会接管 composerApprovalPanel 注册为由会话声明的 conversation.composer 链中一个按选择器路由的条目(即 ui-question 模式),以理由标题、配对的命令与一次性的拒绝/允许按钮取代 InputBarui-conversation 契约中的 PendingApproval 领域面拥有 ApprovalResponsePayload 在该载体上的协议编码wire encoding广播的 resolved 帧使该等待落定并恢复 composer。问题占位符仍留在消息流中。侧边栏用一枚琥珀色警示圆点同步呈现这一阻塞状态且其优先级高于表示运行中的圆环manager 跟踪每个会话尚未解决的 approvalId对 mux 打开时的回放幂等,并按连接代次清除,以保证重开后的回放才是权威依据),而非读取 Session 实例因此从未实例化过的会话也能点亮该圆点。composer 底行的 chip 经会话注入面挂载 PermissionSelect 控件。连接 fixture测试前置数据与 host 保持一致:它的常驻审批可应答一次,其权限选择项按会话持久保存。

曾考虑的替代方案

在 Web 协议上复用 ACP 的 session/set_config_option 形状。 不予采纳Web 契约的一元方法注册表(RpcMethodMap + 逐方法的 zod schema是它自成一体的方言一个通用的 config-option 接口会为一个选择项绕开编译期锁定的 schema 表。一对专用方法让两侧都能从签名推导得出。

用一个会话事件承载 pending 审批,而非 proxy 侧注册表。 不予采纳:审批请求是瞬态的交互状态,而非持久的会话数据——approval/asked/decided 审计对已经记录了持久的那一半。持久化 requested 帧会在回放时重新问出已经作废的问题。

仅在存在 mux 订阅者时才注册应答者。 不予采纳pending 条目必须在 client 断连后依然存活(刷新恢复正是要点所在),因此注册表的生命周期长于任何单个流;一个受订阅者门控的应答者,会让在重载窗口期间关闭的 ask 落空。

点击即乐观移除卡片。 不予采纳:广播的 resolved 帧才是真相;点击即移除会隐藏一个因拒绝回执或传输失败而仍然悬置的问题。面板改为在本地禁用其按钮,并在失败时重新启用。

后果

Web 会话现在从受限状态启动(默认 workspace-write + ask),一次沙箱拒绝的升级会以可应答的卡片形式抵达浏览器;部署方可以通过 BootHostOptions.sandbox 放宽或收紧默认值无需触动装配。问题应答已通过同一注册表模式单独交付ui-question 基于问题 pending 表)。权限选择在每次挂载时读取一次;来自另一个 client 切换的实时刷新暂缓实现。覆盖情况proxy 注册表与权限 RPC 的单元测试套件、会话对象与 fixture 的单元测试套件,以及无密钥 Web 冒烟测试在真实浏览器中演练 fixture 模式的审批应答与预设切换。