fix(web): resolve the pwsh-terminal overlay duplicate-loader entry
The web E2E seed lane for the pwsh terminal card failed on every platform with `duplicate loader entry id: tool-pwsh`. The overlay inserted a new tool-pwsh row, but the base bundle has declared that id since the shell platform layer moved into it, so the overlay's insert delivered a second row with the same id and the loader rejected the pair at boot. Enable the existing tool-pwsh row by id instead of inserting it, and disable pwsh-sandbox so the inserted pwsh-local is the lone executor on every platform (the base gates pwsh-sandbox on win32, which would otherwise collide with the inserted executor there).
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# 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-12-fix-pwsh-terminal-overlay-dup.md
|
||||
2026-08-12-fix-pwsh-terminal-overlay-dup.md: 7a214ea093faf49141c66bfc2cbb065fbeb29825
|
||||
2026-08-12-fix-pwsh-terminal-overlay-dup.zh.md: 6d38ca58ad97e5ad04992e54a7938c9fea300863
|
||||
@@ -0,0 +1,55 @@
|
||||
# Agent Note: fix the pwsh terminal overlay duplicate-loader collision
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-08-12-fix-pwsh-terminal-overlay-dup.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`apps/web/tests/pwsh-terminal.e2e.ts` fails on every platform with `TypeError: duplicate loader entry id: tool-pwsh`, thrown from `vendor/loader/src/config/group.ts:64` while applying the web composition. The failing seed lane boots the full shipped bundle plus a test overlay, so the E2E never reaches its rendering assertion and every `check:ci:snapshot`/`test:web` run reports a red web test even though the feature under test is unrelated to the change under review.
|
||||
|
||||
The web E2E scaffold applies an `extraOverlayPath` after the shipped Web surface and base patches. `pwsh-terminal.overlay.yml` used an `insert` block to add a `tool-pwsh` row:
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
- id: pwsh-local
|
||||
name: '@deepseek-ai/dsh-pwsh-local'
|
||||
- id: tool-pwsh
|
||||
name: '@deepseek-ai/dsh-tool-pwsh'
|
||||
```
|
||||
|
||||
`insert` is correct only while `tool-pwsh` is absent from the composition. The id exists because `86b6979bdc` (refactor(bundle): fold the Windows shell platform layer into the base rows) moved both shell stacks into the base bundle with inverted platform gates — `packages/bundle/base/cordis.patch.yml` declares `tool-pwsh` with `disabled: !!js process.platform !== 'win32'`, so the row is present in the composition on every platform. Later, `42fc7c5ffb` (refactor(preset): gate tool-pwsh by platform alongside tool-bash) added a web-app patch row that disables `tool-pwsh` for surfaces that use presets; a patch row cannot introduce an id, so it is not the source of the collision. The overlay's `insert` delivers a second row with the same id in the same loader group, and the loader rejects the pair at boot.
|
||||
|
||||
## Decision
|
||||
|
||||
Replace the overlay's `insert` of `tool-pwsh` with a top-level id-targeted override:
|
||||
|
||||
```yaml
|
||||
- id: tool-pwsh
|
||||
name: '@deepseek-ai/dsh-tool-pwsh'
|
||||
disabled: false
|
||||
```
|
||||
|
||||
The effective `tool-pwsh` state is a three-layer stack: the base row gates `disabled` on `process.platform !== 'win32'`, the web-app overlay sets `disabled: true` unconditionally for preset surfaces, and this lane's override clears it back to `disabled: false` regardless of platform. An `id`-targeted top-level override replaces the composed row; only an `insert` would collide.
|
||||
|
||||
The lane also now disables `pwsh-sandbox` by id, symmetric with the existing `bash-sandbox` disable: the base gates `pwsh-sandbox` with `disabled: !!js process.platform !== 'win32'`, so on Windows it would otherwise mount beside the inserted `pwsh-local` and both would register the same executor service. Disabling it keeps `pwsh-local` the lone executor on every platform.
|
||||
|
||||
The overlay header comment was updated to describe the full selection and the `tool-pwsh` inline comment now names the base row as the source of the id.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the `insert` and change the web composition instead.** Rejected, because the shipped web composition should keep the host `tool-pwsh` row disabled for every surface that uses presets; the overlay is the lane that deliberately needs it, so the by-id enable belongs there. The base row itself cannot be removed either: it is the platform-gated shell-stack declaration shared by every bundle.
|
||||
|
||||
**Enable `tool-pwsh` in the `insert` block.** Not possible: an `insert` of an id that already exists is the very duplicate being fixed. The row must be targeted by id, which is the top-level override form, not `insert`.
|
||||
|
||||
**Patch `tool-pwsh` by id without setting `disabled: false`.** Insufficient: the web-app overlay sets `disabled: true` unconditionally, and the base row's platform gate only applies where the web-app override is absent, so an override that only restates `name` leaves the row disabled and the lane renders no terminal card. The `disabled: false` is required.
|
||||
|
||||
**Only disable `bash-sandbox` and rely on the platform gate to keep `pwsh-sandbox` off.** Rejected: that holds on POSIX but breaks on Windows, where the base row leaves `pwsh-sandbox` enabled and it would collide with the inserted `pwsh-local` on the shared executor service. The lane's `pwsh-sandbox` disable keeps one executor on every platform.
|
||||
|
||||
## Verification
|
||||
|
||||
Reverting the fix (restoring the `insert` of `tool-pwsh`) reproduces the exact `duplicate loader entry id: tool-pwsh` boot failure, confirming the override is load-bearing. With the fix in place `pwsh-terminal.e2e.ts` passes 2/2 on the same head — this exercises the POSIX seam, where the seeded pwsh call renders through the enabled `tool-pwsh` and the inserted `pwsh-local`. The seed lane requires a usable `pwsh`, so it skips on hosts without one; a `pwsh` binary is present on this machine and the test ran. The Windows path (base `pwsh-sandbox` mounted beside the inserted `pwsh-local`) is not exercised by any CI lane, whose `test:web` runs only on Linux; the overlay disables `pwsh-sandbox` to keep that path composable if it ever runs on a Windows dev machine.
|
||||
|
||||
## Consequences
|
||||
|
||||
The web E2E seed lane that exercises PowerShell boot now composes instead of colliding, so `check:ci:snapshot` and `test:web` stop failing on the duplicate independently of the change under test. The pattern is general: a `--patch`/`extraOverlayPath` overlay must probe whether a row already exists in the bundle it augments before choosing `insert` over an id-targeted override; `insert` of an id that the base or shipped Web surface already declares is a boot-time duplicate.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Agent Note:修复 pwsh 终端 overlay 的重复 loader 冲突
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-08-12-fix-pwsh-terminal-overlay-dup.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
`apps/web/tests/pwsh-terminal.e2e.ts` 在所有平台上都以 `TypeError: duplicate loader entry id: tool-pwsh` 失败,由 `vendor/loader/src/config/group.ts:64` 在应用 web 组合时抛出。该失败的 seed 通道会启动完整发布的 bundle 加一个测试 overlay,因此 E2E 永远到不了渲染断言,导致 `check:ci:snapshot` 与 `test:web` 每次运行都报一个红的 web 测试,即便被测功能与评审中的改动无关。
|
||||
|
||||
web E2E scaffold 在已发布的 Web 表面与 base patches 之后应用 `extraOverlayPath`。`pwsh-terminal.overlay.yml` 用 `insert` 块新增 `tool-pwsh` 行:
|
||||
|
||||
```yaml
|
||||
- insert:
|
||||
- id: pwsh-local
|
||||
name: '@deepseek-ai/dsh-pwsh-local'
|
||||
- id: tool-pwsh
|
||||
name: '@deepseek-ai/dsh-tool-pwsh'
|
||||
```
|
||||
|
||||
`insert` 仅在组合中不存在 `tool-pwsh` 时才正确。该 id 存在是因为 `86b6979bdc`(refactor(bundle): fold the Windows shell platform layer into the base rows)把两套 shell 栈以互逆的平台门移进了 base bundle —— `packages/bundle/base/cordis.patch.yml` 声明 `tool-pwsh` 且 `disabled: !!js process.platform !== 'win32'`,于是该行在每个平台都存在于组合中。随后 `42fc7c5ffb`(refactor(preset): gate tool-pwsh by platform alongside tool-bash)往 web-app patch 里加了一行对使用 preset 的表面禁用 `tool-pwsh` 的行;patch 不能引入 id,因此它不是冲突来源。overlay 的 `insert` 于是在同一个 loader 组里再送一个同 id 的行,loader 在启动时拒绝这对重复。
|
||||
|
||||
## Decision
|
||||
|
||||
把 overlay 对 `tool-pwsh` 的 `insert` 替换成顶层按 id override:
|
||||
|
||||
```yaml
|
||||
- id: tool-pwsh
|
||||
name: '@deepseek-ai/dsh-tool-pwsh'
|
||||
disabled: false
|
||||
```
|
||||
|
||||
有效的 `tool-pwsh` 状态是三层栈:base 行把 `disabled` 门在 `process.platform !== 'win32'` 上,web-app overlay 对 preset 表面无条件设 `disabled: true`,本通道的 override 无论平台都把 `disabled: false` 还回去。`id` 定位的顶层 override 替换组合后的行;只有 `insert` 才会相撞。
|
||||
|
||||
该通道现在也按 id 禁用 `pwsh-sandbox`,与既有的 `bash-sandbox` 禁用对称:base 以 `disabled: !!js process.platform !== 'win32'` 门住 `pwsh-sandbox`,因此在 Windows 上它本会与插入的 `pwsh-local` 并存,两者会注册同一个 executor 服务。禁用它让 `pwsh-local` 在每个平台上都是唯一 executor。
|
||||
|
||||
overlay 头部注释已更新为完整描述选择,`tool-pwsh` 行内注释现在把 base 行标为该 id 的来源。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留 `insert`、改 web 组合。** 拒绝。已发布的 web 组合应在所有使用 preset 的表面上保持 host `tool-pwsh` 行禁用;overlay 才是那条刻意需要该行的通道,因此按 id 启用应放在那里。base 行本身也不能移除:它是所有 bundle 共享的平台门 shell 栈声明。
|
||||
|
||||
**在 `insert` 块里启用 `tool-pwsh`。** 不可行。对已存在的 id 做 `insert` 正是这里要修的重复。该行必须按 id 定位,即顶层 override 形式,而非 `insert`。
|
||||
|
||||
**只按 id 改 `tool-pwsh` 而不设 `disabled: false`。** 不充分。web-app 无条件设 `disabled: true`,base 行的平台门只在 web-app override 缺失处生效,因此只重申 `name` 的 override 会让行保持禁用,通道渲染不出终端卡。`disabled: false` 是必需的。
|
||||
|
||||
**只禁用 `bash-sandbox`、依赖平台门让 `pwsh-sandbox` 保持关闭。** 拒绝。在 POSIX 上成立,但在 Windows 上会失败:base 行让 `pwsh-sandbox` 启用,它会与插入的 `pwsh-local` 在共享 executor 服务上相撞。本通道禁用 `pwsh-sandbox` 让每个平台只有唯一 executor。
|
||||
|
||||
## Verification
|
||||
|
||||
把修复还原(恢复对 `tool-pwsh` 的 `insert`)即复现同样的 `duplicate loader entry id: tool-pwsh` 启动失败,证实 override 是有效的。修复后同一 head 上 `pwsh-terminal.e2e.ts` 2/2 通过 —— 这作用于 POSIX seam,播种的 pwsh 调用经启用的 `tool-pwsh` 与插入的 `pwsh-local` 渲染出来。该 seed 通道需要可用的 `pwsh`,无此二进制的主机会跳过;本机有 `pwsh`,测试实际跑过。Windows 路径(base `pwsh-sandbox` 与插入的 `pwsh-local` 并存)没有任何 CI lane 覆盖,其 `test:web` 只在 Linux 上跑;overlay 禁用 `pwsh-sandbox` 让该路径在真到 Windows 开发机运行时可组合。
|
||||
|
||||
## Consequences
|
||||
|
||||
用于执行 PowerShell 启动的 web E2E seed 通道现在能组合而非相撞,因此 `check:ci:snapshot` 与 `test:web` 不再与被测改动无关地在该 duplicate 上失败。该模式具有通用性:`--patch`/`extraOverlayPath` overlay 在决定用 `insert` 还是按 id override 之前,必须探测目标 bundle 是否已存在该行;对已由 base 或已发布 Web 表面声明的 id 做 `insert`,是启动期重复。
|
||||
Reference in New Issue
Block a user