diff --git a/src/shared/tmux/cmux-detect.ts b/src/shared/tmux/cmux-detect.ts new file mode 100644 index 000000000..202733c77 --- /dev/null +++ b/src/shared/tmux/cmux-detect.ts @@ -0,0 +1,11 @@ +/** + * Detect whether we are running inside cmux (cmux omo). + * When cmux-omo sets up the environment it injects a tmux shim and sets + * CMUX_SOCKET_PATH / TMUX. If detected, redirect tmux commands to + * `cmux __tmux-compat` so they become native cmux splits instead of + * failing because there is no real tmux server running. + */ +export function isCmuxCompatEnvironment(): boolean { + return Boolean(process.env.CMUX_SOCKET_PATH) || + process.env.TMUX?.includes("cmuxterm") === true +} diff --git a/src/shared/tmux/index.ts b/src/shared/tmux/index.ts index b523bf642..b8ef46b75 100644 --- a/src/shared/tmux/index.ts +++ b/src/shared/tmux/index.ts @@ -1,4 +1,5 @@ export * from "./types" export * from "./constants" +export * from "./cmux-detect" export * from "./runner" export * from "./tmux-utils" diff --git a/src/shared/tmux/runner.ts b/src/shared/tmux/runner.ts index b61995fbc..6bbd2cf4b 100644 --- a/src/shared/tmux/runner.ts +++ b/src/shared/tmux/runner.ts @@ -1,4 +1,5 @@ import { spawn } from "../bun-spawn-shim" +import { isCmuxCompatEnvironment } from "./cmux-detect" type RunTmuxOptions = { retry?: number @@ -29,18 +30,6 @@ function isTerminalTmuxError(stderr: string): boolean { return TERMINAL_TMUX_ERROR_PATTERN.test(stderr) } -/** - * Detect whether we are running inside cmux (cmux omo). - * When cmux-omo sets up the environment it injects a tmux shim and sets - * CMUX_SOCKET_PATH / TMUX. If detected, redirect tmux commands to - * `cmux __tmux-compat` so they become native cmux splits instead of - * failing because there is no real tmux server running. - */ -function isCmuxCompatEnvironment(): boolean { - return Boolean(process.env.CMUX_SOCKET_PATH) || - process.env.TMUX?.includes("cmuxterm") === true -} - function resolveTmuxExecutable(tmuxPath: string): string[] { if (!isCmuxCompatEnvironment()) { return [tmuxPath] diff --git a/src/tools/interactive-bash/tmux-path-resolver.ts b/src/tools/interactive-bash/tmux-path-resolver.ts index 0562caa0d..2ef2324eb 100644 --- a/src/tools/interactive-bash/tmux-path-resolver.ts +++ b/src/tools/interactive-bash/tmux-path-resolver.ts @@ -1,14 +1,10 @@ import { spawn } from "../../shared/bun-spawn-shim" +import { isCmuxCompatEnvironment } from "../../shared/tmux/cmux-detect" let tmuxPath: string | null = null let initPromise: Promise | null = null let tmuxPathEnvironmentKey: "cmux" | "tmux" | null = null -function isCmuxCompatEnvironment(): boolean { - return Boolean(process.env.CMUX_SOCKET_PATH) || - process.env.TMUX?.includes("cmuxterm") === true -} - function getEnvironmentKey(): "cmux" | "tmux" { return isCmuxCompatEnvironment() ? "cmux" : "tmux" } diff --git a/src/tools/interactive-bash/tools.ts b/src/tools/interactive-bash/tools.ts index a0795ee36..21a03cf7f 100644 --- a/src/tools/interactive-bash/tools.ts +++ b/src/tools/interactive-bash/tools.ts @@ -1,8 +1,19 @@ import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool" import { spawnWithWindowsHide } from "../../shared/spawn-with-windows-hide" +import { isCmuxCompatEnvironment } from "../../shared/tmux/cmux-detect" import { BLOCKED_TMUX_SUBCOMMANDS, DEFAULT_TIMEOUT_MS, INTERACTIVE_BASH_DESCRIPTION } from "./constants" import { getCachedTmuxPath } from "./tmux-path-resolver" +function resolveTmuxExecutable(tmuxPath: string): string[] { + if (!isCmuxCompatEnvironment()) { + return [tmuxPath] + } + + const executableName = tmuxPath.split(/[\\/]/).pop() + const cmuxExecutable = executableName === "cmux" ? tmuxPath : "cmux" + return [cmuxExecutable, "__tmux-compat"] +} + /** * Quote-aware command tokenizer with escape handling * Handles single/double quotes and backslash escapes without external dependencies @@ -90,7 +101,7 @@ tmux capture-pane -p -t ${sessionName} -S -1000 The Bash tool can execute these commands directly. Do NOT retry with interactive_bash.` } - const proc = spawnWithWindowsHide([tmuxPath, ...parts], { + const proc = spawnWithWindowsHide([...resolveTmuxExecutable(tmuxPath), ...parts], { stdout: "pipe", stderr: "pipe", })