s47
Native 能力
产品化4 种 OS 集成策略
~300 行代码25 个工具npm 原生包 + spawn 系统命令 + FFI + 纯 TS 重写 + Stub 模式
与操作系统集成不一定需要写 C++——4 种策略各有适用场景
“The terminal is your canvas; the OS is your palette”
The terminal is your canvas; the OS is your palette.
[ Phase 11: 生产功能 ] · 主题:纯 TS / WASM / 子进程 / Native Addon 与降级链
前置知识
- 需要完成: s46 打包与分发
你将学到
- 四种策略对比:Pure TS / WASM / Spawn 系统命令 / Native Addon 的优劣取舍
- 三档降级(以 ripgrep 为例):system → vendor/builtin → embedded/fallback
- 剪贴板封装:macOS pbcopy / Linux xclip 统一接口
- detectCapabilities:启动时检测系统工具、磁盘、Git 可用性
- 安全考量:PATH 劫持防护、findExecutable 命令名解析
问题场景
终端 Agent 既要跨平台,又要快:全量 JS 扫仓库可能太慢;图像处理、diff 高亮、搜索等场景需要调用 OS 或原生库。另一方面,企业环境可能没有预装工具,或安全策略禁止任意二进制。你需要策略矩阵 + 明确降级。
设计决策
四种策略对比
| 策略 | 优点 | 缺点 | 典型用途 |
|---|---|---|---|
| Pure TS | 无原生依赖、易测试 | CPU 重时慢 | color-diff、轻量解析 |
| WASM | 可移植、接近原生性能 | 体积、初始化成本 | 加密、解析器、图像解码(视场景) |
| Spawn 系统命令 | 利用用户已装工具(如 rg) | PATH/版本差异、Windows 路径 | ripgrep、git |
| Native Addon + FFI | 极致性能、系统 API | 编译矩阵、升级痛苦 | sharp、macOS Carbon/原生剪贴板 |
选择原则:默认 Pure TS;瓶颈再 WASM;再不行 spawn;最后才引入 addon(维护成本高)。
三档降级(以 ripgrep 为例)
- system:用户强制使用 PATH 上的
rg(需注意 PATH 劫持,应用命令名解析而非当前目录可执行文件)。 - vendor / builtin:随包携带各平台
rg二进制(arch-platform目录布局)。 - embedded / fallback:在 bundled 模式下用 同一可执行文件 以不同
argv0嵌入调用(如 Bun internal),否则可退到纯 TS grep(慢但可用)。
剪贴板
- macOS:
pbcopy/pbpaste - Linux:
xclip或wl-copy/wl-paste(Wayland) - 统一封装
readClipboard/writeClipboard,失败时返回明确错误码
detectCapabilities
启动或首次使用时检测:
- 可选命令是否存在(
which/where) - 磁盘可写、Git 是否可用
- 是否沙箱 / CI(影响 spawn 权限)
结果缓存,避免每条消息都探测。
实现要点
ripgrepCommand():返回command、args、argv0,供spawn统一使用。- memoize:配置级检测只做一次;环境变量变化需使缓存失效或进程级固定。
- 安全:
findExecutable后对 用户显式要求 system rg 仍只用rg名字执行,避免 cwd 下的恶意同名文件(见实现内注释)。 - EAGAIN 等:Docker/CI 线程不足时 stderr 可能提示资源暂不可用,应重试或限并发。
运行验证
cd agents/s47-native-capabilities
npm run dev
# 1. 观察能力检测结果
# → [capabilities] git: ✓ (system)
# → [capabilities] rg: ✓ (system) / ✗ (fallback to builtin grep)
# → [capabilities] clipboard: ✓ (pbcopy) / ✗
# 2. 验证 ripgrep 三档降级
# 有系统 rg → 使用 system rg
# 无系统 rg → 使用 vendor/ripgrep 或内置 fallback
which rg && echo "system rg available"
# 3. 测试搜索功能
# > 搜索项目中所有 TODO 注释
# → Agent 调用 ripgrep(或 fallback)执行搜索
# → 结果正确返回,不因工具缺失而崩溃
# 4. 验证降级可观测
# → 日志记录当前使用的搜索策略档位
对照 Claude Code 表格
| 概念 | Claude Code 中的位置 | 说明 |
|---|---|---|
| Ripgrep 三档 | src/utils/ripgrep.ts — getRipgrepConfig | system(USE_BUILTIN_RIPGREP falsy 时找系统 rg)→ embedded(bundled)→ builtin(vendor/ripgrep) |
| 多包 monorepo | packages/ 下多个包 | 能力按包拆分,主 CLI 聚合 |
| 平台抽象 | getPlatform、execFile 封装 | Windows / Unix 路径与可执行扩展名 |
| 能力与健康检查 | Doctor + doctorDiagnostic | 与「遥测与诊断」章节联动 |
深入思考
- 为何 embedded 用
process.execPath+argv0: 'rg'? 单文件/自包含运行时把 rg 编译进宿主,避免再带一份独立二进制路径。 - WASM vs spawn:搜索类任务往往已有成熟
rg,spawn 比自研 WASM 维护成本更低;WASM 更适合算法固定、无系统工具的场景。 - Native addon 与 CI:
sharp等需预编译二进制,Alpine musl 与 glibc 不兼容——要在文档中标明。 - 降级要可观测:记录当前档位(system/builtin/embedded),否则用户报「慢」时无法复现。
练习
- 画出
getRipgrepConfig决策树:环境变量 → bundled → vendor。 - 实现一个最小
detectCapabilities():返回hasGit、hasRg、clipboard可用性。
下一课预告
s48 — 遥测与诊断:启动剖析、Doctor 健康检查、采样遥测与结构化诊断报告——最后一课收官。