Replies: 3 comments
附录:调查记录 B + 包装器路线实验记录 + 证据清单附录:调查记录 B(暂不提交)
标题(若日后补证再提) 已确认的实现限制
static void Visit(string path, Action<IHandler> visit) {
var extension = Path.GetExtension(path);
if (extension.Length == 0) return; // 无扩展名 → 直接返回,零 handler
SHAssocEnumHandlers(extension, 0, out handlers);
…
}
static void OpenRegistered(string path, string application) {
…
if (!opened) throw new InvalidOperationException("Application is not registered for this file");
}判断依据是扩展名是否为空,不是"是否是目录":
修正后的复现脚本(注意:该函数返回的是 stdout 字符串,不是 import { spawn } from 'node:child_process';
import { windowsFileApplications } from './extract/…file-applications-windows.js'; // 抽取步骤见文末
// 自定义 runner。注意:实现内部是 `return result.stdout`,所以 runner 必须 resolve 一个**含 stdout 的对象**;
// 这里同时把退出码与 stderr 存进 run.last 以便打印(生产的 runner 在非零退出时的行为与此不同)。
const run = (cmd, args) => new Promise((resolve, reject) => {
const p = spawn(cmd, args, { stdio: ['ignore', 'pipe', 'pipe'] });
let stdout = '', stderr = '';
p.stdout.on('data', d => { stdout += d; });
p.stderr.on('data', d => { stderr += d; });
p.on('error', reject);
p.on('close', code => { run.last = { code, stderr }; resolve({ stdout, stderr, code }); });
});
const noExtension = String.raw`H:\Harness`;
const withExtension = String.raw`H:\Harness\AGENTS.md`;
const forFile = JSON.parse(await windowsFileApplications(withExtension, null, undefined, run));
const vscode = forFile.find(app => /Code\.exe$/i.test(app.id));
if (!vscode) { console.log('未在该扩展名上找到 VS Code handler,跳过'); process.exit(0); }
console.log('枚举无扩展名目标:',
JSON.parse(await windowsFileApplications(noExtension, null, undefined, run)).length); // 实测 0
await windowsFileApplications(noExtension, vscode.id, undefined, run);
console.log('指定应用打开 →', run.last.code, (run.last.stderr || '').split('\n')[0]);
// 实测: 1 Exception calling "Open" with "2" argument(s): "Application is not registered for this file"关于 UI 可达性(保持未确认,不做推断) 上述失败是在底层 API 上复现的。右侧栏文档控件对目录是否真的会展示"应用选择",我们没有逐帧确认; 判断口径 这是已确认的实现限制;是否构成缺陷,取决于该 API 的契约与实际调用者需求, 若日后确认需要修(候选方案)
附:证据、材料与复核方式原始记录(建议随 issue 提交)
复现脚本存档:
抽取前置步骤( # 有 node 时
node asar-extract.mjs "D:\DSHHarness\resources\app.asar" "<outDir>" "dsh-native-command"
# 无 node、有 python3 时
python3 asar_extract.py /mnt/d/DSHHarness/resources/app.asar <outDir> "dsh-native-command"注意:上述脚本里的自定义 runner 在非零退出时仍 resolve(便于打印 stderr), 已核对的源码位置(全部为本次直接读取
附:包装器路线实验记录(结论:不建议采用,已回滚)背景:官方按钮启动的 exe 由 三条可独立核对的事实:
包装器链与成功对照的差别尚未定位(候选:Job Object、继承句柄表、窗口站/桌面、Chromium 早期 CHECK), 对上游的意义:这组实验说明"靠外部包装器兜底"在本机不可行,但不构成对 Issue A 根因判断的反驳 —— |
|
|
在另一台机器上独立复核:0.2.0-rc.2 仍复现,根因与主楼完全一致。补充几条新证据,并已在本地按主楼「建议修法 1」实现修复、验证全部通过——如需要可提供补丁。 环境
复核证据(方法同主楼,独立机器)
对主楼「修复边界」一节的支持证据 同主楼,该变量不能从 关于附录包装器实验中未定位的 在 DSH 受限会话(工具沙箱约束下的进程)里以同样方式启动 已本地实现并验证的修复(= 主楼建议修法 1)
|
Uh oh!
There was an error while loading. Please reload this page.
说明:本仓库 Issues 已关闭,故以 Discussion 提交。以下为完整缺陷报告(含环境、逐步复现、根因定位与建议修法);复现脚本与四轮对抗性复核结论见评论。
环境
D:\DSHHarness\DeepSeek Harness.exe,FileVersion 0.1.7-rc.2)645f29cc3176500b4b5762ba887cf2a7f0ffdf2c,x64,per-user 安装:%LOCALAPPDATA%\Programs\Microsoft VS Code\Code.exe)app.asar内dsh/node_modules/):@deepseek-ai/dsh-host-open-in-app@0.1.7-rc.2、@deepseek-ai/dsh-native-command@0.1.7-rc.2、@deepseek-ai/dsh-subprocess@0.1.7-rc.2、@deepseek-ai/dsh-subprocess-local@0.1.7-rc.2现象
会话头右侧「在本地打开」分键按钮(槽位
conversation.session.header.utilities,registrantopen-in-app)选择VS Code 后:未出现预期的工作区窗口,启动进程约 40 ms 后自行退出,界面提示「打开失败,请重试」。
宿主路由
POST /open-in-app/open {"app":"vscode","path":"<workspace>"}返回502
{"code":"launch-failed","message":"failed to launch vscode"}。关于 200 的语义(避免误读):
launchDetachedApp有两个 resolve 条件 —— ①子进程 exit 0;②看护窗口(
launchWatchMs)到期时子进程仍在运行。因此 200 只代表"判定为已启动",不保证 GUI 真的打开;本例是明确收到 502。
复现
(1) 最小复现 —— 纯 Windows,不依赖 DSH(在带该变量的环境中执行):
stderr:
(2) 环境有/无对照(用归档脚本
env-toggle-proof.mjs;目标默认取一个新建的空目录,以排除目录内可执行入口的干扰;两次使用同一目标,只改变环境变量):
(3) 变量的作用(交叉印证):
(4) UI 复现:桌面端打开任一工作区会话 → 点会话头右侧「在本地打开」→ 菜单选 VS Code。
根因
宿主进程自身带着该变量(实测,非推断)。桌面壳以 Electron 的 Node 模式派生核心宿主:
asar 根
lib/main.js的desktopNodeEnvironment(executable, bin, environment)返回{ ...environment, ELECTRON_RUN_AS_NODE: "1", ... }(注释即 "Environment for a Node-mode child process")。在宿主进程(监听
127.0.0.1:19387的那个进程,本机 PID 70924)内部读到的实测值:{ "pid": 70924, "action": "removed", "before": "1", "after": null }(该记录只证明当时的状态,不是实时证明。)
GUI 启动器原样继承。
@deepseek-ai/dsh-host-open-in-app的运行时入口是lib/index.js(
package.json的exports["."].default);launchDetachedApp内联在该文件 第 348 行,第 8 行
import { scrubbedParentEnv } from "@deepseek-ai/dsh-subprocess":scrubbedParentEnv()不剔除 Electron 模式变量。@deepseek-ai/dsh-subprocess/lib/index.js第 32 行
const SENSITIVE_ENV_PATTERN = /KEY|PASSWORD|SECRET|TOKEN/i;,第 52 行遍历时另丢弃DSH_*前缀并处理代理变量 ——ELECTRON_RUN_AS_NODE不在其中。启动失败被判定为错误:同一函数中
child.on('exit', ...)对看护窗口内的非 0 退出 reject;open 路由据此
sendJson(res, 502, { code: "launch-failed", ... })。VS Code 在本机由目录表解析为
spec(appPaths('Code.exe'), installRecord('Microsoft Visual Studio Code', 'Code.exe'), file([...]))(目录表
vscode条目),即直接启动 exe,因此命中上面整条链。本机
HKCU\...\App Paths\Code.exe存在、Code.exe存在 —— 解析本身是健康的。我们最初的临时变通是"在宿主里
delete process.env.ELECTRON_RUN_AS_NODE",它打断了经 Windows Job runner 的命令执行。前提限定:以下结论适用于本次这种 Electron Node 模式宿主 + 当前 Windows Job runner 实现;
不涵盖直接调用其它可执行文件、其它 provider、或不同宿主启动方式的情形。
源码链(已逐处核对):
@deepseek-ai/dsh-subprocess-local/lib/runner-launch-B2zsQ1Dz.js1311–1316 行:Windows runner 以
process.execPath(即桌面 Electron 可执行文件)+ runner 脚本启动;694–695 行:
childEnv()→scrubbedParentEnv();1343–1345 行:
runnerEnvironment()取childEnv(),没有显式补回ELECTRON_RUN_AS_NODE。@deepseek-ai/dsh-subprocess-local/lib/index.js601 行:
env: runnerEnvironment(WINDOWS_RUNNER_SELECTION, invocation);629 行:
subprocess-local: Windows Job runner exited with ${status} before proving its managed range empty。实测后果(删除该变量之后的同一会话内):实测经该 runner 的
pwsh工具与grep工具内部 ripgrep 调用均失败 ——两者都返回上面第 629 行那句(退出码 0、无残留进程,符合"runner 以 GUI 入口启动、未履行 IPC 协议即退出")。
其它调用未逐一验证,不作断言。
建议修法
应用启动边界(本 issue 的直接修复) ——
launchDetachedApp(lib/index.js:348,并同步其源文件):先清理继承值,再合并适配器的显式值,以保留 GitHub Desktop
cli.js适配器的显式 opt-in:内部 runner 显式声明 Node 模式(独立加固) —— 在
runnerEnvironment()中为"以 Electron 可执行文件启动Node runner"的路径显式提供该变量。需注意的边界:
runnerEnvironment()也服务其它启动路径,不能仅凭本次 Windows 反例认定无条件补值已完整验证。回归要求(建议随修复一起给出):
cli.js一路仍以 Node 模式运行);process.env不因启动应用而改变);关于改哪里:
lib/index.js与lib/types/resolver.js是生成产物(后者是同实现的副本),本次引用它们只为定位受影响产物;正解是在源文件中修改并重新构建,不宜要求维护者手工维护两份生成代码。
不建议用调整
launchWatchMs来掩盖:增大窗口能覆盖更多较晚发生的失败,但不能修复 Node 模式导致的退出(本例 42 ms 就结束了);缩短窗口则可能提前判定成功,同样不代表 GUI 已打开。
影响面(收窄后的表述)
App Paths\*.exe直接启动、且自身尊重该变量"的Electron 应用 —— 目录表中的 VS Code Insiders、Cursor、Windsurf 属此形态。
runAsNodefuse,会忽略该变量(官方文档:https://www.electronjs.org/docs/latest/api/environment-variables#electron_run_as_node),故不做"所有 Electron 应用必失败"的断言。
shell-open走explorer.exe)——不直接受此机制影响,但未逐项实测。macOS / Linux 未验证。
All reactions