# The bundle layer this package contributes:
#
#     "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
#
# `dsh plugin --profile <name> add dsh-plugin-wsl-env` installs the package and
# appends this file as one layer — there is nothing to merge by hand. It is
# still an ordinary patch layer: a top-level YAML array of loader patch entries
# (id-targeted config overrides, disables, and insert lists; `!!js` expressions
# allowed).
#
# Layers compose in the order bundles, the profile's own `cordis.patch.yml`,
# the home-level `cordis.patch.yml`, then `--patch` overlays, and later layers
# win per row. A row's `config` is replaced whole, never deep-merged, so a user
# who wants a different value overrides that row by id in their own layer —
# examples/profile.cordis.patch.yml shows what such a layer looks like.
#
# ═════════════════════════════════════════════════════════════════════════════
# The GUI terminal window is a COMPOSITION-level environment, not a preset one.
#
# The right-sidebar terminal is dsh-api-terminal-controller. It resolves its
# execution world from the Session's own context —
#     agent.ctx.get("subprocess")
# — and that context is created by the agent loop under the ROOT realm
# (dsh-agent-loop: `createScope(loopCtx, …)`, loopCtx = the root `ctx.agents`),
# while a preset's isolate realms are created by dsh-agent-preset-registry under
# the REGISTRY's context (`createScope(this.owner, …)`). The two subtrees never
# meet, so a `subprocess` provider mounted inside preset-wsl — the natural-looking
# design, since `shell` and `fs` live there — is invisible to the terminal
# window. The registry ships `agentPresets.serviceFor(agent, name)` for exactly
# that gap; the terminal controller does not use it.
#
# So the terminal is bound here, once, for the whole composition:
#
#   1. the shipped local provider is disabled and replaced by a subclass whose
#      ONLY override is `spawnTerminal`, which rewrites a `wsl.exe` launch into
#      `wsl.exe -d <distro> --cd <linux dir>` for the directory the Session
#      supplies. Ordinary `spawn()` — every command, the host ripgrep search,
#      every other consumer — reaches the shipped implementation untouched;
#   2. the controller is given the one shell profile it should resolve, so the
#      window's default is a distro shell rather than `cmd.exe` inside a UNC
#      workspace. Its shell menu is reduced to that single entry on purpose:
#      shell discovery also runs against the root provider and receives no
#      directory, so offering host executables there would advertise shells this
#      terminal never starts.
#
# Consequence: a terminal's EXECUTION WORLD follows the Session — a WSL-folder
# session runs the distro's shell, a Windows-folder session runs PowerShell in
# its own directory (`hostSessions` on `subprocess-wsl`) — while the shell menu
# and the tab title stay the composition's single `WSL` profile, which no
# Session identity reaches. The title that implies is documented in
# docs/LIMITATIONS.md and bridged upstream in docs/UPSTREAM-TERMINAL-TITLE.md.
# ═════════════════════════════════════════════════════════════════════════════
- id: subprocess
  disabled: true

- id: terminal-controller
  name: '@deepseek-ai/dsh-api-terminal-controller'
  config:
    shell:
      path: wsl.exe
      name: WSL
      args: []
    shellCandidates: []

- insert:
    - id: subprocess-wsl
      name: 'dsh-plugin-wsl-env/subprocess'
      config:
        distro: ''

# ═════════════════════════════════════════════════════════════════════════════
# Environment is per-session, not per-process.
#
# There is no DSH_WSL switch any more. The profile keeps the SHIPPED environment
# for host workspaces — sandboxed PowerShell executor, ACL-confined filesystem,
# and therefore the Permissions selector — while the wsl agent preset (appended
# below) supplies the WSL environment to agents that select it, isolated to that
# preset's own service realm. Both can run at the same time in one process.
#
# dsh-plugin-wsl-env/auto-preset binds the WSL preset automatically when a new
# session's workspace directory is inside a distro, and leaves an explicit
# operator choice alone.
# ═════════════════════════════════════════════════════════════════════════════

# ── The workspace picker serves BOTH worlds ──────────────────────────────────
#
# Kept from the previous design: the shipped chooser resolves to the native OS
# dialog on Windows, which cannot browse a distro. This one lists the Windows home
# directory and every installed distro in its root level, so it is the single
# picker for host and WSL workspaces alike — and the directory it returns is what
# auto-preset reads to choose the environment.
- id: directory-picker
  disabled: true

- insert:
    - id: directory-picker-wsl
      name: 'dsh-plugin-wsl-env/picker'
      config:
        preferredDistro: ''
        includeHostHome: true
        maxEntries: 1000

    - id: directory-picker-browse-surface
      name: '@deepseek-ai/dsh-client-ui-directory-picker-browse'

# ═════════════════════════════════════════════════════════════════════════════
# The distro-side takeovers of the two root-plane services.
#
# A patch layer is a flat list: an `id:` entry patches the shipped row, and a
# bare `insert:` list appends new rows as data. A disable written INSIDE an
# insert list is not a patch — it becomes a duplicate-id data row that shadows
# the real service row (the loader keys rows by id, last occurrence wins), and
# a nested `insert:` list is never interpreted at all. Both takeovers are
# therefore two top-level entries: disable the shipped row by id, then append
# the replacement with a bare insert. test/composition.test.mjs runs this file
# through the real patch algorithm to keep the shape honest.
# ═════════════════════════════════════════════════════════════════════════════

# The GUI file tree and previews for a distro workspace are served from inside
# the distro (resident agent) instead of Windows-side 9P walks. The variant
# extends the shipped service — a distro UNC routes distro-side, a drive path
# calls super (host native), the watch stream delegates unchanged — so
# replacing the row is safe for Windows-folder sessions too.
- id: workspace-files
  disabled: true

# The root-plane filesystem routes by coordinate: distro UNC identities are
# served by the resident agent (real watch events, ext4 reads), drive paths
# stay host-native. Replaces the root fs-sandbox row, whose write fence has no
# root-plane consumers (the model's writes ride the preset filesystems; see
# docs/root-fs-routing.md).
- id: fs-sandbox
  disabled: true

- insert:
    - id: workspace-files-wsl
      name: 'dsh-plugin-wsl-env/workspace-files-wsl'
      config:
        maxBytes: 2097152
        maxFileBytes: 33554432
        maxLines: 5000
        maxEntries: 2000

    - id: fs-routing
      name: 'dsh-plugin-wsl-env/fs-routing'
      config:
        distro: ''
        wslPath: wsl.exe
        sandbox: true
        maskWindowsDrive: false
        restrictToDistro: true
        watchMaxDepth: 0
    # Contributes the WSL environment's facts (distro / real shell / home) to the
    # managed DSH_* namespace that dsh-shell-env injects into every model shell
    # call. That is DSH's sanctioned channel for shell-environment facts.
    - id: wsl-shell-env
      name: 'dsh-plugin-wsl-env/shell-env'

    # Per-session environment selection from the workspace directory. It only
    # rewrites a preset that still equals the registry default, so an operator's
    # explicit choice is never overridden.
    - id: auto-preset
      name: 'dsh-plugin-wsl-env/auto-preset'

    # Session-start sandbox preflight: when a distro session opens, run the
    # providers' own bwrap usability probe (shared cache, so the first confined
    # command pays nothing) and surface a missing or broken bubblewrap as a
    # warning with the full remedy — instead of the first command's fail-closed
    # error being the discovery moment. Advisory only: it never installs and
    # never changes the providers' fail-closed semantics.
    - id: wsl-preflight
      name: 'dsh-plugin-wsl-env/preflight'

- insert:
    # ═════════════════════════════════════════════════════════════════════════════
    # preset-wsl — the WSL environment, scoped to agents that select this preset.
    #
    # This is what makes environments per-session instead of per-process: the shell
    # and fs services are isolated to this preset's realm, so an agent on the host
    # preset keeps the profile's global sandboxed PowerShell executor and ACL-confined
    # filesystem while an agent on this preset gets the distro's login shell and its
    # UNC-backed filesystem — concurrently, in one process.
    #
    # The wrapping group is required by dsh-agent-preset-registry, which audits every
    # preset mount and rejects a provider that is not behind an isolate realm:
    #     "Preset services require isolate realms: shell, fs"
    # Because a preset's plugins mount under a REGISTRY-owned scope (not this row's
    # ctx), the realm has to be declared here, inside config.plugins.
    #
    # Generated from the shipped standard preset by build-preset-wsl.mjs so the
    # embedded plan-mode section and every nested group survive byte-for-byte.
    # Re-run it after an app update that changes the preset.
    # ═════════════════════════════════════════════════════════════════════════════
    - id: preset-wsl
      name: '@deepseek-ai/dsh-agent-preset'
      config:
        id: wsl
        order: 2
        plugins:
          - id: wsl-env
            name: cordis:group
            group: true
            isolate:
              shell: true
              fs: true
            config:
              # ── the WSL environment for this preset only ────────────────────
              # No `cwd`: an empty default workdir means the distro user's home,
              # asked of the distro itself. Pin a directory here to override it —
              # a value that is right on one machine is wrong on every other.
              #
              # Each command is confined inside the distro by bubblewrap, in the
              # policy's mode, and the file tools are fenced by the same policy.
              # Install bubblewrap there (`sudo apt install bubblewrap`); without
              # it the providers fail closed rather than run unconfined. Add
              # `sandbox: false` to either provider for the old, unconfined
              # posture.
              - id: wsl-shell
                name: 'dsh-plugin-wsl-env'
                config:
                  distro: ''
                  loginShell: true
                  timeoutMs: 120000
                  maxTimeoutMs: 600000
              - id: wsl-fs
                name: 'dsh-plugin-wsl-env/fs'
                config:
                  distro: ''
                  restrictToDistro: true
                  # agent (the default): reads/writes/identities run inside the
                  # distro over the resident agent (lib/fs-substrate.js) —
                  # native symlinks and mode bits, the write guard survives to
                  # publication, and confined mutations are kernel-enforced.
                  # share: the opt-out — the Windows-side host fs stack over
                  # the 9p share.
                  substrate: agent
              - id: persona
                name: '@deepseek-ai/dsh-persona'
                config:
                  suffix: Your working directory is {{cwd}}.
                  prefix: You are a coding agent powered by the {{model}} model.
              - id: agent-instructions
                name: '@deepseek-ai/dsh-agent-instructions'
                config:
                  maxBytes: 65536
              - id: tool-bash
                name: '@deepseek-ai/dsh-tool-bash'
                disabled: false
              - id: tool-pwsh
                name: '@deepseek-ai/dsh-tool-pwsh'
                disabled: true
              - id: tool-fs
                name: '@deepseek-ai/dsh-tool-fs'
              - id: tool-fs-search
                name: '@deepseek-ai/dsh-tool-fs-search'
                config:
                  sampleOverCapGlobResults: false
              - id: tool-jobs
                name: '@deepseek-ai/dsh-tool-jobs'
              - id: skill-filesystem
                name: '@deepseek-ai/dsh-skill-filesystem'
              - id: tool-skill
                name: '@deepseek-ai/dsh-tool-skill'
              - id: command-goal
                name: '@deepseek-ai/dsh-command-goal'
              - id: tool-goal
                name: '@deepseek-ai/dsh-tool-goal'
              - id: planning
                name: cordis:group
                group: true
                isolate:
                  planMode: true
                config:
                  - id: plan-mode
                    name: '@deepseek-ai/dsh-plan-mode'
                    config:
                      section: |
                        You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.

                        Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.

                        The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.

                        Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.

                        Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.

                        When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
              - id: compaction
                name: cordis:group
                group: true
                isolate:
                  compaction: true
                  toolResultPruner: true
                config:
                  - id: compaction-basic
                    name: '@deepseek-ai/dsh-compaction-basic'
                  - id: command-compact
                    name: '@deepseek-ai/dsh-command-compact'
                  - id: tool-result-pruner
                    name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
                    config:
                      thresholdChars: 8192
                      headChars: 4096
                      tailChars: 1024
              - id: delegation
                name: cordis:group
                group: true
                isolate:
                  workflowEngine: true
                config:
                  - id: tool-subagent-control
                    name: '@deepseek-ai/dsh-tool-subagent-control'
                  - id: tool-subagent-list-agents
                    name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
                  - id: tool-subagent
                    name: '@deepseek-ai/dsh-tool-subagent'
                    config:
                      provider: spawn
                      toolName: subagent
                      modelSelectionSettings: true
                      backgroundMode: continuable
                  - id: tool-subagent-fork
                    name: '@deepseek-ai/dsh-tool-subagent'
                    config:
                      provider: fork
                      toolName: subagent_fork
                      backgroundMode: continuable
                  - id: tool-subagent-codex
                    name: '@deepseek-ai/dsh-tool-subagent'
                    disabled: true
                    config:
                      provider: codex
                      toolName: subagent_codex
                      backgroundMode: one-shot
                      maxDepth: provider-managed
                  - id: tool-subagent-claude-code
                    name: '@deepseek-ai/dsh-tool-subagent'
                    disabled: true
                    config:
                      provider: claude-code
                      toolName: subagent_claude_code
                      backgroundMode: one-shot
                      maxDepth: provider-managed
                  - id: workflow-ptc
                    name: '@deepseek-ai/dsh-workflow-ptc'
                    config:
                      provider: spawn
                  - id: tool-workflow
                    name: '@deepseek-ai/dsh-tool-workflow'
                  - id: tool-ralph
                    name: '@deepseek-ai/dsh-tool-ralph'
                    disabled: true
                    config:
                      subagentProvider: spawn
                      maxRounds: 64
              - id: tool-ask-user
                name: '@deepseek-ai/dsh-tool-ask-user'
              - id: tool-todo
                name: '@deepseek-ai/dsh-tool-todo'
                config:
                  allowParallelInProgress: true
              - id: tool-web
                name: '@deepseek-ai/dsh-tool-web'
                config:
                  fetch: true
                  searchTimeoutMs: 60000
              - id: present
                name: '@deepseek-ai/dsh-tool-present'
              - id: tool-plugin-manager
                name: '@deepseek-ai/dsh-plugin-manager/tools'
                disabled: true
