Files
apes-sidebar/docs/plans/2026-08-23-agent-open-tools-design.md
T
Menghuan1918 68640101e1 feat: sidebar_open tool gates model-driven sidebar opens behind a global setting
New global Side card setting `agentOpenTools` (default off, 常规 group):
when enabled, one `sidebar_open` tool is injected into the model so it can
actively open a local file, a local folder (tree rooted there), or an
HTTP(S) page in the calling session's sidebar.

- host: AgentOpenRegistry + registerOpenTool (src/agent-opens.ts); the tool
  classifies http(s) URLs / stat-driven file/folder paths, refuses other
  schemes, resolves relative paths against the session cwd, and reports
  target-tab-disabled instead of silently no-oping. Gated like
  agentTerminalTools (register/drain on settings watch).
- delivery: /sidebar/ws/agent-opens push (consume-on-send queue, replay on
  attach); the tool result reports `delivered` so queued opens for
  non-active sessions are honest.
- client: Sidebar subscribes and routes pushes to editor (file), folder
  window (meta.dir, tree rooted at the folder — EditorHost), or browser
  (URL) tabs; settings row in 常规 + 20 locale dictionaries.
- tests: agent-opens registry/tool spec, smoke gating + WS route list,
  prefs/plugin-shape defaults, editor-host folder window, side-card rows.
- docs: design doc, AGENTS.md key lists + reference, README feature bullets.
2026-08-24 01:19:43 +08:00

6.8 KiB
Raw Blame History

设计:模型主动在侧边栏打开(sidebar_open 工具 + agentOpenTools 设置)

日期:2026-08-23 · 状态:已实现 · 仓库:omdsh-dev/DSH-better-sidebar

需求

新增一个侧边栏全局设置(Side card 设置页「常规」分组):agentOpenTools,默认关闭。 开启后向模型注入一个工具 sidebar_open,允许模型主动在调用方会话的侧边栏中打开:

  • 本地文件 → 内置 editor tab(按 path 去重,聚焦既有 tab)
  • 本地文件夹 → 内置 editor tab 的文件夹窗口(文件树以该目录为根)
  • HTTP(S) 网页 → 内置 browser tab(URL 预填)

关键决策

1. 作用域 = 调用方 agent 会话

与 terminal_* 工具一致:工具只绑定 exec.agent.session.id,模型不传 sessionId。 子代理(或未激活会话)调用时若无客户端连接,打开请求排队,该会话侧边栏下次可见时重放。 工具结果通过 delivered 如实报告「当前是否已推送到已连接的侧边栏视图」。

2. 投递通道:host→client WS 推送(无 ack 协议)

  • 新增 WS 升级路由 /sidebar/ws/agent-opens?sessionId=...(与 /sidebar/ws/agent-terminals 同模式,过同一 trust fence)。
  • host 侧 AgentOpenRegistry 维护会话级队列 + 已连接视图集合:
    • enqueue:有订阅者 → 即时推送并消费(delivered: true);无订阅者 → 保留队列(delivered: false)。
    • attach:注册订阅者并重放队列(消费即出队),返回 disposer(close/error 时退订)。
    • drainAll:特性关闭时丢弃未投递队列。
  • 消费即出队是关键:browser tab 无 per-URL 去重,重连重放会铸造重复 tab;消费语义保证每个请求恰好推送一次。
  • 不加 ack 回执、不加 /sidebar/api 路由——结果的最佳努力语义(delivered)已足够,符合 KISS 与 terminal 先例。

3. sidebar_open 工具(唯一新增工具)

  • 参数:target(必填:绝对/相对路径,或 http(s) URL)、title(可选:tab 标题)。
  • 执行流程:throwIfAborted → 分类 → 目标 tab 启用检查 → 入队 → 返回规范结果。
  • 分类规则:
    • http:// / https://(大小写不敏感)→ url,默认标题 = hostname;
    • 其他 scheme(file: / javascript: / vscode: 等)→ 报错(Windows 盘符前缀 C:\/C:/ 除外,它是路径不是 scheme);
    • 本地路径 → 相对路径以会话 cwd(sessionCwdOf)解析为绝对路径,stat 定 file/folder;不存在/不可读 → 报错;
    • 目标 tab 类型被用户禁用(tabsEnabled['editor'|'browser'] === false)→ 报错并提示启用(避免客户端静默 no-op)。
  • 输出(canonical JSON,render 纯文本投影):{ kind, target, title, delivered }。
  • 工具描述/错误消息为英文(与 8 个 terminal_* 一致)。

4. 文件夹窗口:editor tab 的 meta.dir

  • 打开文件夹 = openTab({ type: 'editor', path: <dir>, id: 'editor:'+<dir>, title, meta: { dir: true } })。
  • EditorHost 识别 meta.dir === true:不进入 viewer 加载流程,渲染全窗 TreePanel(cwd = tab.path); FileTree 根部即 cwd(fs.tree 接受绝对路径),搜索结果/上传仍受会话工作区约束(已知限制,见下)。
  • meta 为可持久化的 tab 状态(v0.12.0+),sanitizeState 原样保留——刷新后文件夹窗口恢复。
  • 折叠/展开、右键打开、拖拽上传等全部复用 TreePanel 既有能力。

5. 设置与门控

  • 新 prefs 字段 agentOpenTools: boolean(SidebarPrefs + PrefsSchema + 客户端 parsePrefs),默认 false。
  • SideCardSection「常规」分组新增一行 Switch(标题/描述走新 locale key)。
  • host 门控完全拷贝 agentTerminalTools 的 syncToolsGate 模式:
    • 开启 → registerOpenTool(...);关闭 → 注销工具 + agentOpenRegistry.drainAll();
    • 一次 scope.watch 同时驱动两个门控(各持有自己的 disposer,互不干扰);
    • 工具不依赖 node-pty,降级模式下也可用。
  • 客户端防御:Sidebar 的 agent-opens 推送订阅在 prefs.agentOpenTools !== true 时忽略消息(host 已门控,此为双保险)。

6. 与既有拦截开关的关系

  • URL 打开不受 browserInterceptLinks / browserInterceptHttp / browserInterceptHttps 约束——那是用户点击外链的接管开关; 模型显式调用 sidebar_open 只受 browser tab 自身启用开关约束(openTab 既有 gating)。
  • 文件打开不受 interceptOpenPath 影响(那是聊天点击路径的接管开关)。

既有模式复用(零新公共 API)

新事物 复用/镜像的既有模式
agentOpenTools 门控 agentTerminalTools 门控(settings seam watch → 注册/注销)
/sidebar/ws/agent-opens /sidebar/ws/agent-terminals 推送(升级注册、fence、每会话 attach)
客户端推送订阅 Sidebar 的 agent-terminals effect(重连上限 FAILURE_LIMIT、防抖/清理)
文件/URL 打开 service.openFile / openTab({ type:'browser', url })(link-intercept 同款)
文件夹窗口 path-less explorer 的全窗 TreePanel 分支(仅根目录改为 tab.path)

不新增 BetterSidebarService 方法;不改变任何现有路由协议。

已知限制

  • 多浏览器窗口:推送按「有订阅者即发送并消费」执行,多窗口同时连接时后连接的窗口不会收到已消费的请求 (其本体状态由 localStorage 恢复——与 agent-terminals 的全量列表 fan-out 不同,属尽力而为语义)。
  • 文件夹窗口的全局搜索/上传:TreePanel 的搜索盒仍以会话 cwd 为根(fs.search 是有意的工作区限定), 目录内上传经 host 会话工作区校验(目录在 cwd 内可上传,cwd 外拒绝)。
  • 无 ack:delivered: true 只表示「推送已发出」,客户端处理失败(罕见)不重试。
  • 关闭特性不清已打开 tab:与 tab 启用开关语义一致,只注销工具并清空未投递队列。

测试覆盖

  • tests/agent-opens.spec.ts(新):注册表语义(排队/重放/消费/作用域隔离/drain/dispose)+ 工具行为 (分类、相对路径、缺路径、非 http(s) scheme、tab 禁用、schema 校验、无 agent 错误)。
  • tests/smoke.spec.ts:WS 升级路由清单 + settings.get 默认值 + 独立 gating 测试(默认 off → 0 工具;on → 1;off → 注销)。
  • tests/plugin-shape.spec.ts / tests/prefs.spec.ts:schema/parse 默认值 + 全量 prefs 字面量。
  • tests/editor-host.spec.tsx:meta.dir 文件夹窗口(树以目录为根、无编辑器 chrome、不触发 viewer 加载)。
  • tests/locales.spec.ts:zh/en/ja 词典 key 集相等(自动覆盖新键)。