fix(bundle): drop the approval service from the Windows layer entirely

The Windows layer previously kept the approval service with policy 'never',
which made the approval plugin inject 'Approval prompts are disabled in
this session: actions that require approval are rejected automatically'
into the model context. On Windows nothing asks for approval — the
escalation surfaces (sandbox_permissions) do not exist — so the sentence
described a rejection surface that is not there. The layer now disables
the approval row too: the service is absent, the model is never told
approval exists, and the danger-full-access degradation is complete.

base.spec.ts pins approval among the Windows disables; the Agent Note and
bundle README record the absent service.
This commit is contained in:
Huanqi Cao
2026-08-07 01:41:32 +08:00
parent acac2149a5
commit 73c6f35171
9 changed files with 31 additions and 26 deletions

View File

@@ -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/feature/2026-08-01-windows-pwsh-default.md
2026-08-01-windows-pwsh-default.md: ceffd825c2316841dfbe01f4510243ba8748e500
2026-08-01-windows-pwsh-default.zh.md: ea8058878fe268bdf1317620c853b8279c543532
2026-08-01-windows-pwsh-default.md: 75a27b5140abacf77b5f20028752a4903a50eaec
2026-08-01-windows-pwsh-default.zh.md: 8b940f1973835f8143bde05b37eba6f00e27f8f4

View File

@@ -12,7 +12,7 @@ The harness's shipped execution profile is bash-first on every platform. Windows
Windows hosts booting a shipped profile (`dsh web`, `dsh --profile headless`, one-shot tasks) get the PowerShell stack by default; POSIX hosts are unchanged.
- **The platform layer is a data file, not a roster rewrite.** `@deepseek-ai/dsh-base` ships [`windows.cordis.patch.yml`](../../../../packages/bundle/base/windows.cordis.patch.yml) alongside its universal `cordis.patch.yml`: it disables `bash-sandbox`/`tool-bash` (the POSIX-only executor and its dialect tool) and inserts `pwsh-local`/`tool-pwsh`. Windows has no OS sandbox runner (landlock/bwrap/seatbelt are POSIX-only), so the layer drops the sandbox stack entirely — `sandbox`, `sandbox-policy`, and `fs-sandbox` are disabled and the unconfined `dsh-fs-local` provides `ctx.fs` — and degrades to danger-full-access: `permission`/`ui-permission` leave the roster (dsh-permission requires a confining executor — presets bundle a sandbox mode the unconfined executor cannot honor; see its constructor guard — and the client knob would advertise a boundary that does not exist), and the `approval` policy is `never`. Keeping fs-only path rules would be theater: the unconfined shell can bypass them with one command, so the honest Windows posture is full access rather than a boundary only the fs tools pretend to enforce.
- **The platform layer is a data file, not a roster rewrite.** `@deepseek-ai/dsh-base` ships [`windows.cordis.patch.yml`](../../../../packages/bundle/base/windows.cordis.patch.yml) alongside its universal `cordis.patch.yml`: it disables `bash-sandbox`/`tool-bash` (the POSIX-only executor and its dialect tool) and inserts `pwsh-local`/`tool-pwsh`. Windows has no OS sandbox runner (landlock/bwrap/seatbelt are POSIX-only), so the layer drops the sandbox stack entirely — `sandbox`, `sandbox-policy`, and `fs-sandbox` are disabled and the unconfined `dsh-fs-local` provides `ctx.fs` — and degrades to danger-full-access: `permission`/`ui-permission` leave the roster (dsh-permission requires a confining executor — presets bundle a sandbox mode the unconfined executor cannot honor; see its constructor guard — and the client knob would advertise a boundary that does not exist), and the `approval` service is disabled — nothing in the Windows roster asks for approval, so the model is never told approval exists or that asks are auto-rejected. Keeping fs-only path rules would be theater: the unconfined shell can bypass them with one command, so the honest Windows posture is full access rather than a boundary only the fs tools pretend to enforce.
- **The launcher injects the layer by platform.** `apps/cli/src/windows-shell.ts` resolves it from the base bundle layer's `packageDir` between the bundle layers and the user layers on `win32` hosts, in every composition path (boot, config-only HMR recomposition, config dumps). Overriding the shipped default is a composition decision: a Windows host that prefers the bash stack — or confinement — re-enables the bash rows through its profile or home `cordis.patch.yml`. Custom profiles without the base bundle are skipped (they own their shell stack); a base bundle that ships no Windows shell patch fails loud.
- **Module resolution is restored for cold starts.** The profiles-rework CLI dropped the pwsh packages from `apps/cli`'s dependency closure, so `healProfilesModuleFallback` never linked them into `$DSH_HOME/profiles/node_modules` and a fresh Windows host could not resolve the inserted rows. `apps/cli` and `dsh-base` re-declare `dsh-pwsh-local`/`dsh-tool-pwsh`, and `dsh-base` also declares `dsh-fs-local`; the base bundle lists every row plugin as a dependency by house style.
@@ -33,12 +33,12 @@ The pwsh GUI rendering stage (stage 2 of the original roadmap) shipped earlier w
## Consequences
- A Windows host running a shipped `dsh` surface gets `pwsh` as its shell tool and PowerShell as the `ctx.bash` executor without configuration; `bash` is absent from the model-visible roster there (its tool row is disabled).
- Windows has no sandbox at all: the fs tools run unconfined (`dsh-fs-local`), the approval policy is `never`, and the permission switcher is gone. The model-visible posture is honest full access rather than a boundary the shell can bypass.
- Windows has no sandbox at all: the fs tools run unconfined (`dsh-fs-local`), the approval service is absent (nothing asks for approval, and the model is never told approval exists), and the permission switcher is gone. The model-visible posture is honest full access rather than a boundary the shell can bypass.
- POSIX hosts are unchanged: the platform layer never applies, and the bash stack remains the universal `cordis.patch.yml` rows.
- Windows hosts that prefer the bash stack (e.g. with WSL/Git-Bash on PATH) override the shipped default through their profile or home `cordis.patch.yml` — composition config is the one override channel.
## Verification
- Unit: `apps/cli/tests/windows-shell.spec.ts` pins the win32 default, the custom-profile skip, and the missing-patch failure, with the platform injected; `packages/bundle/base/tests/base.spec.ts` pins the shipped Windows roster (disables, inserts, and the `never` approval policy).
- Unit: `apps/cli/tests/windows-shell.spec.ts` pins the win32 default, the custom-profile skip, and the missing-patch failure, with the platform injected; `packages/bundle/base/tests/base.spec.ts` pins the shipped Windows roster (disables, inserts, and the absent approval service).
- Keyless: a win32 `dsh --profile <name> --dump-config` shows the pwsh rows with `windows.cordis.patch.yml` provenance and the bash rows disabled; the POSIX dump (CI Linux) is unchanged.
- The real-composition smoke boots the web profile on win32 with the pwsh stack mounted (the exact roster this note describes).

View File

@@ -12,7 +12,7 @@ harness 交付的执行画像在每个平台都是 bash 优先。Windows 主机
启动交付 profile`dsh web``dsh --profile headless`、一次性任务)的 Windows 主机默认获得 PowerShell 栈POSIX 主机不变。
- **平台层是数据文件,不是清单重写。** `@deepseek-ai/dsh-base` 随通用 `cordis.patch.yml` 一起交付 [`windows.cordis.patch.yml`](../../../../packages/bundle/base/windows.cordis.patch.yml):它禁用 `bash-sandbox`/`tool-bash`(仅 POSIX 的执行器及其方言工具)并插入 `pwsh-local`/`tool-pwsh`。Windows 上没有 OS 级 sandbox runnerlandlock/bwrap/seatbelt 均为 POSIX 专属),因此该层整体移除 sandbox 栈——`sandbox``sandbox-policy``fs-sandbox` 被禁用,由不限权的 `dsh-fs-local` 提供 `ctx.fs`——并完全退化为 danger-full-access`permission`/`ui-permission` 离开清单dsh-permission 要求有限权能力的执行器——preset 捆绑的是无限制执行器无法兑现的 sandbox 模式;见其构造函数守卫——客户端旋钮会宣传一个并不存在的边界),`approval` 策略为 `never`。保留仅限 fs 的路径规则是摆设:不限权的 shell 一条命令即可绕过,因此诚实的 Windows 姿态是全权访问,而不是一个只有 fs 工具假装执行的边界。
- **平台层是数据文件,不是清单重写。** `@deepseek-ai/dsh-base` 随通用 `cordis.patch.yml` 一起交付 [`windows.cordis.patch.yml`](../../../../packages/bundle/base/windows.cordis.patch.yml):它禁用 `bash-sandbox`/`tool-bash`(仅 POSIX 的执行器及其方言工具)并插入 `pwsh-local`/`tool-pwsh`。Windows 上没有 OS 级 sandbox runnerlandlock/bwrap/seatbelt 均为 POSIX 专属),因此该层整体移除 sandbox 栈——`sandbox``sandbox-policy``fs-sandbox` 被禁用,由不限权的 `dsh-fs-local` 提供 `ctx.fs`——并完全退化为 danger-full-access`permission`/`ui-permission` 离开清单dsh-permission 要求有限权能力的执行器——preset 捆绑的是无限制执行器无法兑现的 sandbox 模式;见其构造函数守卫——客户端旋钮会宣传一个并不存在的边界),`approval` 服务也被禁用——Windows 清单里没有任何动作需要审批,模型也不会被告知"审批存在"或"请求会被自动拒绝"。保留仅限 fs 的路径规则是摆设:不限权的 shell 一条命令即可绕过,因此诚实的 Windows 姿态是全权访问,而不是一个只有 fs 工具假装执行的边界。
- **启动器按平台注入该层。** `apps/cli/src/windows-shell.ts``win32` 主机上从 base bundle 层的 `packageDir` 解析它,置于 bundle 层与用户层之间覆盖所有组合路径启动、config-only HMR 重组合、配置转储)。覆盖交付默认是组合决策:偏好 bash 栈(或偏好有限权)的 Windows 主机通过其 profile 或 home 的 `cordis.patch.yml` 重新启用 bash 行。未挂 base bundle 的自定义 profile 被跳过(它们自己拥有 shell 栈base bundle 缺 `windows.cordis.patch.yml` 时 fail loud。
- **冷启动的模块解析已恢复。** profiles 重构把 pwsh 包从 `apps/cli` 的依赖闭包中删掉了,`healProfilesModuleFallback` 因此从未把它们链接进 `$DSH_HOME/profiles/node_modules`,新 Windows 主机解析不到插入的行。`apps/cli``dsh-base` 重新声明 `dsh-pwsh-local`/`dsh-tool-pwsh``dsh-base` 还声明 `dsh-fs-local`按仓库惯例base bundle 把每个行插件都列为依赖。
@@ -33,12 +33,12 @@ harness 交付的执行画像在每个平台都是 bash 优先。Windows 主机
## 后果
- 运行交付版 `dsh` 表面的 Windows 主机无需配置即获得 `pwsh` 作为 shell 工具、PowerShell 作为 `ctx.bash` 执行器;那里的模型可见清单中没有 `bash`(其工具行被禁用)。
- Windows 上没有任何沙箱fs 工具不限权运行(`dsh-fs-local`)、`approval` 策略为 `never`、权限切换器消失。模型可见的姿态是诚实的全权访问,而不是一个 shell 可以绕过的边界。
- Windows 上没有任何沙箱fs 工具不限权运行(`dsh-fs-local`)、`approval` 服务不存在(没有任何动作需要审批,模型也不会被告知审批存在)、权限切换器消失。模型可见的姿态是诚实的全权访问,而不是一个 shell 可以绕过的边界。
- POSIX 主机不变平台层永不生效bash 栈仍是通用 `cordis.patch.yml` 的行。
- 偏好 bash 栈的 Windows 主机(例如 PATH 上有 WSL/Git-Bash 时)通过其 profile 或 home 的 `cordis.patch.yml` 覆盖交付默认——组合配置是唯一的覆盖通道。
## 验证
- 单元:`apps/cli/tests/windows-shell.spec.ts` 固定 win32 默认、自定义 profile 跳过与缺文件失败,平台注入;`packages/bundle/base/tests/base.spec.ts` 固定交付的 Windows 清单(禁用、插入与 `never` approval 策略)。
- 单元:`apps/cli/tests/windows-shell.spec.ts` 固定 win32 默认、自定义 profile 跳过与缺文件失败,平台注入;`packages/bundle/base/tests/base.spec.ts` 固定交付的 Windows 清单(禁用、插入与缺席的 approval 服务)。
- Keylesswin32 上的 `dsh --profile <name> --dump-config` 显示带 `windows.cordis.patch.yml` 出处的 pwsh 行、被禁用的 bash 行POSIX 转储CI Linux不变。
- 真实组合冒烟在 win32 上启动 web profilepwsh 栈挂载成功(即本笔记描述的确切清单)。

View File

@@ -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 packages/bundle/base/README.md
README.md: 81932442fe9e2edac82bda88bbe28ce56fd8b6c1
README.zh.md: b0af307b7655a8729a8face324a2750e2e480651
README.md: 84e7f43aaa6cfead0ff28e30063e71062d358f0a
README.zh.md: 30c7c259f8893ebd34da6cab362dedac067ce099

View File

@@ -4,7 +4,7 @@ English | [中文](README.zh.md)
The shared dsh core as a profile bundle: [`cordis.patch.yml`](cordis.patch.yml) inserts every base plugin row — model adapters, tools, persistence, policy, settings/credentials, repository Plugins, telemetry — over the empty profile root, as the first layer of every profile's `dsh.profile.bundles` list. Later bundle layers (e.g. [`dsh-web-app`](../web-app/README.md)) and the user's profile `cordis.patch.yml` override these rows by id; a patch replaces a row's whole `config`, so mode-specific values live in mode bundles, not here. The package has no runtime API; the profile composer resolves the universal patch through the `dsh.bundle.patch` manifest field, and the launcher reads the Windows platform layer below from code on win32 hosts.
Windows hosts booting a shipped profile additionally receive [`windows.cordis.patch.yml`](windows.cordis.patch.yml): it disables the POSIX-only sandboxed stacks — the bash executor/tool, the permission switcher (dsh-permission requires a confining executor), and the sandbox/fs-policy stack — and inserts the PowerShell executor and tool (`@deepseek-ai/dsh-pwsh-local`, `@deepseek-ai/dsh-tool-pwsh`) plus the unconfined `dsh-fs-local`, with the approval policy set to `never`. Windows has no OS sandbox runner (landlock/bwrap/seatbelt are POSIX-only), so the shipped posture is honest danger-full-access rather than a boundary only the fs tools pretend to enforce. The launcher applies the layer between the bundle layers and the user layers on win32 hosts; a Windows host that prefers the bash stack overrides these rows through its profile or home `cordis.patch.yml`. POSIX hosts never receive it.
Windows hosts booting a shipped profile additionally receive [`windows.cordis.patch.yml`](windows.cordis.patch.yml): it disables the POSIX-only sandboxed stacks — the bash executor/tool, the permission switcher (dsh-permission requires a confining executor), the sandbox/fs-policy stack, and the approval service — and inserts the PowerShell executor and tool (`@deepseek-ai/dsh-pwsh-local`, `@deepseek-ai/dsh-tool-pwsh`) plus the unconfined `dsh-fs-local`. Windows has no OS sandbox runner (landlock/bwrap/seatbelt are POSIX-only), so the shipped posture is honest danger-full-access rather than a boundary only the fs tools pretend to enforce; nothing in the roster asks for approval, so the approval service is absent. The launcher applies the layer between the bundle layers and the user layers on win32 hosts; a Windows host that prefers the bash stack overrides these rows through its profile or home `cordis.patch.yml`. POSIX hosts never receive it.
The row set and its rationale are documented inline in the patch file; the [generated composition graph](../../../apps/cli/composition.md) renders it.
@@ -19,4 +19,4 @@ None directly; each inserted row's package owns its effect.
## Known Limitations and Deferred Work
- **A patch replaces whole row configs** — profile overrides must restate every field a row keeps; there is no deep-merge layer.
- **Windows has no sandbox** — no OS runner exists on win32 (landlock/bwrap/seatbelt are POSIX-only), so the Windows platform layer removes the whole sandbox stack: `sandbox`/`sandbox-policy`/`fs-sandbox` are disabled, `dsh-fs-local` provides `ctx.fs`, the permission switcher leaves the roster, and the approval policy is `never`. Everything degrades to danger-full-access: the shell is unconfined and the fs tools make no confinement claims.
- **Windows has no sandbox and no approval** — no OS runner exists on win32 (landlock/bwrap/seatbelt are POSIX-only), so the Windows platform layer removes the whole sandbox stack (`sandbox`/`sandbox-policy`/`fs-sandbox` disabled, `dsh-fs-local` provides `ctx.fs`), the permission switcher leaves the roster, and the approval service is disabled — nothing on Windows asks for approval, so the model is never told approval exists. Everything degrades to danger-full-access: the shell is unconfined and the fs tools make no confinement claims.

View File

@@ -4,7 +4,7 @@
以 profile 组合包形式交付的共享 dsh 核心:[`cordis.patch.yml`](cordis.patch.yml) 在空的 profile 根之上插入全部基础插件行——模型适配器、工具、持久化、策略、settingscredentials、repository 插件、遥测——作为每个 profile 的 `dsh.profile.bundles` 列表中的第一层。后续的组合包层(例如 [`dsh-web-app`](../web-app/README.md))和用户 profile 的 `cordis.patch.yml` 按 id 覆盖这些行patch 会替换目标行的整个 `config`,因此模式专属的值放在各模式组合包中,而不是这里。该包没有运行时 APIprofile 组合器通过 manifest元数据清单`dsh.bundle.patch` 字段解析通用 patch启动器在 win32 主机上通过代码读取下面的 Windows 平台层。
启动交付 profile 的 Windows 主机还会额外收到 [`windows.cordis.patch.yml`](windows.cordis.patch.yml):它禁用仅 POSIX 的受限栈——bash 执行器/工具、权限切换器dsh-permission 要求有限权能力的执行器)sandbox/fs 策略栈——并插入 PowerShell 执行器与工具(`@deepseek-ai/dsh-pwsh-local``@deepseek-ai/dsh-tool-pwsh`)以及不限权的 `dsh-fs-local``approval` 策略设为 `never`。Windows 上没有 OS 级 sandbox runnerlandlock/bwrap/seatbelt 均为 POSIX 专属),因此交付姿态是诚实的 danger-full-access而不是一个只有 fs 工具假装执行的边界。启动器在 win32 主机上把它应用于 bundle 层与用户层之间;偏好 bash 栈的 Windows 主机通过其 profile 或 home 的 `cordis.patch.yml` 覆盖这些行。POSIX 主机永远不会收到它。
启动交付 profile 的 Windows 主机还会额外收到 [`windows.cordis.patch.yml`](windows.cordis.patch.yml):它禁用仅 POSIX 的受限栈——bash 执行器/工具、权限切换器dsh-permission 要求有限权能力的执行器)sandbox/fs 策略栈与 approval 服务——并插入 PowerShell 执行器与工具(`@deepseek-ai/dsh-pwsh-local``@deepseek-ai/dsh-tool-pwsh`)以及不限权的 `dsh-fs-local`。Windows 上没有 OS 级 sandbox runnerlandlock/bwrap/seatbelt 均为 POSIX 专属),因此交付姿态是诚实的 danger-full-access而不是一个只有 fs 工具假装执行的边界;清单里没有任何动作需要审批,因此 approval 服务缺席。启动器在 win32 主机上把它应用于 bundle 层与用户层之间;偏好 bash 栈的 Windows 主机通过其 profile 或 home 的 `cordis.patch.yml` 覆盖这些行。POSIX 主机永远不会收到它。
行集合及其设计依据以行内注释写在 patch 文件里;[生成的组合图](../../../apps/cli/composition.md)负责渲染它。
@@ -19,4 +19,4 @@
## 已知限制与延期工作
- **patch 会替换整行 `config`**profile 覆盖必须重述该行需要保留的每个字段;不存在深度合并层。
- **Windows 上没有沙箱**win32 上不存在 OS 级 runnerlandlock/bwrap/seatbelt 均为 POSIX 专属),因此 Windows 平台层移除整个 sandbox 栈——`sandbox`/`sandbox-policy`/`fs-sandbox` 被禁用,由 `dsh-fs-local` 提供 `ctx.fs`权限切换器离开清单,`approval` 策略为 `never`。一切退化为 danger-full-accessshell 不限权fs 工具也不做任何限权声明。
- **Windows 上没有沙箱、没有 approval**win32 上不存在 OS 级 runnerlandlock/bwrap/seatbelt 均为 POSIX 专属),因此 Windows 平台层移除整个 sandbox 栈——`sandbox`/`sandbox-policy`/`fs-sandbox` 被禁用,由 `dsh-fs-local` 提供 `ctx.fs`——权限切换器离开清单approval 服务也被禁用Windows 上没有任何动作需要审批,模型也不会被告知审批存在。一切退化为 danger-full-accessshell 不限权fs 工具也不做任何限权声明。

View File

@@ -46,7 +46,9 @@ describe('dsh-base bundle', () => {
.map(patch => patch.id)
// The POSIX-only sandboxed stacks leave the Windows roster as one unit:
// shell (bash-sandbox/tool-bash), the permission switcher it requires,
// and the fs/sandbox policy stack whose OS runners do not exist on win32.
// the fs/sandbox policy stack whose OS runners do not exist on win32,
// and the approval service — nothing on Windows asks for approval, so
// the model is never told approval exists or that asks auto-reject.
expect(disables).toEqual(
expect.arrayContaining([
'bash-sandbox',
@@ -56,6 +58,7 @@ describe('dsh-base bundle', () => {
'sandbox',
'sandbox-policy',
'fs-sandbox',
'approval',
]),
)
const inserted = parsed
@@ -64,9 +67,7 @@ describe('dsh-base bundle', () => {
expect(inserted).toEqual(
expect.arrayContaining(['pwsh-local', 'tool-pwsh', 'fs-local']),
)
// Full danger-full-access degradation: no approval prompts on Windows.
expect(parsed.find(patch => patch.id === 'approval')?.config).toEqual({
policy: 'never',
})
// Full danger-full-access degradation: no approval surface at all.
expect(parsed.find(patch => patch.id === 'approval')?.config).toBeUndefined()
})
})

View File

@@ -1,12 +1,14 @@
# The dsh-base Windows platform layer: applied by the dsh launcher on win32
# hosts, between the bundle layers and the user layers. Windows cannot run
# the POSIX-only sandboxed stacks, so this layer swaps the shipped bash stack
# for the PowerShell stack AND drops the sandbox entirely: no OS runner
# exists on Windows (landlock/bwrap/seatbelt are POSIX-only), so any policy
# would be theater — the unconfined shell could bypass fs-only path rules
# with one command. Windows therefore degrades to danger-full-access:
# for the PowerShell stack AND drops the whole permission surface: no OS
# runner exists on Windows (landlock/bwrap/seatbelt are POSIX-only), so any
# policy would be theater — the unconfined shell could bypass fs-only path
# rules with one command. Windows therefore degrades to danger-full-access:
# unconfined pwsh + unconfined fs (`dsh-fs-local`), no permission switcher
# (dsh-permission requires a confining executor), approval policy `never`.
# (dsh-permission requires a confining executor), and no approval service —
# nothing in the roster asks for approval, and the model is never told
# approval exists or that requests are auto-rejected.
# The launcher reads THIS file from the base bundle package (never through
# dsh.bundle.patch — that field names the one universal layer). A Windows
# host that prefers bash or confinement overrides these rows through its
@@ -34,8 +36,7 @@
disabled: true
- id: approval
config:
policy: never
disabled: true
- insert:
- id: pwsh-local

3
pnpm-lock.yaml generated
View File

@@ -898,6 +898,9 @@ importers:
'@deepseek-ai/dsh-credentials-local':
specifier: workspace:^
version: link:../../credentials/credentials-local
'@deepseek-ai/dsh-fs-local':
specifier: workspace:^
version: link:../../fs/fs-local
'@deepseek-ai/dsh-fs-policy':
specifier: workspace:^
version: link:../../fs/fs-policy