Repository navigation
v0.3.0
Breaking: rewritten for dsh 0.1.7
dsh 0.1.7 removed the shell seam's routing field, which is what this plugin's whole pre-0.1.7 design was built on. Loading the old version under 0.1.7 made shell-select fail to import, which left ctx.shell unprovided, which in turn left every plugin that injects shell — our own two tool rows, dsh's tool-pwsh, and permission-presets — stuck at pending (waiting for service: shell).
- The selector is gone; each tool owns its executor.
ShellExecRequestno longer carriesshell, so there is no routing input left for a shared seat to dispatch on — the tool is what knows which shell it wants.git_bash/wsl_bashnow each construct their own executor (on a child fiber with an isolatedshellscope, so the cordis duplicate-service rule is respected) and drive it directly. - The plugin no longer occupies
ctx.shell. The patch no longer disablespwsh-sandbox(norshell-select): the base bundle's own row keeps providing the seat, so pwsh,tool-pwshandpermission-presetswork exactly as they would without this plugin installed. This is also the regression the smoke fixture now guards. - Config moved onto the tool rows. The
win-mb-shell-selectrow is removed;gitBash:/wslBash:now live on the row that uses them:- id: win-mb-tool-git name: 'dsh-win-multi-bash/tool-git-bash' config: gitBash: { bashPath: '...' }
install.ps1writes the new shape, and because it replaces its managed block by marker, re-running it over an old installation migrates cleanly (uninstall.ps1likewise still removes either generation). A hand-maintained oldwin-mb-shell-selectblock is not marker-managed, so replace it by hand.
API migration
ShellExecutor.run()/start()→ a singleexecute(spec)returning aShellExecution; foreground is(await execute(spec)).result(), background is the same handle'sdone/readOutput/kill. Both executors were re-based ontoexecuteArgv(spec, argvOrPrepare, onStarted)+ a memoizedresult()decoration, mirroring the base runtime's ownSandboxBashExecutor.ctx.sandbox.confine()is now async and takes an optionalAbortSignal; the windows-acl probe awaits it.- The confinement probe can no longer be answered synchronously, so the tool layer awaits a new
resolveSandboxMode()before it registers: the escalation surface (sandbox_permissions,@deepseek-ai/dsh-sandbox'sESCALATION_TARGETS) is still settled before the model ever sees the tool. A backend whose probe fails loud (an explicitsandbox: bwrapwith bubblewrap missing) is deliberately swallowed at registration so it cannot brick the tool row — its error resurfaces at the first routed command, which is the documented lazy contract.requireSandboxis unchanged in timing: it declares the mode regardless of the probe, so the tool still advertises the escalation surface, and the refusal happens when a command is actually attempted underread-only/workspace-writerather than at row load. - Background jobs follow the new registry contract: the model's reads are pumped from
outputpull sources declared atjobs.start, not returned from the starter (readOutputis gone), andownertakes a session id. Per-process sandbox facts continue to be stamped byonProcessDonebeforedonesettles, sorunnerFailed/deniedstill reach both the job outcome and the foreground renderer. settings.installSection/SHELL_SETTINGS_NAMESPACEwere removed upstream. Settings are now schema-driven per profile entry, so the plugin'sConfigschema is the settings surface — no registration call is needed (and none is made).
Bug fixes
wsl_bashnow pins its start directory explicitly.argv()/bwrapArgv()used to add--cdonly when the workdir differed from adefaultWorkdir()helper — but that helper returned thecwdvolatile wrapper object instead of its value (this.config.cwd ?? process.cwd()rather thanthis.config.cwd.get() ?? process.cwd()), so the comparison never matched and--cdwas passed on every command anyway. Rather than revive the skip, the dead helper is gone and--cdis now unconditional: the auto-cd contract (a command with no explicitworkdirstarts in the WSL view of the session directory,/mnt/<drive>/...) becomes a property of the spec instead of a property of the distro's cwd-inheritance and automount settings. This is behaviour-preserving — it is what every deployment already ran — andwsl.exeaccepts both Windows and Linux workdirs for--cd.
Cleanup
- Deleted
lib/tool-bash/index.js— a stale pre-bundled copy of the upstream tool with no importers. - Deleted the unreferenced POSIX
bashtool instance (lib/tool-bash/types/index.js) — never exported by this package, never wired into the patch. - Deleted 5 unwired
invariant.jscompanions. They registered no-op invariants under upstream package names (@deepseek-ai/dsh-tool-bash,@deepseek-ai/dsh-bash-git, …) that this package does not own; the real packages already register their own. - Peer floors raised from
>=0.1.0-rc.7to>=0.1.7-rc.2;dsh-pwsh-sandbox,dsh-pwsh-localanddsh-settingsdropped frompeerDependencies(nothing imports them any more), and the./shell-selectexport is gone.
Verification
smoke/run.ps1 boots a real 0.1.7-rc.2 composition. In both the schema-default and pinned bashPath variants it reports: git_bash / wsl_bash registered with the expected MSYS / WSL descriptions, one real command executed through each, shellSeatPresent: true (the base bundle's ctx.shell provider still alive beside our rows — the exact condition that failed before), and a real pwsh command executed through that seat. smoke/audit.test.mjs runs 129 assertions over the executor internals, backend ownership, the background-job adapters, the tool config schemas and the patch/misconfiguration matrices — including a real-spawn check that a wsl_bash command with no workdir auto-cds to /mnt/<drive>/....