Skip to content

v0.3.0

Choose a tag to compare

@Dinosaur-MC Dinosaur-MC released this 27 Sep 18:09
· 88 commits to main since this release

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. ShellExecRequest no longer carries shell, 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_bash now each construct their own executor (on a child fiber with an isolated shell scope, so the cordis duplicate-service rule is respected) and drive it directly.
  • The plugin no longer occupies ctx.shell. The patch no longer disables pwsh-sandbox (nor shell-select): the base bundle's own row keeps providing the seat, so pwsh, tool-pwsh and permission-presets work 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-select row 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.ps1 writes the new shape, and because it replaces its managed block by marker, re-running it over an old installation migrates cleanly (uninstall.ps1 likewise still removes either generation). A hand-maintained old win-mb-shell-select block is not marker-managed, so replace it by hand.

API migration

  • ShellExecutor.run() / start() → a single execute(spec) returning a ShellExecution; foreground is (await execute(spec)).result(), background is the same handle's done / readOutput / kill. Both executors were re-based onto executeArgv(spec, argvOrPrepare, onStarted) + a memoized result() decoration, mirroring the base runtime's own SandboxBashExecutor.
  • ctx.sandbox.confine() is now async and takes an optional AbortSignal; 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's ESCALATION_TARGETS) is still settled before the model ever sees the tool. A backend whose probe fails loud (an explicit sandbox: bwrap with 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. requireSandbox is 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 under read-only / workspace-write rather than at row load.
  • Background jobs follow the new registry contract: the model's reads are pumped from output pull sources declared at jobs.start, not returned from the starter (readOutput is gone), and owner takes a session id. Per-process sandbox facts continue to be stamped by onProcessDone before done settles, so runnerFailed / denied still reach both the job outcome and the foreground renderer.
  • settings.installSection / SHELL_SETTINGS_NAMESPACE were removed upstream. Settings are now schema-driven per profile entry, so the plugin's Config schema is the settings surface — no registration call is needed (and none is made).

Bug fixes

  • wsl_bash now pins its start directory explicitly. argv() / bwrapArgv() used to add --cd only when the workdir differed from a defaultWorkdir() helper — but that helper returned the cwd volatile wrapper object instead of its value (this.config.cwd ?? process.cwd() rather than this.config.cwd.get() ?? process.cwd()), so the comparison never matched and --cd was passed on every command anyway. Rather than revive the skip, the dead helper is gone and --cd is now unconditional: the auto-cd contract (a command with no explicit workdir starts 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 — and wsl.exe accepts 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 bash tool instance (lib/tool-bash/types/index.js) — never exported by this package, never wired into the patch.
  • Deleted 5 unwired invariant.js companions. 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.7 to >=0.1.7-rc.2; dsh-pwsh-sandbox, dsh-pwsh-local and dsh-settings dropped from peerDependencies (nothing imports them any more), and the ./shell-select export 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>/....