Describe your idea
BaseTmuxBackend.new_window / new_parked_window (src/automator/adapters/tmux_base.py) hard-code the POSIX shell wrapper: sh -c, shlex quoting, $? exit capture, echo, read -r, and an sh return-trailer. Factor those shell-dialect fragments into small overridable hooks with POSIX defaults, so the tmux argv construction, the parked-window protocol, and the recipe shape (inner; exit-capture; banner; park; return-trailer) live in the base exactly once.
Why is this needed?
A native-Windows backend (the psmux tmux.exe drop-in, follow-up to #40) has no /bin/sh — its windows run under pwsh. With the wrapper hard-coded, a Windows subclass is forced to duplicate both entire methods just to swap the shell dialect, even though the tmux verbs and argv layout are identical (new-window -t =S: -n NAME -c CWD -P -F '#{window_id}' …, and the trailer's show-options/switch-client/detach-client hit tmux.exe unchanged). A prototype Windows backend did exactly that, and the two copies immediately became the biggest drift risk in the adapter layer: any later fix to the parked protocol in the base silently fails to reach the override.
How should it work?
Keep both contract methods in the base and route only the dialect through hooks (POSIX defaults shown; a Windows leaf overrides the strings, never a method body):
_EXIT_CAPTURE = "ec=$?" # pwsh later: "$ec = $LASTEXITCODE"
_ECHO = "echo" # pwsh later: "Write-Host"
_PARK = "read -r" # pwsh later: "Read-Host"
def _join_argv(self, argv): ... # shlex.join | pwsh: & 'a' 'b' with '' escaping
def _parked_trailer(self, opt): ... # sh if/elif | pwsh: if/elseif (verbs identical)
def _source_prefix(self): ... # "" | pwsh: env-hygiene prelude
def _shell_wrap(self, source): ... # ["sh","-c",s] | pwsh: -NoProfile -EncodedCommand
def _window_launch(self, env, cmd): # ["-e","K=V"…, cmd] | pwsh: wrapped cmd + prelude
new_parked_window composes _source_prefix() + f"{inner}; {exit}; {echo} \"[bmad-auto exited $ec — press enter]\"; {park}; {trailer}" and spawns *self._shell_wrap(source). The recipe line is genuinely dialect-neutral (; sequencing and "$ec" interpolation behave the same in sh and pwsh); only the trailer stays a whole-string hook because sh/pwsh control-flow syntax can't be tokenized.
Guarantees:
- POSIX output stays byte-identical — locked by tests asserting the literal
subprocess.run argv (including the full sh -c source) before/after.
tmux_backend.py (the POSIX leaf) overrides nothing new.
- The pwsh side is deliberately not implemented here; this only opens the seam for the Windows backend to land against.
PR
I'm working on this — implementation with byte-identity + fake-dialect-leaf tests is ready on my fork; PR to follow.
Additional context
Companion to #40 (which widened the _run spawn seam for encoding/env the same way: default-preserving hooks instead of method duplication). Together they get a tmux-family Windows leaf to ~zero copied plumbing.
Describe your idea
BaseTmuxBackend.new_window/new_parked_window(src/automator/adapters/tmux_base.py) hard-code the POSIX shell wrapper:sh -c,shlexquoting,$?exit capture,echo,read -r, and an sh return-trailer. Factor those shell-dialect fragments into small overridable hooks with POSIX defaults, so the tmux argv construction, the parked-window protocol, and the recipe shape (inner; exit-capture; banner; park; return-trailer) live in the base exactly once.Why is this needed?
A native-Windows backend (the
psmuxtmux.exedrop-in, follow-up to #40) has no/bin/sh— its windows run underpwsh. With the wrapper hard-coded, a Windows subclass is forced to duplicate both entire methods just to swap the shell dialect, even though the tmux verbs and argv layout are identical (new-window -t =S: -n NAME -c CWD -P -F '#{window_id}' …, and the trailer'sshow-options/switch-client/detach-clienthittmux.exeunchanged). A prototype Windows backend did exactly that, and the two copies immediately became the biggest drift risk in the adapter layer: any later fix to the parked protocol in the base silently fails to reach the override.How should it work?
Keep both contract methods in the base and route only the dialect through hooks (POSIX defaults shown; a Windows leaf overrides the strings, never a method body):
new_parked_windowcomposes_source_prefix() + f"{inner}; {exit}; {echo} \"[bmad-auto exited $ec — press enter]\"; {park}; {trailer}"and spawns*self._shell_wrap(source). The recipe line is genuinely dialect-neutral (;sequencing and"$ec"interpolation behave the same in sh and pwsh); only the trailer stays a whole-string hook because sh/pwsh control-flow syntax can't be tokenized.Guarantees:
subprocess.runargv (including the fullsh -csource) before/after.tmux_backend.py(the POSIX leaf) overrides nothing new.PR
I'm working on this — implementation with byte-identity + fake-dialect-leaf tests is ready on my fork; PR to follow.
Additional context
Companion to #40 (which widened the
_runspawn seam for encoding/env the same way: default-preserving hooks instead of method duplication). Together they get a tmux-family Windows leaf to ~zero copied plumbing.