Skip to content

原生目录选择器 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构定位
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能与可靠性特征
  8. 故障排查指南
  9. 结论

引言 ​

本文件聚焦“原生目录选择器”能力:扩展端侧边栏的“切换工作目录”弹窗中,当连接的是本机 Agent 时,提供“浏览”按钮调用系统原生文件夹选择对话框;用户确认后,路径回填到输入框,再由后端接口设置 Agent 的工作目录。该能力横跨 Chrome 扩展(Manifest V3)与独立 Node.js Agent 进程,是本地工作区管理的关键入口之一。

项目结构定位 ​

原生目录选择器涉及两个代码域:

  • Agent 端:负责实际弹出系统原生目录选择框、解析平台命令、处理超时与降级。
  • 扩展端:负责在 Side Panel 中渲染“切换工作目录”弹窗、判断是否允许使用原生浏览按钮、发起 HTTP 请求并处理返回结果。
graph TB
subgraph "Chrome 扩展端"
SP["Side Panel<br/>workspace-panel.js"]
WM["Workspace Manager<br/>workspace-manager.js"]
end
subgraph "Agent 端"
API["HTTP Server<br/>server.js"]
DP["Directory Picker<br/>directory-picker.js"]
OS["操作系统 GUI<br/>macOS / Windows / Linux"]
end
SP --> WM
WM --> API
API --> DP
DP --> OS

图表来源

章节来源

核心组件 ​

  • pickDirectoryNative:Agent 端对外暴露的原生目录选择函数,封装跨平台命令构造、子进程执行、超时保护与结果归一化。
  • buildPickCommand:根据平台生成对应命令及参数,并在 Linux 上支持 zenity/kdialog 回退。
  • /api/config/pick-directory:Agent HTTP 路由,负责来源 IP 校验、调用 pickDirectoryNative、记录日志并返回统一结果对象。
  • pickDirectory:扩展端 Workspace Manager 中的轻量封装,直接 POST 到 Agent 的 /api/config/pick-directory。
  • “切换工作目录”弹窗:扩展端 UI 层,检测是否连接本机 Agent,决定是否显示“浏览”按钮,并根据返回结果填充路径或提示不可用。

章节来源

架构总览 ​

从用户点击“浏览”到最终路径落盘,整体流程如下:

sequenceDiagram
participant User as "用户"
participant Panel as "Side Panel<br/>workspace-panel.js"
participant Manager as "Workspace Manager<br/>workspace-manager.js"
participant Server as "Agent HTTP Server<br/>server.js"
participant Picker as "Directory Picker<br/>directory-picker.js"
participant OS as "系统 GUI"
User->>Panel : 打开“切换工作目录”弹窗
Panel->>Panel : 检测 Agent URL 是否为本机
Panel->>Manager : 点击“浏览”调用 pickDirectory()
Manager->>Server : POST /api/config/pick-directory
Server->>Server : 校验 remoteAddress 是否 loopback
Server->>Picker : pickDirectoryNative(title)
Picker->>OS : 调用 osascript / powershell / zenity / kdialog
OS-->>Picker : 输出选中路径或空输出
Picker-->>Server : {success, path, available}
Server-->>Manager : 返回相同结构体
Manager-->>Panel : 返回结果
Panel->>Panel : 根据 success/available/error 填充或提示

图表来源

详细组件分析 ​

Agent 端:原生目录选择实现 ​

directory-picker.js 是原生目录选择器的核心实现,职责包括:

  • 按平台构造命令:
    • macOS:通过 osascript 调用 AppleScript 的 choose folder with prompt。
    • Windows:通过 PowerShell 的 FolderBrowserDialog,并使用 -EncodedCommand 避免命令行注入。
    • Linux:优先 zenity --file-selection --directory,失败后回退 kdialog --getexistingdirectory。
  • 安全处理标题:对传入 title 进行引号、反斜杠等字符清理,防止脚本注入。
  • 子进程生命周期管理:
    • 默认 120 秒悬挂超时,超时后 kill 子进程并返回 timeout 错误。
    • 捕获 spawn 失败(如 ENOENT),用于判定环境是否可用。
  • 路径归一化:去除首尾空白与 POSIX 路径尾斜杠(根目录 / 除外)。
  • 统一返回结构:
    • 成功选中:{ success: true, path, available: true }
    • 用户取消:{ success: true, path: null, available: true }
    • 无 GUI/工具不可用:{ success: false, path: null, available: false }
    • 其他错误:{ success: false, path: null, available: true, error }
flowchart TD
Start(["进入 pickDirectoryNative"]) --> CheckLinux["Linux 且无 DISPLAY/WAYLAND_DISPLAY?"]
CheckLinux --> |是| NoGui["返回 available:false"]
CheckLinux --> |否| BuildCmd["buildPickCommand(title, platform)"]
BuildCmd --> Attempts{"是否有 fallback?"}
Attempts --> |有| TryFallback["依次尝试主命令与回退命令"]
Attempts --> |无| TryMain["仅尝试主命令"]
TryMain --> RunOnce["runPickOnce(command, args, timeout)"]
TryFallback --> RunOnce
RunOnce --> SpawnError{"spawn 失败?"}
SpawnError --> |是| NextAttempt["尝试下一个候选命令"]
SpawnError --> |否| Timeout{"是否超时?"}
Timeout --> |是| ReturnTimeout["返回 success:false, error:'timeout'"]
Timeout --> |否| Normalize["normalizePickedPath(stdout)"]
Normalize --> HasPath{"path 非空?"}
HasPath --> |是| ReturnSuccess["返回 success:true, path, available:true"]
HasPath --> |否| ReturnCancel["返回 success:true, path:null, available:true"]
NextAttempt --> Attempts
NoGui --> End(["结束"])
ReturnSuccess --> End
ReturnCancel --> End
ReturnTimeout --> End

图表来源

章节来源

Agent 端:HTTP 路由与安全门控 ​

/api/config/pick-directory 是扩展端调用的唯一入口,关键行为:

  • 仅接受 POST 请求。
  • 严格校验来源 IP:仅允许 127.0.0.1、::1、::ffff:127.0.0.1、localhost。
  • 远端来源不返回 403/404,而是以 available:false 降级返回,让扩展端隐藏“浏览”按钮。
  • 调用 pickDirectoryNative,成功选中时记录系统日志。
  • 异常路径记录错误日志并返回 error 字段。
flowchart TD
Req["收到 POST /api/config/pick-directory"] --> GetIP["读取 remoteAddress"]
GetIP --> IsLocal{"是否 loopback?"}
IsLocal --> |否| Block["记录 security 日志<br/>返回 available:false"]
IsLocal --> |是| CallPicker["调用 pickDirectoryNative(title)"]
CallPicker --> PickOk{"result.success && result.path?"}
PickOk --> |是| LogPick["记录 directory_picked 日志"]
PickOk --> |否| SkipLog["跳过日志"]
LogPick --> Respond["返回 JSON 结果"]
SkipLog --> Respond
Block --> Respond

图表来源

章节来源

扩展端:UI 与调用链 ​

扩展端通过 Side Panel 的“切换工作目录”弹窗触发原生目录选择:

  • 弹窗初始化时解析 Agent URL 的 hostname,若为 localhost、127.0.0.1 或 [::1],则启用“浏览”按钮。
  • 点击“浏览”后调用 workspace-manager.js 的 pickDirectory(),后者直接 POST 到 /api/config/pick-directory。
  • 根据返回结果:
    • success && path:将路径填入输入框并聚焦。
    • success && !path:视为用户取消,静默处理。
    • available === false:提示“浏览不可用”。
    • 其他错误:提示具体 error。
sequenceDiagram
participant UI as "Switch Workdir Dialog"
participant WM as "workspace-manager.pickDirectory"
participant Fetch as "fetch('/api/config/pick-directory')"
participant Resp as "响应处理"
UI->>WM : pickDirectory()
WM->>Fetch : POST JSON {}
Fetch-->>WM : JSON {success,path,available,error}
WM-->>UI : 返回结果
UI->>Resp : 根据 success/available/error 更新 UI

图表来源

章节来源

依赖关系分析 ​

  • server.js 依赖 directory-picker.js,并通过 HTTP 路由暴露给扩展端。
  • workspace-manager.js 依赖 Agent HTTP 配置与 token,封装所有 Agent 相关请求。
  • workspace-panel.js 依赖 workspace-manager.js,并负责 UI 交互与状态展示。
  • 测试用例覆盖命令构造、路径归一化、取消行为、无 GUI 降级、回退机制与超时保护。
graph LR
WP["workspace-panel.js"] --> WM["workspace-manager.js"]
WM --> SJS["server.js"]
SJS --> DPK["directory-picker.js"]
DPK --> OS["系统 GUI"]

图表来源

章节来源

性能与可靠性特征 ​

  • 超时保护:默认 120 秒,避免用户长时间不操作导致子进程常驻。
  • 多候选命令:Linux 下 zenity 失败自动回退 kdialog,提升可用性。
  • 无 GUI 快速降级:Linux 无 DISPLAY/WAYLAND_DISPLAY 时直接返回 available:false,减少无效报错。
  • 标题安全清洗:防止通过 title 注入 shell 或脚本。
  • 路径归一化:消除 POSIX 路径尾斜杠与换行,保证前端一致性。
  • 安全门控:仅本机 loopback 可调用,远端请求以 available:false 降级,避免误暴露。

[本节为通用特性说明,不直接分析具体代码片段]

故障排查指南 ​

常见现象与定位建议:

  • “浏览”按钮未显示:
    • 检查 Agent URL 的 hostname 是否为 localhost、127.0.0.1 或 [::1]。
    • 若连接远端 Agent,预期行为是隐藏浏览按钮。
  • 点击“浏览”提示不可用:
    • macOS:确认 osascript 可用。
    • Windows:确认 PowerShell 与 System.Windows.Forms 可用。
    • Linux:确认存在 DISPLAY/WAYLAND_DISPLAY,且 zenity/kdialog 至少一个可用。
  • 点击“浏览”后无响应:
    • 检查是否触发 120 秒超时,查看返回 error:'timeout'。
    • 检查 Agent 进程是否被终止或端口不可达。
  • 返回路径异常:
    • 检查 stdout 是否包含换行或尾斜杠,directory-picker.js 已做归一化。
    • 确认 Agent 端日志是否记录 directory_picked。

章节来源

结论 ​

原生目录选择器通过 Agent 端跨平台命令封装与扩展端 UI 控制,实现了本机工作目录的安全、直观选择。其设计强调:

  • 平台兼容:macOS、Windows、Linux 均有明确实现与回退策略。
  • 安全边界:仅本机 loopback 可调用,远端请求降级。
  • 用户体验:超时保护、取消静默、路径归一化、错误提示清晰。
  • 可测试性:单元测试覆盖命令构造、路径解析、取消、无 GUI、回退与超时场景。

在实际使用中,若需新增平台或替换底层 GUI 工具,应优先修改 buildPickCommand 与 pickDirectoryNative,并确保测试用例同步覆盖新行为。

[本节为总结性内容,不直接分析具体代码片段]