fix(run): stop Start/Resume from silently no-oping on an idle lane session
spawnRun adopted any existing `ccam-lane-<id>` tmux session without looking
at it, so a Resume issued while the session sat at a bare shell prompt (left
by `ccam lanes shell`, or by a `claude` that had already exited) dropped the
whole argv: no `--resume` ran, no initial prompt was typed, and the API still
answered 200. The lane's DB `run_id` is not set in that case, so the ERUNLIVE
guard in the start route never saw it either.
Reuse the pane instead of erroring: when the session exists and
`#{pane_current_command}` is a shell, type the argv into that pane and record
the run. A pane running a program (a live `claude`, an editor, a build) is
still adopted untouched, so attaching shows what is running rather than typing
over it. `sendCommand` POSIX single-quotes every argument and uses
`send-keys -l`, the only place in tmux.js that composes a command line.
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
* `tmux attach -t ccam-lane-<id>` (or `ccam lanes shell`). Never builds a
|
||||
* shell string — every call is `execFileSync("tmux", [...argv])` with an
|
||||
* explicit argument array (matches this repo's rule for git in worktree.js).
|
||||
* The one place a command line is composed is `sendCommand`, which types into
|
||||
* an existing pane's shell: there the shell IS the consumer, so every argument
|
||||
* is POSIX single-quoted first and sent with `send-keys -l` (literal).
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -39,6 +42,36 @@ function newSession({ name, cwd, argv }) {
|
||||
execImpl(["new-session", "-d", "-s", name, "-c", cwd, "--", ...argv]);
|
||||
}
|
||||
|
||||
/**
|
||||
* The command currently running in the session's active pane (`bash`, `zsh`,
|
||||
* `claude`, …). Null when tmux can't answer — callers treat that as "unknown,
|
||||
* don't touch the pane".
|
||||
*/
|
||||
function paneCommand(name) {
|
||||
try {
|
||||
return (
|
||||
execImpl(["display-message", "-p", "-t", name, "#{pane_current_command}"]).trim() || null
|
||||
);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** POSIX single-quote escaping — the pane is a shell, so argv must be quoted. */
|
||||
function shellQuote(arg) {
|
||||
return `'${String(arg).replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type `argv` into an existing session's pane and press Enter. Only ever
|
||||
* called when the pane sits at a shell prompt (see `paneCommand`); `-l` sends
|
||||
* the string literally so no character is read as a tmux key name.
|
||||
*/
|
||||
function sendCommand(name, argv) {
|
||||
execImpl(["send-keys", "-t", name, "-l", argv.map(shellQuote).join(" ")]);
|
||||
execImpl(["send-keys", "-t", name, "Enter"]);
|
||||
}
|
||||
|
||||
/** Idempotent — a session that's already gone is not an error. */
|
||||
function killSession(name) {
|
||||
try {
|
||||
@@ -74,6 +107,8 @@ function isTmuxAvailable() {
|
||||
module.exports = {
|
||||
hasSession,
|
||||
newSession,
|
||||
paneCommand,
|
||||
sendCommand,
|
||||
killSession,
|
||||
listSessions,
|
||||
isTmuxAvailable,
|
||||
|
||||
Reference in New Issue
Block a user