fix(tmux,run,hooks): the four pre-existing bugs the #37 evaluation found

- tmux targets are exact (`=name`, `=name:` for panes/options): a bare
  `-t agenthub-lane-1` resolves by prefix to agenthub-lane-12 once lane 1
  is gone, so killing a finished lane could kill another.
- getRun/killRun refuse ids that are not `agenthub-lane-<n>`, so
  DELETE /api/run/main can no longer kill a user's own tmux session.
- router/apply reports restart_required only for a running pane; getRun
  returns a `gone` handle, so it was always true.
- needs_action is cleared only by the session that raised it (new nullable
  lanes.needs_action_session); a second session in the same cwd used to
  clear it on its second hook.
This commit is contained in:
2026-09-23 13:13:16 +07:00
parent 04b6b98d97
commit 99a2a8277c
18 changed files with 134 additions and 33 deletions
+15 -7
View File
@@ -33,9 +33,16 @@ function __reset() {
execImpl = (args) => execFileSync("tmux", args, EXEC_OPTS);
}
// tmux resolves a bare `-t agenthub-lane-1` by PREFIX when no exact match
// exists, so it would hit `agenthub-lane-12` once lane 1's session is gone —
// and killSession would kill the wrong lane. `=` forces an exact match; pane
// and option targets need the trailing `:` (tmux 3.4 rejects `=name` there).
const session = (name) => `=${name}`;
const pane = (name) => `=${name}:`;
function hasSession(name) {
try {
execImpl(["has-session", "-t", name]);
execImpl(["has-session", "-t", session(name)]);
return true;
} catch {
return false;
@@ -71,7 +78,7 @@ function newSession({ name, cwd, argv, env }) {
// this option existed — or before a server restart picked up the change —
// still gets it; `set-option` is idempotent, so re-running it is harmless.
function enableMouse(name) {
execImpl(["set-option", "-t", name, "mouse", "on"]);
execImpl(["set-option", "-t", pane(name), "mouse", "on"]);
}
/**
@@ -82,7 +89,8 @@ function enableMouse(name) {
function paneCommand(name) {
try {
return (
execImpl(["display-message", "-p", "-t", name, "#{pane_current_command}"]).trim() || null
execImpl(["display-message", "-p", "-t", pane(name), "#{pane_current_command}"]).trim() ||
null
);
} catch {
return null;
@@ -107,7 +115,7 @@ function sendCommand(name, argv, env, cwd) {
"respawn-pane",
"-k",
"-t",
name,
pane(name),
...(cwd ? ["-c", cwd] : []),
...envFlags(env),
"--",
@@ -125,14 +133,14 @@ function sendCommand(name, argv, env, cwd) {
* only the first line and leave the rest sitting unsent in the input box.
*/
function sendText(name, text) {
execImpl(["send-keys", "-t", name, "-l", text]);
execImpl(["send-keys", "-t", name, "Enter"]);
execImpl(["send-keys", "-t", pane(name), "-l", text]);
execImpl(["send-keys", "-t", pane(name), "Enter"]);
}
/** Idempotent — a session that's already gone is not an error. */
function killSession(name) {
try {
execImpl(["kill-session", "-t", name]);
execImpl(["kill-session", "-t", session(name)]);
} catch {
/* already gone */
}