- 审批卡尊重滚动意图,等长切换回底,排队超时按钮修活 - 带图编辑提示不静默丢,删除目标回写,切换文案消歧 - 会话键盘可达+搜索态重命名,筛选计数口径对齐 - 子任务提示正名,看板加载态 - 主题跟随系统,表单脏态拦截,密钥显隐留空提示
82 lines
5.1 KiB
Markdown
82 lines
5.1 KiB
Markdown
# S2 子代理审批语义勘察(只读,2026-10-03)
|
||
|
||
> 结论先行:任务前提不成立。sub_agent.rs grep approval 零命中≠语义空白——审批以
|
||
> 「无通道→一律拒绝(fail-closed)」实现,命名是 deny 非 approval。**非安全债**。
|
||
|
||
## 1. ToolExecPolicy::SubAgent 分流实况
|
||
|
||
`policy` 入参 → `is_sub_agent = matches!(policy, ToolExecPolicy::SubAgent)`
|
||
(src-tauri/src/commands/ai/audit/mod.rs:474-477;策略文档 mod.rs:100-112)
|
||
|
||
| 路径 | SubAgent 行为 | 证据 |
|
||
|---|---|---|
|
||
| spawn 入口白名单 | 非 Low 工具根本不进子 tool_defs,恒剔 spawn_agent | sub_agent.rs:210-251 |
|
||
| 路径授权 NeedsAuth | 就地拒绝(sub_agent_path_denied) | mod.rs:618-622 |
|
||
| 路径黑名单 Denied | 就地拒绝(同上收集) | mod.rs:633-636, 676-682 |
|
||
| Medium/High 风险 | 就地拒绝,**在 auto_exec_mode 判定之前,all 档也不放行** | mod.rs:795-803 |
|
||
| Low + 已授权 | 正常并行执行(与主会话同路径,含缓存/去重/信任) | mod.rs:774, 972+ |
|
||
|
||
拒绝形态 = `deny_sub_agent_tool` 单点(mod.rs:117-148):结构化 envelope
|
||
`{status:rejected, reason:sub_agent_denied}` + push tool_result + emit
|
||
AiToolCallCompleted + 审计 decided_by=sub_agent_denied。
|
||
|
||
**挂起对子会话实际不能工作**:SubAgent 策略下 pending 恒 0(不 insert);
|
||
子循环断言 pending>0 即 Failed 终止(sub_agent.rs:577-587)。即便 insert 也无
|
||
人消费——try_continue_agent_loop 是主 loop 恢复器(agentic/mod.rs:1888),
|
||
子 loop 是 spawn_agent handler 内同步 for-round 循环,且子会话跑完即从
|
||
per_conv 移除(sub_agent.rs:348),无任何恢复入口。
|
||
|
||
## 2. 若复用主会话审批链路到子会话:缺口清单
|
||
|
||
- 事件路由:AiApprovalRequired 带 conversation_id,前端 per-conv 幂等收集
|
||
(useAiToolEvents.ts:60-87)本可收;但子会话不在侧栏(mod.rs:235 注释
|
||
「挂父不挂子」),审批卡挂 child conv 下无处展示。
|
||
- 批准续跑:ai_approve 批准后调 try_continue_agent_loop(conv_id)
|
||
(commands/ai/commands/chat.rs:1051)恢复主 loop;子 loop 无对应机制——
|
||
deadline/tokens/fail_window/stall 计数全在 run_sub_agent_loop_inner 栈上,
|
||
挂起即栈销毁,resume 需整套状态快照/重建。
|
||
- 拒绝终止:主会话拒绝=replace tool_result+emit AiApprovalResult
|
||
(chat.rs:1004-1034);子会话侧缺 ① 不 emit AiAgentFinished 终态卡
|
||
(sub_agent.rs:320 仅单一出口处发)② 不落库子轨迹(:342)③ 不清
|
||
per_conv 条目(:348)④ payload 固定 user_denied 无子代理语义。
|
||
- 超时:arm_approval_timeout_timers 扫 pending 表可覆盖子条目
|
||
(mod.rs:826),但超时收尾不含子循环 truncated 终态,会留孤儿。
|
||
|
||
## 3. 现状语义定性
|
||
|
||
**默认拒绝(fail-closed)**:入口钳制 + 运行期就地处决双层,零挂起零放行,
|
||
Med/High 连 all 档都拒。风险评级:**非安全债**;真正可议是产品语义债——
|
||
子代理遇需审批操作只能拒绝并提示转交主会话(envelope 文案 mod.rs:130),
|
||
无「上报父会话审批」通道。
|
||
|
||
## 4. S2 最小方案骨架
|
||
|
||
**方案A(推荐,颗粒=单代理一轮)**:维持默认拒绝,增强转交闭环。
|
||
- audit/mod.rs:deny envelope 增结构化字段 tool/args/reason(现仅 message 文案)。
|
||
- sub_agent.rs:报告协议(system_prompt 段)要求列「被拒操作清单」→ 父会话
|
||
收报告后由主 loop 决定是否发起审批。
|
||
- 前端无需改。
|
||
|
||
**方案B(真审批通道,超单轮,拆 3 批)**:
|
||
1. 挂起路径:SubAgent 下 Med/High 不 deny 改 insert pending(kind 新增
|
||
SubAgentRisk{parent_conv_id})+ emit AiApprovalRequired(conversation_id
|
||
填父 conv 复用现有弹窗);子循环 pending>0 改返新状态 AwaitingApproval,
|
||
栈上状态快照存 per_conv 新字段(conv_state.rs),不删条目。
|
||
2. resume 通道:ai_approve 识别 kind==SubAgentRisk → 批准则调新函数
|
||
resume_sub_agent_loop(从快照重建上下文续 for-round);拒绝则替换
|
||
tool_result+emit AiAgentFinished(truncated)+落库+清条目。
|
||
3. 收尾:approval_timeout 超时终态补子循环 truncated 收尾;前端 agent 卡
|
||
消费 AiApprovalResult。
|
||
- 涉及文件:audit/mod.rs、agentic/sub_agent.rs、commands/ai/commands/chat.rs、
|
||
agentic/conv_state.rs、src/composables/ai/useAiToolEvents.ts、src/api/types.ts。
|
||
- 拆批理由:子循环暂停/恢复状态快照(deadline/tokens/fail_window/stall 等
|
||
7+ 项)是硬骨头,与事件路由、前端消费耦合度低,可独立验收。
|
||
|
||
## 关键文件索引
|
||
|
||
- E:\wk-lab\devflow\src-tauri\src\commands\ai\audit\mod.rs(策略分流+deny 单点)
|
||
- E:\wk-lab\devflow\src-tauri\src\commands\ai\agentic\sub_agent.rs(白名单钳制+子循环+单一出口)
|
||
- E:\wk-lab\devflow\src-tauri\src\commands\ai\commands\chat.rs(ai_approve/ai_authorize_dir)
|
||
- E:\wk-lab\devflow\src-tauri\src\commands\ai\agentic\helpers.rs(try_continue_agent_loop)
|
||
- E:\wk-lab\devflow\src\composables\ai\useAiToolEvents.ts(前端审批事件消费)
|