7.2 KiB
7.2 KiB
终端 Shell 配置加固(引号解析 + 双平台预检 + 友好报错)设计
日期:2026-09-08 | 状态:已确认(用户选定方案二) | 分支:feat/terminal-shell-quoted-args-probe
背景与现状
配置启动终端命令的功能已存在于 main(fd9544f #95 部署级 config.shell/shellArgs + pwsh 探测;c973d6e #125 设置页 terminalShell/terminalShellArgs 用户偏好,覆盖顺序:设置页 > yaml > 平台自动,UI 标签页与模型 terminal_* 工具两终端面共用)。本次不是新增功能,而是加固三处已知缺口:
- 参数解析硬切:
shellOverridesOf(src/index.ts:257)用args.split(/\s+/),引号内空格被切碎(-File "C:\my script\init.ps1"得到 3 个参数)。 - POSIX 无预检:
resolveShellExecutable(src/pty-manager.ts:351)仅 win32 做存在性探测(PATHEXT/PATH/System32/pwsh 目录,找不到抛SidebarError('pty-error', 'shell executable not found: …')→ WS catchws.close(1011, message));POSIX 直接透传 node-pty,配错路径只得到[process exited with code N]。 - 报错不可操作:客户端
TerminalView.tsx:247对任意1011 + reason显示原始英文 reason 横幅,没有本地化说明与"去设置改"指引;粘贴带引号路径("C:\Program Files\…\pwsh.exe")会因引号参与路径解析而失败;shell.get返回启动时解析的 shell,设置页覆盖后新终端标签标题不跟随。
方案取舍
- 方案一(最小):只修引号参数解析。POSIX 配错仍不可诊断,引号路径仍炸——否。
- 方案二(选定):解析 + 双平台预检 + 本地化友好报错 + 标题跟随。不碰声明式设置契约。
- 方案三(全家桶):方案二 + 设置页实时
shell.probeinline 校验。校验型输入行需扩展 declarative settings 契约(service.ts+docs/external-plugin-guide.md),收益/成本比低(保存后开终端立即可见)——YAGNI,不做。
设计
1. 引号感知参数解析 splitShellArgs(src/pty-manager.ts)
- 空白分词;
'…'/"…"分组,组内字符全部字面——\不是转义符(Windows 路径场景直接可用)。代价:参数值本身含引号无法表达,注释注明该取舍。 - 未闭合引号宽容处理:剩余输入并入当前 token(不抛错——设置页输入体验优先)。
- 空引号对
""丢弃,不产生空参数。 shellOverridesOf中args.split(/\s+/).filter(Boolean)替换为splitShellArgs(args)。
2. 路径去引号 unquotePath(src/pty-manager.ts)
- 长度 ≥2 且首尾为成对
"或'时剥掉,否则原样返回。 - 统一入口:
shellOverridesOf与resolveShellExecutable首行均走它(单一实现防两处漂移)。
3. POSIX 预检(泛化 resolveShellExecutable)
- POSIX 分支:配置值含
/→existsSync直查;裸名 → 沿env.PATH(:分隔,缺省回退/usr/bin:/bin)逐目录探测。 - 找不到统一抛
SidebarError('shell-not-found', 'shell executable not found: "…"')(新错误码;消息保持英文技术文案——模型侧terminal_create工具报错复用同一消息,模型读英文更稳)。 ShellExecutableResolutionOptions的platform/env/exists注入口径不变,win32 逻辑不动。
4. 友好报错(host ↔ client 线协议)
SidebarError增加可选meta载荷,shell-not-found时携带配置的 shell 原文。- WS 升级 handler catch(
src/index.ts终端 upgrade)识别error.code === 'shell-not-found'→ close reason 发机器标记shell-not-found:<shellDisplayName(shell)>(受 WS close reason 123 字节上限约束,超长截断显示名)。 TerminalView.tsxonclose在通用 1011 分支之前识别reason.startsWith('shell-not-found:')→ 本地化 fatal 横幅:terminalShellNotFound(「未找到配置的 Shell:<名>」)+terminalShellNotFoundHint(「请检查 设置 → 侧边卡片 → 终端 的 Shell 路径」)。- locale 新 key 落点:
locales.ts(zh + en 两块)+locales-ja.ts(仓库规则强制同步),其余语言文件走既有回退链。
5. 标题跟随
shell.get路由改为overrides.shell ?? 启动时解析值,shellDisplayName随之;仅影响新开标签的标题,不触碰已存在 pty。
错误流总览
设置页 terminalShell/Args(或 yaml config.shell/Args)
→ shellOverridesOf:unquotePath(shell) + splitShellArgs(args)
→ ptyManager.open / registry.create
→ resolveShellExecutable:win32 既有探测 ∥ POSIX 新探测
→ 找不到:SidebarError('shell-not-found', msg, meta:{shell})
├─ WS 路径:catch → close(1011, 'shell-not-found:<name>') → TerminalView 本地化横幅 + 设置指引
└─ 工具路径:terminal_create 抛英文消息 → 模型可见,自行纠正
测试计划
tests/pty-helpers.spec.ts:splitShellArgs语义矩阵(普通分词 / 单双引号 / 含空格路径 / 反斜杠字面 / 未闭合宽容 / 空引号丢弃 / 连续空白);unquotePath(成对剥、不成对留);POSIX 探测(fakeoptions.env.PATH+options.exists:命中解析、绝对路径缺失抛shell-not-found);win32 既有用例保持绿。shellOverridesOf引号行为与shell.get跟随:就近扩展现有 spec,无宿主则新建。- 客户端横幅为纯展示逻辑,已有 TerminalView spec 则扩展,否则不强行 e2e。
- 门禁:
pnpm typecheck+pnpm test;不改 chunk 边界与 exclude 配置,mount e2e 不受影响。
实施偏差记录
- 报错文案合并为单 key:设计时的
terminalShellNotFound+terminalShellNotFoundHint两 key 合并为单个terminalShellNotFound(含{name}占位符,一句话内含指引),避免跨语言的句子拼接问题。 - POSIX 旧契约用例被有意改写:
tests/pty-helpers.spec.ts原用例keeps POSIX bare shell resolution delegated to execvp锁定「POSIX 裸名直通 execvp」,与本设计冲突,已删除并由POSIX: resolves bare names along PATH…等新用例替代。 - locale 落点为全部 21 词典:设计原计划只写 zh/en/ja,实施时受
tests/locales.spec.ts的「第三方词典键集必须与 zh 相等」门禁约束,terminalShellNotFound实际落入locales.ts(zh+en 双块)+ 19 个locales-*.ts(共 21 文件 22 处),chunks/locale.tsx的Record<CopyKey, string>类型检查兜底。 resolveShellExecutable的 JSDoc 同步:win32 错误码从pty-error变更为shell-not-found后,函数上方文档一并更新(commit8704994)。- 本机全量测试基线:
pnpm test在本机(Windows)存在 29 个预存环境性失败(5 个 client spec 的 jsdomreading 'clear'×24、smoke 的 git 身份 ×2、git.spec 截断用例、agent-pty 的 node-pty「Signals not supported on windows」unhandled error)。经与origin/main同法全量对比确认零新增失败(main 30 个,feat 29 个——main 上多出的git-worktree用例为 flaky)。CI 以 CI 环境为准。
交付
- AGENTS 硬约束:
feat/*分支开发,commit 后gh pr create,review 合并进 main;不直推。