feat: make the wmux CLI work out of the box in agent shells (PATH cli-bin) - #75
Conversation
The wmux CLI was only reachable two ways, and neither reaches a non-interactive
shell: (1) $WMUX_CLI, injected into every spawned shell's env but never read by
bare `wmux` callers; (2) an interactive-only `wmux` shell function dot-sourced
into the pane. So agent contexts that run bare `wmux` — Claude Code's Bash tool,
orchestrator hook scripts, the coordinator — had $WMUX_CLI but no `wmux` on PATH,
and had to rely on a hand-installed ~/.local/bin shim.
Prepend a bundled cli-bin/ (wmux + wmux.cmd, each `node $WMUX_CLI`) to PATH in
the env PtyManager builds for every shell. PATH inherits down the whole process
tree regardless of shell/login/interactive state — which is precisely what the
interactive function cannot do — so bare `wmux` now resolves everywhere with no
shim and no user PATH edit. The dir has no wmux.exe, so there is no PATHEXT
collision with the GUI; the interactive `wmux` function still shadows the shim in
the pane (function > PATH), so pane behavior is unchanged.
- src/main/pty-manager.ts: getCliBinPath() + case-insensitive PATH prepend.
- src/cli-bin/{wmux,wmux.cmd}: the shims (bash + cmd).
- .gitattributes: force LF on the extensionless bash shim.
Verified: with cli-bin prepended and ~/.local/bin removed, a non-interactive
`bash -c` resolves `wmux` to the cli-bin shim and `wmux ping` returns pong via
$WMUX_CLI. build:main + vite build clean, 158/158 tests.
The orchestrator calls bare wmux from non-interactive shells. On a patched wmux the cli-bin shim is on PATH; on an un-patched/upstream wmux it isn't. Add scripts/wmux-resolve.sh (defines a wmux() -> node $WMUX_CLI fallback), sourced from orchestration-state.sh (covers all callers that use it) and directly in the standalone detect-wmux.sh. Defining the function makes existing 'command -v wmux' guards pass, so no other call sites change. App-bundled mirror; the standalone tawman/wmux-orchestrator repo gets the same change.
getCliBinPath() resolves to process.resourcesPath/cli-bin in packaged builds — add the src/cli-bin shims to extraResources so the dir actually exists there.
Byte-for-byte mirror of the wmux-orchestrator skill improvements into the app-bundled plugin copy: result-file completion (interactive agents never exit), coordinator-owned state.json with the sidebar's exited/complete vocabulary, bare agent ids, Windows TMPDIR/state.json path rules, pane hygiene, send --surface, plus the new browser-driving reference.
|
Merged and released in v0.17.0 — thank you, this closes a real out-of-the-box gap for exactly the workflow wmux is built for. The PATH-inheritance approach is the right call: it reaches Also appreciated the folded-in orchestration lessons in the skill docs (state.json ownership, result-file completion signal, TMPDIR alignment on Git Bash) — those match battle scars from our own runs. All 162 tests green after merge. |
Syncs the fork with upstream master (release v0.17.0) — the first sync carrying third-party code, security-gate reviewed before merge (PASS): - amirlehmam#73 (Rajveerx11) pipe auth — security-positive: V1 commands now require an "auth <token>" prefix (ping stays public) and hook.event/agent.activity leave the tokenless V2 allowlist; timing-safe compare reused, clients (CLI sendV1, PowerShell integration) updated coherently, +4 auth tests. Closes the unauthenticated-local-pipe-write gap our own review flagged. - amirlehmam#74 + maintainer CI commits — SignPath signing wired behind secrets (only egress is app.signpath.io with the repo's own secrets; inert on the fork), publisherName pinned via win.signtoolOptions, README updater-security docs, asarUnpack narrowed to node-pty prebuilds. - Our merged amirlehmam#75 content (cli-bin OOTB, orchestrator resolver, skills lessons) arrives as no-ops; electron-builder cli-bin extraResources lands. - No dependency changes (lock diff = version fields only). Base bump 0.16.0 -> 0.17.0; fork version resets to 0.17.0-local.1. Conflicts resolved: version files; .gitattributes (fork superset); README.md + orchestration-state.sh (fork copies kept — hardening superset). Fork CI repo-guard on release.yml survived the auto-merge (verified). Verified: build:main + vite build clean, 169/169 tests, 0 vulns (full + --omit=dev), security-touched files byte-identical to upstream.
The problem we hit
We use wmux heavily for Claude Code multi-agent orchestration — it's become our daily driver for exactly the workflow the README describes. But on a fresh install, orchestration silently degrades:
spawn-agents.shprints "WARNING: wmux not found in PATH" and falls back to subagent mode, and the coordinator's ownwmuxcalls fail. The only way we could make it work as-is was hand-writing shims into~/.local/binand adding that to PATH on every machine.Root cause
The orchestrator (and Claude Code itself) invokes bare
wmuxfrom non-interactive shells — Claude Code's Bash tool and the plugin's hook scripts runbash -c, not an interactive pane shell. wmux currently exposes the CLI two ways, and neither reaches that context:$WMUX_CLI— injected into every spawned shell's env, but nothing that calls barewmuxreads it.wmuxshell function — dot-sourced by the shell-integration scripts into the pane's interactive shell only. Shell functions don't propagate tobash -cchildren.(You can't fix it by putting the install folder on PATH either:
wmux.exeshadows anywmux.cmdvia PATHEXT ordering, sowmux <command>would launch the GUI.)The fix
PtyManagerprepends a bundledcli-bin/directory — containing tinywmux(bash) andwmux.cmd(cmd) shims that just runnode "$WMUX_CLI"— toPATHin the env it already builds for every spawned shell. PATH inherits down the entire process tree regardless of shell/login/interactive state, which is exactly what the shell function can't do. So barewmuxresolves everywhere: Claude Code's Bash tool, orchestrator hooks, cmd, PowerShell, Git Bash.cli-bin/contains nowmux.exe.wmuxfunction still shadows the PATH shim (function > PATH).$WMUX_CLI/$WMUX_PIPEfrom the spawning instance.wmux.exe→ orchestration works.Changes:
src/main/pty-manager.ts—getCliBinPath()+ case-insensitivePATHprepend (the Windows env key isPath).src/cli-bin/{wmux,wmux.cmd}— the shims;.gitattributesforces LF on the bash one.electron-builder.json— shipsrc/cli-bin→resources/cli-binviaextraResources.resources/wmux-orchestrator/scripts/wmux-resolve.sh(+ sourced fromorchestration-state.sh/detect-wmux.sh) — a defense-in-depth fallback for the bundled plugin: ifwmuxstill isn't on PATH (older wmux), it defineswmux() { node "$WMUX_CLI" "$@"; }, which also makes the existingcommand -v wmuxguards pass. We're submitting the same fallback to the standalonewmux-orchestratorrepo so plugin users on current wmux releases get working orchestration immediately.Verification
cli-binprepended and our~/.local/binshims deleted, a non-interactivebash -cresolveswmuxto the cli-bin shim andwmux ping→pongvia$WMUX_CLI.wmuxoff PATH entirely, sourcingwmux-resolve.shmakesdetect-wmux.shreportavailableandwmux ping→pong.wmux— with no shims installed anywhere.build:main+vite buildclean; all tests pass (158/158).Also: orchestration lessons learned, folded into the distributed skills
We have been running heavy multi-agent Claude Code orchestrations on wmux, and this PR also contributes that operating knowledge back into the distributed skills so they match how orchestration actually behaves:
skills/orchestratePhase 7 (monitoring) corrected — the completion signal is the agent's result file, not process exit:launch-agent.jsruns agents interactively, so a finished agent idles at its TUI and never exits, and the previous "pollwmux agent listforexited" loop never terminates. The skill now polls result files, and documents that the coordinator ownsstate.json(Claude Code Stop/SubagentStop hooks don't fire for wmux-spawned agents) using the status vocabulary the sidebar actually counts (exitedfor agents,completefor waves/run)."a", not"agent-a"): the scripts prefixagent-themselves, so the old schema example produced double-prefixed prompt/result filenames the launcher couldn't find.TMPDIRwith the temp dir the sidebar watches (Git Bash), forward-slash paths instate.json(backslashes are invalid JSON escapes and freeze the cockpit), retry reads that race the app's 1 s poller.wmux sendtargets the caller's own surface by default (use--surface);agent killdoesn't kill agents' child processes (sweep orphaned dev-server ports).skills/orchestrate/references/browser-driving.md— field recipes for agents driving the browser panel: bareeNrefs, the single persistentevalscope (IIFE), snapshot parsing, React/Radix synthetic-event input recipes, the transient-UI polling caveat, and the port-9222 CDP collision warning.