diff --git a/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.i18n.yaml b/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.i18n.yaml index e26d1ca354..711699aa0b 100644 --- a/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.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/feature/2026-08-01-windows-pwsh-default.md -2026-08-01-windows-pwsh-default.md: 1fa30ef757ccb81544479a08887355839fb86d46 -2026-08-01-windows-pwsh-default.zh.md: c6e1361dba29cff8340ef6e26bf071f81544642b +2026-08-01-windows-pwsh-default.md: ceffd825c2316841dfbe01f4510243ba8748e500 +2026-08-01-windows-pwsh-default.zh.md: ea8058878fe268bdf1317620c853b8279c543532 diff --git a/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.md b/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.md index 1fa30ef757..ceffd825c2 100644 --- a/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.md +++ b/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.md @@ -12,9 +12,9 @@ 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), disables `permission`/`ui-permission` (dsh-permission requires a confining executor — presets bundle a sandbox mode the unconfined pwsh executor cannot honor; see its constructor guard — and the client knob would advertise a shell it cannot enforce), and inserts `pwsh-local`/`tool-pwsh`. The fs tools keep the sandbox policy and approval service, so file confinement and escalation still apply on Windows. -- **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 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`; the base bundle lists every row plugin as a dependency by house style. +- **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 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. The pwsh GUI rendering stage (stage 2 of the original roadmap) shipped earlier with the [pwsh UI presentation matches bash decision](2026-08-05-pwsh-ui-bash-parity.md); the [pwsh tool bash parity decision](2026-08-02-pwsh-tool-bash-parity.md) ships the tool's surface. Nothing in this decision changes POSIX behavior. @@ -24,19 +24,21 @@ The pwsh GUI rendering stage (stage 2 of the original roadmap) shipped earlier w **Ship the platform layer from `apps/cli` code instead of a bundle data file.** Rejected: the patch belongs next to the rows it replaces, in the bundle that owns them, so the shipped roster stays visible as composition data and dumps carry its provenance; the launcher contributes only the win32 gate. -**Keep `permission`/`ui-permission` on Windows.** Rejected: `dsh-permission` hard-requires `ctx.bash.sandboxMode` and fails loud at load over an unconfined executor; making it tolerate an unconfined shell would advertise presets the shell cannot honor. File confinement keeps working through the fs stack. +**Keep `permission`/`ui-permission` on Windows.** Rejected: `dsh-permission` hard-requires `ctx.bash.sandboxMode` and fails loud at load over an unconfined executor; making it tolerate an unconfined shell would advertise presets the shell cannot honor. + +**Keep fs path-rule confinement on Windows (`sandbox-policy` + `fs-sandbox` without OS runners).** Rejected: the shell is the model's primary tool and unconfined on Windows, so fs-only path rules are trivially bypassable and would overstate the boundary; the honest posture is full degradation to danger-full-access. **Ship a `DSH_WINDOWS_SHELL` environment escape hatch.** Rejected: decisive behavior changes belong in composition config, which already overrides the platform layer row by id; a second override channel would split the single source of truth for roster decisions. ## 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. - 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. -- The permission switcher leaves the Windows roster; session permission facts pin the composition defaults through the approval service and sandbox policy. ## 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. +- 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). - Keyless: a win32 `dsh --profile --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). diff --git a/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.zh.md b/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.zh.md index c6e1361dba..ea8058878f 100644 --- a/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.zh.md +++ b/.agents/notes/implemented/feature/2026-08-01-windows-pwsh-default.zh.md @@ -12,9 +12,9 @@ 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 的执行器及其方言工具)、禁用 `permission`/`ui-permission`(dsh-permission 要求有限权能力的执行器——preset 捆绑的是无限制 pwsh 执行器无法兑现的 sandbox 模式;见其构造函数守卫——客户端旋钮会宣传一个它无法强制执行的 shell),并插入 `pwsh-local`/`tool-pwsh`。fs 工具保留 sandbox 策略与批准服务,因此 Windows 上的文件限制与升级仍然生效。 -- **启动器按平台注入该层。** `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`;按仓库惯例,base bundle 把每个行插件都列为依赖。 +- **平台层是数据文件,不是清单重写。** `@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 runner(landlock/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 工具假装执行的边界。 +- **启动器按平台注入该层。** `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 把每个行插件都列为依赖。 原路线图的阶段 2(pwsh GUI 渲染)已随 [pwsh UI 呈现与 bash 对齐决策](2026-08-05-pwsh-ui-bash-parity.md) 先行交付;[pwsh 工具与 bash 对齐决策](2026-08-02-pwsh-tool-bash-parity.md) 交付了工具表面。本决策不改变任何 POSIX 行为。 @@ -24,19 +24,21 @@ harness 交付的执行画像在每个平台都是 bash 优先。Windows 主机 **从 `apps/cli` 代码而非 bundle 数据文件交付平台层。** 否决:patch 应放在它替换的行旁边、属于拥有这些行的 bundle,让交付清单作为组合数据保持可见、转储带有出处;启动器只贡献 win32 门控。 -**在 Windows 上保留 `permission`/`ui-permission`。** 否决:`dsh-permission` 硬性要求 `ctx.bash.sandboxMode`,在无限制执行器上加载即 fail loud;让它容忍无限制 shell 会宣传 shell 无法兑现的 preset。文件限制继续经由 fs 栈生效。 +**在 Windows 上保留 `permission`/`ui-permission`。** 否决:`dsh-permission` 硬性要求 `ctx.bash.sandboxMode`,在无限制执行器上加载即 fail loud;让它容忍无限制 shell 会宣传 shell 无法兑现的 preset。 + +**在 Windows 上保留 fs 路径规则限制(无 OS runner 的 `sandbox-policy` + `fs-sandbox`)。** 否决:shell 是模型的主工具且在 Windows 上不限权,仅限 fs 的路径规则一行命令即可绕过,会夸大边界;诚实的姿态是完全退化到 danger-full-access。 **交付 `DSH_WINDOWS_SHELL` 环境变量逃生门。** 否决:决定性的行为变更应集中在组合配置中,而组合配置已能按行 id 覆盖平台层;第二条覆盖通道会分裂清单决策的单一事实来源。 ## 后果 - 运行交付版 `dsh` 表面的 Windows 主机无需配置即获得 `pwsh` 作为 shell 工具、PowerShell 作为 `ctx.bash` 执行器;那里的模型可见清单中没有 `bash`(其工具行被禁用)。 +- Windows 上没有任何沙箱:fs 工具不限权运行(`dsh-fs-local`)、`approval` 策略为 `never`、权限切换器消失。模型可见的姿态是诚实的全权访问,而不是一个 shell 可以绕过的边界。 - POSIX 主机不变:平台层永不生效,bash 栈仍是通用 `cordis.patch.yml` 的行。 - 偏好 bash 栈的 Windows 主机(例如 PATH 上有 WSL/Git-Bash 时)通过其 profile 或 home 的 `cordis.patch.yml` 覆盖交付默认——组合配置是唯一的覆盖通道。 -- 权限切换器离开 Windows 清单;会话权限事实通过批准服务与 sandbox 策略固定组合默认值。 ## 验证 -- 单元:`apps/cli/tests/windows-shell.spec.ts` 固定 win32 默认、自定义 profile 跳过与缺文件失败,平台注入。 +- 单元:`apps/cli/tests/windows-shell.spec.ts` 固定 win32 默认、自定义 profile 跳过与缺文件失败,平台注入;`packages/bundle/base/tests/base.spec.ts` 固定交付的 Windows 清单(禁用、插入与 `never` approval 策略)。 - Keyless:win32 上的 `dsh --profile --dump-config` 显示带 `windows.cordis.patch.yml` 出处的 pwsh 行、被禁用的 bash 行;POSIX 转储(CI Linux)不变。 - 真实组合冒烟在 win32 上启动 web profile,pwsh 栈挂载成功(即本笔记描述的确切清单)。 diff --git a/packages/bundle/base/README.i18n.yaml b/packages/bundle/base/README.i18n.yaml index a47ac9cbc7..0cc7f47a1b 100644 --- a/packages/bundle/base/README.i18n.yaml +++ b/packages/bundle/base/README.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 packages/bundle/base/README.md -README.md: a4f220c956f7f67d7810827d4488d54a071ad3d6 -README.zh.md: eaf96f8c0640481163e7a1c1533a07e35ecc294f +README.md: 81932442fe9e2edac82bda88bbe28ce56fd8b6c1 +README.zh.md: b0af307b7655a8729a8face324a2750e2e480651 diff --git a/packages/bundle/base/README.md b/packages/bundle/base/README.md index a4f220c956..81932442fe 100644 --- a/packages/bundle/base/README.md +++ b/packages/bundle/base/README.md @@ -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 bash executor/tool and the permission stack (dsh-permission requires a confining executor), and inserts the PowerShell executor and tool (`@deepseek-ai/dsh-pwsh-local`, `@deepseek-ai/dsh-tool-pwsh`). The launcher applies it 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), 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. 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 loses the permission switcher** — `dsh-permission` hard-requires a confining `ctx.bash` executor, so the Windows platform layer disables `permission`/`ui-permission` with the bash stack. The fs tools keep the sandbox policy and the approval service, so file confinement and escalation still apply on Windows. +- **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. diff --git a/packages/bundle/base/README.zh.md b/packages/bundle/base/README.zh.md index eaf96f8c06..b0af307b76 100644 --- a/packages/bundle/base/README.zh.md +++ b/packages/bundle/base/README.zh.md @@ -4,7 +4,7 @@ 以 profile 组合包形式交付的共享 dsh 核心:[`cordis.patch.yml`](cordis.patch.yml) 在空的 profile 根之上插入全部基础插件行——模型适配器、工具、持久化、策略、settings/credentials、repository 插件、遥测——作为每个 profile 的 `dsh.profile.bundles` 列表中的第一层。后续的组合包层(例如 [`dsh-web-app`](../web-app/README.md))和用户 profile 的 `cordis.patch.yml` 按 id 覆盖这些行;patch 会替换目标行的整个 `config`,因此模式专属的值放在各模式组合包中,而不是这里。该包没有运行时 API;profile 组合器通过 manifest(元数据清单)的 `dsh.bundle.patch` 字段解析通用 patch,启动器在 win32 主机上通过代码读取下面的 Windows 平台层。 -启动交付 profile 的 Windows 主机还会额外收到 [`windows.cordis.patch.yml`](windows.cordis.patch.yml):它禁用仅 POSIX 的 bash 执行器/工具与权限栈(dsh-permission 要求有限权能力的执行器),并插入 PowerShell 执行器与工具(`@deepseek-ai/dsh-pwsh-local`、`@deepseek-ai/dsh-tool-pwsh`)。启动器在 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 策略栈——并插入 PowerShell 执行器与工具(`@deepseek-ai/dsh-pwsh-local`、`@deepseek-ai/dsh-tool-pwsh`)以及不限权的 `dsh-fs-local`,`approval` 策略设为 `never`。Windows 上没有 OS 级 sandbox runner(landlock/bwrap/seatbelt 均为 POSIX 专属),因此交付姿态是诚实的 danger-full-access,而不是一个只有 fs 工具假装执行的边界。启动器在 win32 主机上把它应用于 bundle 层与用户层之间;偏好 bash 栈的 Windows 主机通过其 profile 或 home 的 `cordis.patch.yml` 覆盖这些行。POSIX 主机永远不会收到它。 行集合及其设计依据以行内注释写在 patch 文件里;[生成的组合图](../../../apps/cli/composition.md)负责渲染它。 @@ -19,4 +19,4 @@ ## 已知限制与延期工作 - **patch 会替换整行 `config`**:profile 覆盖必须重述该行需要保留的每个字段;不存在深度合并层。 -- **Windows 上失去权限切换器**:`dsh-permission` 硬性要求有限权能力的 `ctx.bash` 执行器,因此 Windows 平台层随 bash 栈一起禁用 `permission`/`ui-permission`。fs 工具保留 sandbox 策略与批准服务,Windows 上的文件限制与升级仍然生效。 +- **Windows 上没有沙箱**:win32 上不存在 OS 级 runner(landlock/bwrap/seatbelt 均为 POSIX 专属),因此 Windows 平台层移除整个 sandbox 栈——`sandbox`/`sandbox-policy`/`fs-sandbox` 被禁用,由 `dsh-fs-local` 提供 `ctx.fs`,权限切换器离开清单,`approval` 策略为 `never`。一切退化为 danger-full-access:shell 不限权,fs 工具也不做任何限权声明。 diff --git a/packages/bundle/base/package.json b/packages/bundle/base/package.json index cb2b7178f7..d2f93e2bd3 100644 --- a/packages/bundle/base/package.json +++ b/packages/bundle/base/package.json @@ -46,6 +46,7 @@ "@deepseek-ai/dsh-compact-basic": "workspace:^", "@deepseek-ai/dsh-compact-tool-result-prune": "workspace:^", "@deepseek-ai/dsh-credentials-local": "workspace:^", + "@deepseek-ai/dsh-fs-local": "workspace:^", "@deepseek-ai/dsh-fs-policy": "workspace:^", "@deepseek-ai/dsh-fs-sandbox": "workspace:^", "@deepseek-ai/dsh-goal": "workspace:^", diff --git a/packages/bundle/base/tests/base.spec.ts b/packages/bundle/base/tests/base.spec.ts index 24ee2a1ba3..6adcab1715 100644 --- a/packages/bundle/base/tests/base.spec.ts +++ b/packages/bundle/base/tests/base.spec.ts @@ -13,13 +13,60 @@ import { entryListSchema } from '@cordisjs/plugin-include' describe('dsh-base bundle', () => { it('declares a parseable patch list through the dsh.bundle.patch manifest field', () => { const root = fileURLToPath(new URL('..', import.meta.url)) - const manifest = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8')) as { dsh?: { bundle?: { patch?: string } } } + const manifest = JSON.parse( + readFileSync(resolve(root, 'package.json'), 'utf8'), + ) as { dsh?: { bundle?: { patch?: string } } } expect(manifest.dsh?.bundle?.patch).toBe('./cordis.patch.yml') - const parsed = yaml.load(readFileSync(resolve(root, manifest.dsh!.bundle!.patch!), 'utf8'), { schema: entryListSchema }) + const parsed = yaml.load( + readFileSync(resolve(root, manifest.dsh!.bundle!.patch!), 'utf8'), + { schema: entryListSchema }, + ) expect(Array.isArray(parsed)).toBe(true) // The base layer is one insert list over the empty profile root. - const rows = (parsed as { insert?: { id?: string }[] }[]).flatMap(patch => patch.insert ?? []) + const rows = (parsed as { insert?: { id?: string }[] }[]).flatMap( + patch => patch.insert ?? [], + ) expect(rows.length).toBeGreaterThan(50) expect(rows.some(row => row.id === 'agent-loop')).toBe(true) }) + + it('ships the Windows platform layer as the documented danger-full-access roster', () => { + const root = fileURLToPath(new URL('..', import.meta.url)) + const parsed = yaml.load( + readFileSync(resolve(root, 'windows.cordis.patch.yml'), 'utf8'), + { schema: entryListSchema }, + ) as { + id?: string + disabled?: boolean + insert?: { id?: string; name?: string }[] + config?: { policy?: string } + }[] + const disables = parsed + .filter(patch => patch.disabled === true) + .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. + expect(disables).toEqual( + expect.arrayContaining([ + 'bash-sandbox', + 'tool-bash', + 'permission', + 'ui-permission', + 'sandbox', + 'sandbox-policy', + 'fs-sandbox', + ]), + ) + const inserted = parsed + .flatMap(patch => patch.insert ?? []) + .map(row => row.id) + 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', + }) + }) }) diff --git a/packages/bundle/base/windows.cordis.patch.yml b/packages/bundle/base/windows.cordis.patch.yml index 59d5e81582..50dbe8d9e5 100644 --- a/packages/bundle/base/windows.cordis.patch.yml +++ b/packages/bundle/base/windows.cordis.patch.yml @@ -1,18 +1,16 @@ # The dsh-base Windows platform layer: applied by the dsh launcher on win32 -# hosts, between the bundle layers and the user layers, replacing the -# POSIX-only bash stack with the PowerShell stack. 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 -# overrides the rows here through its profile or home cordis.patch.yml. -# -# Windows hosts cannot run the shipped bash executor (POSIX-only: hardcoded -# `bash -c` argv and process-group semantics), so the shipped Windows -# experience is PowerShell-native: pwsh-local backs `ctx.bash` and tool-pwsh -# is the model-facing shell tool. dsh-permission requires a confining -# executor (its presets bundle a sandbox mode the unconfined pwsh executor -# cannot honor), so the permission service and its client knob leave the -# Windows roster with the bash stack; the fs tools keep the sandbox policy -# and the approval service, so file confinement and escalation still apply. +# 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: +# unconfined pwsh + unconfined fs (`dsh-fs-local`), no permission switcher +# (dsh-permission requires a confining executor), approval policy `never`. +# 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 +# profile or home cordis.patch.yml. - id: bash-sandbox disabled: true @@ -26,9 +24,25 @@ - id: ui-permission disabled: true +- id: sandbox + disabled: true + +- id: sandbox-policy + disabled: true + +- id: fs-sandbox + disabled: true + +- id: approval + config: + policy: never + - insert: - id: pwsh-local name: '@deepseek-ai/dsh-pwsh-local' - id: tool-pwsh name: '@deepseek-ai/dsh-tool-pwsh' + + - id: fs-local + name: '@deepseek-ai/dsh-fs-local'