diff --git a/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.i18n.yaml index 8621e073de..d38fcb89d9 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.i18n.yaml +++ b/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.md -2026-08-19-win32-dialog-worker-source-launch.md: 44eb6634d44c7338bae7ac90c5f50aecfa142bd2 -2026-08-19-win32-dialog-worker-source-launch.zh.md: 9055289fa40a914f6506064b63388520352cf29d +2026-08-19-win32-dialog-worker-source-launch.md: 549f99a7b6c71e634d7a1d7e589a6482e8f9acac +2026-08-19-win32-dialog-worker-source-launch.zh.md: 1a59a397a015d3eba2f5b3c97f794c86bcbd5fd9 diff --git a/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.md b/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.md index 44eb6634d4..549f99a7b6 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.md +++ b/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.md @@ -6,7 +6,9 @@ English | [中文](2026-08-19-win32-dialog-worker-source-launch.zh.md) ## Problem -On Windows, the source-plane folder dialog worker never started: the Web UI reported `win32 folder dialog worker exited before reporting a result`. The failure was in the launch vector, not koffi: the source arm ran `node --import tsx/esm `. With a loader registered through `--import`, an absolute path such as `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` can be read as an `e:` scheme URL and rejected with `ERR_UNSUPPORTED_ESM_URL_SCHEME`, before the worker posts its first IPC message. The registered entry is what differs from this repository's other absolute-path launches: they register the full `tsx` entry (`packages/test-support/loader-smoke/src/index.ts`) and pass on Windows CI, while this arm registered the ESM-only `tsx/esm` hook. +On Windows, the source-plane folder dialog worker never started: the Web UI reported `win32 folder dialog worker exited before reporting a result`, and the worker died before koffi ever loaded. The source arm ran `node --import tsx/esm `, and the reported diagnosis was that a loader registered through `--import` makes an absolute path such as `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` resolve as an `e:` scheme URL, rejected with `ERR_UNSUPPORTED_ESM_URL_SCHEME` before the first IPC message. Replacing that launch fixed the dialog on Windows. + +The vector alone does not explain the failure. `packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` spawns `node --import tsx/esm ` on win32 only, and `vitest.config.ts` treats that suite as the Windows signal that lets `runner.ts` leave the per-file coverage gate; `packages/test-support/loader-smoke/src/index.ts` launches an absolute path behind a resolved tsx loader from a spec Windows does not exclude. The factor unique to this call site is `ELECTRON_RUN_AS_NODE=1` over a `process.execPath` that a packaged host points at its own binary; whether that is what turned the path into an `e:` scheme URL is untested. The decision below does not rest on settling it. A raw `import.meta.url.endsWith('.ts')` check also decided which arm to launch. Vitest and Vite may decorate a module URL with a query string, and a decorated URL fails that suffix test, so a source-plane test could exercise the built arm — a bundler-specific test hazard rather than a cause of the Windows failure. @@ -47,7 +49,7 @@ An inherited `--import` is preserved like any other entry, so a host that regist The `dsh` CLI source launch keeps the tsx ESM hook because its graph needs a transform mode Node no longer ships, per [the source-launch decision](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md); that constraint is about the CLI graph, not about native stripping being unavailable in the engines range. -`packages/sandbox/sandbox-local/src/index.ts` still builds this vector — the same ESM-only `tsx/esm` hook in front of an absolute path — for the windows-acl runner's source arm, and that graph is package-local and erasable too, so the same launch applies there. It is a separate change: it also rewrites the assertion in `packages/sandbox/sandbox-local/tests/local.spec.ts` that pins the `--import tsx/esm` prefix. +`packages/sandbox/sandbox-local/src/index.ts` builds the same argv for the windows-acl runner's source arm, and `packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` runs exactly that argv on win32, which is why it is not recorded here as a second instance of this failure. Its graph is package-local and erasable, so this launch would suit it as a simplification — one that rewrites the `--import tsx/esm` assertion in `packages/sandbox/sandbox-local/tests/local.spec.ts` — not as a fix. `packages/workflow/workflow-worker-thread/src/host.ts` selects its own source/built arm from the raw `import.meta.url`, but it boots the worker from a `data:` URL carrying a proper `file://` href, so the `e:` scheme failure cannot reach that launch. Its raw check does leave the query-string hazard: on a built tree a decorated URL selects `worker.cjs`, so a source-plane test there can exercise built code — which artifact a test covers, not a production launch. @@ -61,7 +63,7 @@ The `dsh` CLI source launch keeps the tsx ESM hook because its graph needs a tra ## Consequences -- Windows source launches (`pnpm dsh web`) run the worker directly under Node's native type stripping, so no loader chain can read the worker path as an `e:` scheme URL. +- Windows source launches (`pnpm dsh web`) run the worker directly under Node's native type stripping, so the launch no longer depends on how a loader chain resolves the worker path. - Packaged hosts keep the unchanged CJS worker arm and an untouched `NODE_OPTIONS`. - The source arm depends on the engines range, a package-local erasable-only graph, marked type imports, and removal of inherited type-stripping disable flags; [the package README](../../../../packages/host/directory-picker-native/README.md) states those preconditions for consumers. - The Win32 smoke reaches the real source launch even where a module runner decorates URLs with query strings. diff --git a/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.zh.md b/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.zh.md index 9055289fa4..1a59a397a0 100644 --- a/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.zh.md +++ b/.agents/notes/implemented/bug-fix/2026-08-19-win32-dialog-worker-source-launch.zh.md @@ -6,7 +6,9 @@ Status: implemented ## 问题 -Windows 上源码面的文件夹对话框 worker 从未启动成功:Web UI 只报出 `win32 folder dialog worker exited before reporting a result`。故障出在启动向量而非 koffi:源码分支运行的是 `node --import tsx/esm <绝对路径 .ts>`。通过 `--import` 注册 loader 后,像 `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` 这样的绝对路径可能被读作 `e:` scheme URL 并以 `ERR_UNSUPPORTED_ESM_URL_SCHEME` 拒绝,此时 worker 还没发出第一条 IPC 消息。与仓库中其他「绝对路径」启动的区别在于注册的入口:它们注册的是完整的 `tsx` 入口(`packages/test-support/loader-smoke/src/index.ts`),在 Windows CI 上是绿的,而这个分支注册的是仅 ESM 的 `tsx/esm` hook。 +Windows 上源码面的文件夹对话框 worker 从未启动成功:Web UI 只报出 `win32 folder dialog worker exited before reporting a result`,且 worker 在 koffi 加载之前就已死亡。源码分支运行的是 `node --import tsx/esm <绝对路径 .ts>`,当时的诊断是:通过 `--import` 注册 loader 后,像 `E:\dsh\packages\host\directory-picker-native\src\win32-dialog-worker.ts` 这样的绝对路径会被解析成 `e:` scheme URL,并在第一条 IPC 消息之前以 `ERR_UNSUPPORTED_ESM_URL_SCHEME` 被拒绝。换掉这个启动方式确实让 Windows 上的对话框恢复了。 + +但仅凭启动向量无法解释这次故障。`packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` 只在 win32 上以 `node --import tsx/esm <绝对路径>` 启动,而 `vitest.config.ts` 正是把这套测试当作让 `runner.ts` 免于每文件覆盖率门禁的 Windows 信号;`packages/test-support/loader-smoke/src/index.ts` 也在 Windows 未排除的 spec 中以解析后的 tsx loader 启动绝对路径。本调用点独有的因素是 `ELECTRON_RUN_AS_NODE=1` 叠加打包宿主指向自身二进制的 `process.execPath`;它是否就是把路径变成 `e:` scheme URL 的原因,尚未验证。下面的决策并不依赖于把这一点定论。 决定启动哪个分支的判断此前读的是裸 `import.meta.url`。Vitest 与 Vite 可能给模块 URL 附加查询串,带查询串的 URL 通不过这个后缀判断,于是源码面的测试可能跑到 built 分支上——这属于 bundler 测试环境的风险,而不是 Windows 故障的成因。 @@ -47,7 +49,7 @@ spawn(process.execPath, [fileURLToPath(new URL('./win32-dialog-worker.ts', impor `dsh` CLI 的源码启动保留 tsx ESM hook,因为它的源码图需要 Node 已不再提供的 transform 模式,见[源码启动决策](../architecture/2026-07-29-dsh-source-launch-tsx-esm.md);那条约束针对的是 CLI 源码图,而不是说 engines 范围内没有原生剥离。 -`packages/sandbox/sandbox-local/src/index.ts` 仍在为 windows-acl runner 的源码分支拼出同一个启动向量——同样是仅 ESM 的 `tsx/esm` hook 加绝对路径,而那个源码图同样包内闭合且可擦除,因此同样的启动方式适用。它属于独立改动:一并要改写 `packages/sandbox/sandbox-local/tests/local.spec.ts` 中钉住 `--import tsx/esm` 前缀的断言。 +`packages/sandbox/sandbox-local/src/index.ts` 为 windows-acl runner 的源码分支拼出同样的 argv,而 `packages/sandbox/sandbox-windows-acl/tests/runner.spec.ts` 正是在 win32 上跑这套 argv,因此这里不把它记作本故障的第二例。它的源码图同样包内闭合且可擦除,所以这套启动方式适合它——作为一次简化,并需一并改写 `packages/sandbox/sandbox-local/tests/local.spec.ts` 中的 `--import tsx/esm` 断言——而不是作为修复。 `packages/workflow/workflow-worker-thread/src/host.ts` 同样从裸 `import.meta.url` 选择源码/构建分支,但它的 worker 从携带正确 `file://` href 的 `data:` URL 启动,`e:` scheme 故障触及不到那条启动路径。它的裸判断确实留下了查询串风险:在已构建的树上,带查询串的 URL 会选中 `worker.cjs`,于是那里的源码面测试可能跑到构建产物上——这影响测试覆盖的是哪个产物,而非生产启动。 @@ -61,7 +63,7 @@ spawn(process.execPath, [fileURLToPath(new URL('./win32-dialog-worker.ts', impor ## 后果 -- Windows 源码启动(`pnpm dsh web`)直接由 Node 原生类型剥离运行 worker,不再有任何 loader 链会把 worker 路径读成 `e:` scheme URL。 +- Windows 源码启动(`pnpm dsh web`)直接由 Node 原生类型剥离运行 worker,启动不再取决于 loader 链如何解析 worker 路径。 - 打包宿主保持不变的 CJS worker 分支,`NODE_OPTIONS` 不被改写。 - 源码分支依赖 engines 范围、包内闭合且只含可擦除语法的依赖图、标注过的类型导入,以及移除继承的类型剥离禁用 flag;[包 README](../../../../packages/host/directory-picker-native/README.md) 为使用者写明了这些前提。 - 即使模块运行器给 URL 附加查询串,Win32 冒烟测试也能进入真实的源码启动。