@wastedcode/claudemux 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +257 -0
- package/LICENSE +21 -0
- package/README.md +493 -0
- package/bin/claudemux +6 -0
- package/dist/agents/claude.d.ts +3 -0
- package/dist/agents/claude.d.ts.map +1 -0
- package/dist/agents/claude.js +585 -0
- package/dist/agents/claude.js.map +1 -0
- package/dist/agents/index.d.ts +2 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/index.js +2 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/types.d.ts +252 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/types.js +2 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/backends/tmux/capture.d.ts +25 -0
- package/dist/backends/tmux/capture.d.ts.map +1 -0
- package/dist/backends/tmux/capture.js +35 -0
- package/dist/backends/tmux/capture.js.map +1 -0
- package/dist/backends/tmux/exec.d.ts +105 -0
- package/dist/backends/tmux/exec.d.ts.map +1 -0
- package/dist/backends/tmux/exec.js +226 -0
- package/dist/backends/tmux/exec.js.map +1 -0
- package/dist/backends/tmux/index.d.ts +22 -0
- package/dist/backends/tmux/index.d.ts.map +1 -0
- package/dist/backends/tmux/index.js +108 -0
- package/dist/backends/tmux/index.js.map +1 -0
- package/dist/backends/tmux/keys.d.ts +38 -0
- package/dist/backends/tmux/keys.d.ts.map +1 -0
- package/dist/backends/tmux/keys.js +63 -0
- package/dist/backends/tmux/keys.js.map +1 -0
- package/dist/backends/tmux/options.d.ts +24 -0
- package/dist/backends/tmux/options.d.ts.map +1 -0
- package/dist/backends/tmux/options.js +84 -0
- package/dist/backends/tmux/options.js.map +1 -0
- package/dist/backends/tmux/sessions.d.ts +70 -0
- package/dist/backends/tmux/sessions.d.ts.map +1 -0
- package/dist/backends/tmux/sessions.js +156 -0
- package/dist/backends/tmux/sessions.js.map +1 -0
- package/dist/backends/tmux/socket.d.ts +26 -0
- package/dist/backends/tmux/socket.d.ts.map +1 -0
- package/dist/backends/tmux/socket.js +31 -0
- package/dist/backends/tmux/socket.js.map +1 -0
- package/dist/backends/types.d.ts +110 -0
- package/dist/backends/types.d.ts.map +1 -0
- package/dist/backends/types.js +24 -0
- package/dist/backends/types.js.map +1 -0
- package/dist/cli/ask.d.ts +11 -0
- package/dist/cli/ask.d.ts.map +1 -0
- package/dist/cli/ask.js +17 -0
- package/dist/cli/ask.js.map +1 -0
- package/dist/cli/capture.d.ts +8 -0
- package/dist/cli/capture.d.ts.map +1 -0
- package/dist/cli/capture.js +15 -0
- package/dist/cli/capture.js.map +1 -0
- package/dist/cli/context.d.ts +71 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/context.js +82 -0
- package/dist/cli/context.js.map +1 -0
- package/dist/cli/exists.d.ts +7 -0
- package/dist/cli/exists.d.ts.map +1 -0
- package/dist/cli/exists.js +16 -0
- package/dist/cli/exists.js.map +1 -0
- package/dist/cli/interrupt.d.ts +10 -0
- package/dist/cli/interrupt.d.ts.map +1 -0
- package/dist/cli/interrupt.js +13 -0
- package/dist/cli/interrupt.js.map +1 -0
- package/dist/cli/kill.d.ts +7 -0
- package/dist/cli/kill.d.ts.map +1 -0
- package/dist/cli/kill.js +14 -0
- package/dist/cli/kill.js.map +1 -0
- package/dist/cli/list.d.ts +10 -0
- package/dist/cli/list.d.ts.map +1 -0
- package/dist/cli/list.js +19 -0
- package/dist/cli/list.js.map +1 -0
- package/dist/cli/main.d.ts +13 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/main.js +143 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/messages.d.ts +9 -0
- package/dist/cli/messages.d.ts.map +1 -0
- package/dist/cli/messages.js +13 -0
- package/dist/cli/messages.js.map +1 -0
- package/dist/cli/respond.d.ts +10 -0
- package/dist/cli/respond.d.ts.map +1 -0
- package/dist/cli/respond.js +23 -0
- package/dist/cli/respond.js.map +1 -0
- package/dist/cli/resume.d.ts +12 -0
- package/dist/cli/resume.d.ts.map +1 -0
- package/dist/cli/resume.js +21 -0
- package/dist/cli/resume.js.map +1 -0
- package/dist/cli/send.d.ts +9 -0
- package/dist/cli/send.d.ts.map +1 -0
- package/dist/cli/send.js +16 -0
- package/dist/cli/send.js.map +1 -0
- package/dist/cli/spawn.d.ts +14 -0
- package/dist/cli/spawn.d.ts.map +1 -0
- package/dist/cli/spawn.js +21 -0
- package/dist/cli/spawn.js.map +1 -0
- package/dist/cli/state.d.ts +7 -0
- package/dist/cli/state.d.ts.map +1 -0
- package/dist/cli/state.js +11 -0
- package/dist/cli/state.js.map +1 -0
- package/dist/cli/turn-complete.d.ts +8 -0
- package/dist/cli/turn-complete.d.ts.map +1 -0
- package/dist/cli/turn-complete.js +14 -0
- package/dist/cli/turn-complete.js.map +1 -0
- package/dist/cli/wait.d.ts +13 -0
- package/dist/cli/wait.d.ts.map +1 -0
- package/dist/cli/wait.js +17 -0
- package/dist/cli/wait.js.map +1 -0
- package/dist/compose.d.ts +81 -0
- package/dist/compose.d.ts.map +1 -0
- package/dist/compose.js +64 -0
- package/dist/compose.js.map +1 -0
- package/dist/errors.d.ts +250 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +300 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/io/baseline.d.ts +53 -0
- package/dist/io/baseline.d.ts.map +1 -0
- package/dist/io/baseline.js +97 -0
- package/dist/io/baseline.js.map +1 -0
- package/dist/io/capture.d.ts +15 -0
- package/dist/io/capture.d.ts.map +1 -0
- package/dist/io/capture.js +13 -0
- package/dist/io/capture.js.map +1 -0
- package/dist/io/interrupt.d.ts +46 -0
- package/dist/io/interrupt.d.ts.map +1 -0
- package/dist/io/interrupt.js +60 -0
- package/dist/io/interrupt.js.map +1 -0
- package/dist/io/respond.d.ts +28 -0
- package/dist/io/respond.d.ts.map +1 -0
- package/dist/io/respond.js +33 -0
- package/dist/io/respond.js.map +1 -0
- package/dist/io/send.d.ts +44 -0
- package/dist/io/send.d.ts.map +1 -0
- package/dist/io/send.js +66 -0
- package/dist/io/send.js.map +1 -0
- package/dist/io/stabilize.d.ts +28 -0
- package/dist/io/stabilize.d.ts.map +1 -0
- package/dist/io/stabilize.js +20 -0
- package/dist/io/stabilize.js.map +1 -0
- package/dist/io/wait.d.ts +47 -0
- package/dist/io/wait.d.ts.map +1 -0
- package/dist/io/wait.js +117 -0
- package/dist/io/wait.js.map +1 -0
- package/dist/observe/incremental.d.ts +28 -0
- package/dist/observe/incremental.d.ts.map +1 -0
- package/dist/observe/incremental.js +57 -0
- package/dist/observe/incremental.js.map +1 -0
- package/dist/observe/observer.d.ts +86 -0
- package/dist/observe/observer.d.ts.map +1 -0
- package/dist/observe/observer.js +167 -0
- package/dist/observe/observer.js.map +1 -0
- package/dist/observe/session-observer.d.ts +49 -0
- package/dist/observe/session-observer.d.ts.map +1 -0
- package/dist/observe/session-observer.js +123 -0
- package/dist/observe/session-observer.js.map +1 -0
- package/dist/session/adopt.d.ts +52 -0
- package/dist/session/adopt.d.ts.map +1 -0
- package/dist/session/adopt.js +57 -0
- package/dist/session/adopt.js.map +1 -0
- package/dist/session/boot.d.ts +66 -0
- package/dist/session/boot.d.ts.map +1 -0
- package/dist/session/boot.js +216 -0
- package/dist/session/boot.js.map +1 -0
- package/dist/session/constants.d.ts +57 -0
- package/dist/session/constants.d.ts.map +1 -0
- package/dist/session/constants.js +54 -0
- package/dist/session/constants.js.map +1 -0
- package/dist/session/create.d.ts +88 -0
- package/dist/session/create.d.ts.map +1 -0
- package/dist/session/create.js +66 -0
- package/dist/session/create.js.map +1 -0
- package/dist/session/default-backend.d.ts +27 -0
- package/dist/session/default-backend.d.ts.map +1 -0
- package/dist/session/default-backend.js +58 -0
- package/dist/session/default-backend.js.map +1 -0
- package/dist/session/handle.d.ts +63 -0
- package/dist/session/handle.d.ts.map +1 -0
- package/dist/session/handle.js +284 -0
- package/dist/session/handle.js.map +1 -0
- package/dist/session/hooks.d.ts +37 -0
- package/dist/session/hooks.d.ts.map +1 -0
- package/dist/session/hooks.js +42 -0
- package/dist/session/hooks.js.map +1 -0
- package/dist/session/mutex.d.ts +15 -0
- package/dist/session/mutex.d.ts.map +1 -0
- package/dist/session/mutex.js +29 -0
- package/dist/session/mutex.js.map +1 -0
- package/dist/session/recover.d.ts +43 -0
- package/dist/session/recover.d.ts.map +1 -0
- package/dist/session/recover.js +45 -0
- package/dist/session/recover.js.map +1 -0
- package/dist/session/ref.d.ts +2 -0
- package/dist/session/ref.d.ts.map +1 -0
- package/dist/session/ref.js +5 -0
- package/dist/session/ref.js.map +1 -0
- package/dist/session/registry.d.ts +31 -0
- package/dist/session/registry.d.ts.map +1 -0
- package/dist/session/registry.js +32 -0
- package/dist/session/registry.js.map +1 -0
- package/dist/session/resolve.d.ts +30 -0
- package/dist/session/resolve.d.ts.map +1 -0
- package/dist/session/resolve.js +24 -0
- package/dist/session/resolve.js.map +1 -0
- package/dist/session/resume.d.ts +68 -0
- package/dist/session/resume.d.ts.map +1 -0
- package/dist/session/resume.js +54 -0
- package/dist/session/resume.js.map +1 -0
- package/dist/session/spawn-boot.d.ts +44 -0
- package/dist/session/spawn-boot.d.ts.map +1 -0
- package/dist/session/spawn-boot.js +87 -0
- package/dist/session/spawn-boot.js.map +1 -0
- package/dist/session/validate.d.ts +10 -0
- package/dist/session/validate.d.ts.map +1 -0
- package/dist/session/validate.js +0 -0
- package/dist/session/validate.js.map +1 -0
- package/dist/state/classifier.d.ts +29 -0
- package/dist/state/classifier.d.ts.map +1 -0
- package/dist/state/classifier.js +37 -0
- package/dist/state/classifier.js.map +1 -0
- package/dist/state/types.d.ts +32 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +2 -0
- package/dist/state/types.js.map +1 -0
- package/dist/types.d.ts +401 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/util/ansi.d.ts +14 -0
- package/dist/util/ansi.d.ts.map +1 -0
- package/dist/util/ansi.js +18 -0
- package/dist/util/ansi.js.map +1 -0
- package/dist/util/emitter.d.ts +17 -0
- package/dist/util/emitter.d.ts.map +1 -0
- package/dist/util/emitter.js +33 -0
- package/dist/util/emitter.js.map +1 -0
- package/dist/util/sleep.d.ts +8 -0
- package/dist/util/sleep.d.ts.map +1 -0
- package/dist/util/sleep.js +10 -0
- package/dist/util/sleep.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { BackendError, BackendUnreachable, SessionExists, SessionGone } from "../../errors.js";
|
|
3
|
+
import { Emitter } from "../../util/emitter.js";
|
|
4
|
+
/**
|
|
5
|
+
* tmux stderr patterns that indicate "the backend's server is not reachable
|
|
6
|
+
* on this socket." Older tmux says `no server running on /tmp/.../sock`;
|
|
7
|
+
* tmux ≥3.3 says `error connecting to /tmp/.../sock (No such file or
|
|
8
|
+
* directory)`. We treat both as the same condition (server's gone).
|
|
9
|
+
*
|
|
10
|
+
* Single-source — callers (`sessions.ts`, `classifyTmuxFailure`) import
|
|
11
|
+
* from here so the regex can't drift.
|
|
12
|
+
*/
|
|
13
|
+
export function isNoServerStderr(text) {
|
|
14
|
+
return /no server running on/i.test(text) || /error connecting to /i.test(text);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* tmux stderr substrings that indicate "the target you asked about doesn't
|
|
18
|
+
* exist on this server." tmux emits different `can't find …:` strings
|
|
19
|
+
* depending on which level of the `session:window.pane` grammar failed.
|
|
20
|
+
* All three are semantically "the session you wanted isn't here" — callers
|
|
21
|
+
* that need a boolean (`hasSession`) treat them all as "no" rather than
|
|
22
|
+
* letting the routine `can't find pane:`/`can't find window:` shapes
|
|
23
|
+
* escape as `SessionGone` throws.
|
|
24
|
+
*/
|
|
25
|
+
const SESSION_GONE_PATTERNS = ["can't find session:", "can't find pane:", "can't find window:"];
|
|
26
|
+
export function isSessionGoneStderr(text) {
|
|
27
|
+
const lower = text.toLowerCase();
|
|
28
|
+
return SESSION_GONE_PATTERNS.some((p) => lower.includes(p));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* tmux stderr shape when `new-session -s <name>` races another process
|
|
32
|
+
* creating the same target. With the shared default socket (ADR 0006),
|
|
33
|
+
* concurrent `spawn`s of the same name are routine, and the TOCTOU window
|
|
34
|
+
* between `create()`'s exists-check and `backend.spawn()` means tmux —
|
|
35
|
+
* not the substrate's check — sometimes discovers the collision. The
|
|
36
|
+
* semantically-correct typed error is `SessionExists`, same as a check-time
|
|
37
|
+
* collision: the substrate refuses to silently adopt either way.
|
|
38
|
+
*/
|
|
39
|
+
export function isDuplicateSessionStderr(text) {
|
|
40
|
+
return /duplicate session:/i.test(text);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Default per-invocation timeout. tmux read/control ops (capture-pane,
|
|
44
|
+
* has-session, new-session, …) return in milliseconds against a healthy
|
|
45
|
+
* server; 10s is generous headroom. The long-lived "wait for the agent"
|
|
46
|
+
* budget belongs to `io/wait.ts`'s loop, NOT to a single subprocess — so
|
|
47
|
+
* a wedged tmux (process alive but unresponsive: NFS stall, server bug,
|
|
48
|
+
* modal pane) surfaces as a typed `BackendUnreachable[timeout]` inside
|
|
49
|
+
* `wait()`'s budget rather than hanging the consumer's `await` forever.
|
|
50
|
+
*/
|
|
51
|
+
const DEFAULT_EXEC_TIMEOUT_MS = 10_000;
|
|
52
|
+
/**
|
|
53
|
+
* One executor instance per backend. Holds the private socket name and the
|
|
54
|
+
* observability emitter. Every tmux invocation goes through {@link run}; that
|
|
55
|
+
* is what enforces the `-f /dev/null` + `-L <socket>` discipline tree-wide.
|
|
56
|
+
*
|
|
57
|
+
* See `engineer/wiki/tmux-private-server-bootstrap` — bare `-L` does not
|
|
58
|
+
* prevent `~/.tmux.conf` reads; both flags are required.
|
|
59
|
+
*/
|
|
60
|
+
export class TmuxExec {
|
|
61
|
+
socket;
|
|
62
|
+
#events = new Emitter();
|
|
63
|
+
constructor(socket) {
|
|
64
|
+
this.socket = socket;
|
|
65
|
+
}
|
|
66
|
+
/** Subscribe to every tmux invocation + result. Returns unsubscribe. */
|
|
67
|
+
onCommand(handler) {
|
|
68
|
+
return this.#events.on(handler);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Spawn `tmux -L <socket> -f /dev/null <args...>` and capture both streams —
|
|
72
|
+
* stdout for the op's payload (e.g. `capture-pane`), stderr for failure
|
|
73
|
+
* classification (`can't find …` → `SessionGone`, etc.).
|
|
74
|
+
*
|
|
75
|
+
* @throws `BackendUnreachable` — `spawn-failed` on spawn error (ENOENT,
|
|
76
|
+
* EPIPE), `no-server` when the server isn't running on a connect-only
|
|
77
|
+
* operation, `timeout` when the invocation doesn't return within
|
|
78
|
+
* `opts.timeoutMs` (default {@link DEFAULT_EXEC_TIMEOUT_MS}). The
|
|
79
|
+
* session-name field on the typed error is the *requested* session.
|
|
80
|
+
*/
|
|
81
|
+
async run(args, opts = {}) {
|
|
82
|
+
const fullArgs = ["-L", this.socket, "-f", "/dev/null", ...args];
|
|
83
|
+
const sessionName = opts.sessionName ?? "<unknown>";
|
|
84
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_EXEC_TIMEOUT_MS;
|
|
85
|
+
const startedAt = Date.now();
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const child = spawn("tmux", fullArgs, {
|
|
88
|
+
stdio: [opts.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
89
|
+
env: process.env,
|
|
90
|
+
});
|
|
91
|
+
if (opts.input !== undefined && child.stdin) {
|
|
92
|
+
child.stdin.write(opts.input);
|
|
93
|
+
child.stdin.end();
|
|
94
|
+
}
|
|
95
|
+
let stdout = "";
|
|
96
|
+
let stderr = "";
|
|
97
|
+
let spawnErr = null;
|
|
98
|
+
let settled = false;
|
|
99
|
+
// Per-invocation timeout: SIGKILL the child WE spawned (peer-process-safe
|
|
100
|
+
// by construction — exact PID, ADR 0004) and reject. The `close` handler
|
|
101
|
+
// still fires after the kill, but `settled` guards against double-settle.
|
|
102
|
+
const timer = setTimeout(() => {
|
|
103
|
+
if (settled)
|
|
104
|
+
return;
|
|
105
|
+
settled = true;
|
|
106
|
+
child.kill("SIGKILL");
|
|
107
|
+
reject(new BackendUnreachable(sessionName, "timeout", new Error(`backend command did not return within ${timeoutMs}ms`)));
|
|
108
|
+
}, timeoutMs);
|
|
109
|
+
timer.unref?.();
|
|
110
|
+
child.stdout?.on("data", (b) => {
|
|
111
|
+
stdout += b.toString("utf8");
|
|
112
|
+
});
|
|
113
|
+
child.stderr?.on("data", (b) => {
|
|
114
|
+
stderr += b.toString("utf8");
|
|
115
|
+
});
|
|
116
|
+
child.on("error", (err) => {
|
|
117
|
+
spawnErr = err;
|
|
118
|
+
});
|
|
119
|
+
child.on("close", (code) => {
|
|
120
|
+
if (settled)
|
|
121
|
+
return; // already rejected via timeout
|
|
122
|
+
settled = true;
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
const durationMs = Date.now() - startedAt;
|
|
125
|
+
const exit = code ?? -1;
|
|
126
|
+
this.#events.emit({
|
|
127
|
+
ts: startedAt,
|
|
128
|
+
argv: ["tmux", ...fullArgs],
|
|
129
|
+
durationMs,
|
|
130
|
+
exit,
|
|
131
|
+
stdout,
|
|
132
|
+
stderr,
|
|
133
|
+
});
|
|
134
|
+
if (spawnErr) {
|
|
135
|
+
reject(new BackendUnreachable(sessionName, "spawn-failed", spawnErr));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// tmux is on PATH but its server is down on a connect-only
|
|
139
|
+
// operation. Both shapes ("no server running on …" and
|
|
140
|
+
// "error connecting to …") are the same condition — promote.
|
|
141
|
+
// Don't echo the raw stderr (it leaks "tmux" via the socket path).
|
|
142
|
+
if (exit !== 0 && isNoServerStderr(stderr)) {
|
|
143
|
+
reject(new BackendUnreachable(sessionName, "no-server", new Error("no server running on the configured socket")));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
resolve({ exit, stdout, stderr, durationMs });
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Classify a non-zero `TmuxResult` into a typed error. Callers use this when
|
|
153
|
+
* the operation was expected to succeed; for idempotent ops (kill), callers
|
|
154
|
+
* inspect `result.exit` + stderr directly and choose to swallow SessionGone.
|
|
155
|
+
*
|
|
156
|
+
* Returns `null` if the result looks successful (exit 0 with no error
|
|
157
|
+
* annotation on stdout), otherwise the right typed error to throw.
|
|
158
|
+
*
|
|
159
|
+
* `sessionName` is the requested target (so the error carries the right
|
|
160
|
+
* context even when tmux's stderr names something else).
|
|
161
|
+
*
|
|
162
|
+
* Order matters: `BackendUnreachable` (no server) > `SessionExists`
|
|
163
|
+
* (duplicate-session race) > `SessionGone` (target doesn't exist) >
|
|
164
|
+
* `BackendError` (unrecognized failure). The routine failure modes are
|
|
165
|
+
* promoted above `BackendError` so the substrate surfaces a clean typed
|
|
166
|
+
* error rather than the catch-all. As a structural backstop,
|
|
167
|
+
* `BackendError.message` itself does not embed the tmux argv (see
|
|
168
|
+
* `errors.ts`), so even an unclassified shape cannot leak the backend's
|
|
169
|
+
* vocabulary into user-facing text.
|
|
170
|
+
*/
|
|
171
|
+
export function classifyTmuxFailure(sessionName, argv, result) {
|
|
172
|
+
if (result.exit === 0)
|
|
173
|
+
return null;
|
|
174
|
+
if (isNoServerStderr(result.stderr)) {
|
|
175
|
+
// Don't echo the backend's stderr (path + "tmux" substring leak).
|
|
176
|
+
// The clean public summary tells the user what they can do; the raw
|
|
177
|
+
// stderr is still available via observability (`onBackendCommand`).
|
|
178
|
+
return new BackendUnreachable(sessionName, "no-server", new Error("no server running on the configured socket"));
|
|
179
|
+
}
|
|
180
|
+
if (isDuplicateSessionStderr(result.stderr)) {
|
|
181
|
+
// A concurrent spawn won the race — same outcome as a check-time
|
|
182
|
+
// collision. The substrate never silently adopts.
|
|
183
|
+
return new SessionExists(sessionName);
|
|
184
|
+
}
|
|
185
|
+
if (isSessionGoneStderr(result.stderr)) {
|
|
186
|
+
return new SessionGone(sessionName);
|
|
187
|
+
}
|
|
188
|
+
return new BackendError(sessionName, argv, result.exit, result.stderr);
|
|
189
|
+
}
|
|
190
|
+
/** Is this the `no-server` {@link BackendUnreachable} (empty/down server)? */
|
|
191
|
+
export function isNoServer(err) {
|
|
192
|
+
return err instanceof BackendUnreachable && err.kind === "no-server";
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Run a tmux op that targets ONE named session, applying the **canonical
|
|
196
|
+
* per-session failure mapping** — the single place reads and writes share so the
|
|
197
|
+
* same crash can't surface as two different errors again (the read/write drift).
|
|
198
|
+
*
|
|
199
|
+
* From a single session's vantage, a `no-server` failure means *this session is
|
|
200
|
+
* gone* → {@link SessionGone}, NOT a distinct backend fault. (Registry ops —
|
|
201
|
+
* `list`/`exists` — keep treating no-server as plain absence; `BackendUnreachable`
|
|
202
|
+
* is left to mean a *real* backend fault: `spawn-failed` / `timeout`.) The
|
|
203
|
+
* canonical answer holds whether no-server arrives as a rejection (the common
|
|
204
|
+
* path) or as a returned non-zero result.
|
|
205
|
+
*
|
|
206
|
+
* Returns the raw {@link TmuxResult} on success; throws the typed error otherwise.
|
|
207
|
+
*/
|
|
208
|
+
export async function runForSession(exec, args, label, opts = {}) {
|
|
209
|
+
let r;
|
|
210
|
+
try {
|
|
211
|
+
r = await exec.run([...args], { sessionName: label, ...opts });
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
if (isNoServer(err))
|
|
215
|
+
throw new SessionGone(label);
|
|
216
|
+
throw err; // spawn-failed / timeout / a real fault — surface as-is
|
|
217
|
+
}
|
|
218
|
+
const classified = classifyTmuxFailure(label, ["tmux", ...args], r);
|
|
219
|
+
if (classified !== null) {
|
|
220
|
+
if (isNoServer(classified))
|
|
221
|
+
throw new SessionGone(label);
|
|
222
|
+
throw classified;
|
|
223
|
+
}
|
|
224
|
+
return r;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=exec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec.js","sourceRoot":"","sources":["../../../src/backends/tmux/exec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Backend } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Assemble the `Backend` implementation from the tmux subsystem.
|
|
4
|
+
*
|
|
5
|
+
* This file is the *only* place where the tmux backend's concrete pieces
|
|
6
|
+
* compose into the seam contract — and the *only* place that bridges
|
|
7
|
+
* `SessionRef` → the tmux target-name encoding (`<ns>--<name>`). Callers
|
|
8
|
+
* outside `src/backends/tmux/**` never construct or parse target strings.
|
|
9
|
+
*
|
|
10
|
+
* **Validation policy (QA P1, 7360b35b):** name validation is a *mutating*
|
|
11
|
+
* concern. `spawn` / `send` / `capture` reject reserved-char names with
|
|
12
|
+
* `InvalidSessionName` (you cannot meaningfully write to / read from a name
|
|
13
|
+
* the substrate can't address). But `exists` and `kill` are *query /
|
|
14
|
+
* idempotent* verbs with total contracts — `exists` returns a boolean,
|
|
15
|
+
* `kill` is a no-op on a missing session. A reserved-char name simply
|
|
16
|
+
* *cannot* name a live session, so `exists` → `false` and `kill` → no-op,
|
|
17
|
+
* rather than throwing and breaking those documented contracts.
|
|
18
|
+
*/
|
|
19
|
+
export declare function tmuxBackend(opts: {
|
|
20
|
+
socket: string;
|
|
21
|
+
}): Backend;
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAKb,MAAM,aAAa,CAAC;AAOrB;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CA+E7D"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { InvalidSessionName } from "../../errors.js";
|
|
2
|
+
import { validateNamePart } from "../../session/validate.js";
|
|
3
|
+
import { formatSessionLabel, } from "../types.js";
|
|
4
|
+
import { capturePane } from "./capture.js";
|
|
5
|
+
import { TmuxExec } from "./exec.js";
|
|
6
|
+
import { pasteText, sendKey } from "./keys.js";
|
|
7
|
+
import { getSessionOption, setSessionOption } from "./options.js";
|
|
8
|
+
import { hasSession, killSession, listSessions, newSession, targetOf } from "./sessions.js";
|
|
9
|
+
/**
|
|
10
|
+
* Assemble the `Backend` implementation from the tmux subsystem.
|
|
11
|
+
*
|
|
12
|
+
* This file is the *only* place where the tmux backend's concrete pieces
|
|
13
|
+
* compose into the seam contract — and the *only* place that bridges
|
|
14
|
+
* `SessionRef` → the tmux target-name encoding (`<ns>--<name>`). Callers
|
|
15
|
+
* outside `src/backends/tmux/**` never construct or parse target strings.
|
|
16
|
+
*
|
|
17
|
+
* **Validation policy (QA P1, 7360b35b):** name validation is a *mutating*
|
|
18
|
+
* concern. `spawn` / `send` / `capture` reject reserved-char names with
|
|
19
|
+
* `InvalidSessionName` (you cannot meaningfully write to / read from a name
|
|
20
|
+
* the substrate can't address). But `exists` and `kill` are *query /
|
|
21
|
+
* idempotent* verbs with total contracts — `exists` returns a boolean,
|
|
22
|
+
* `kill` is a no-op on a missing session. A reserved-char name simply
|
|
23
|
+
* *cannot* name a live session, so `exists` → `false` and `kill` → no-op,
|
|
24
|
+
* rather than throwing and breaking those documented contracts.
|
|
25
|
+
*/
|
|
26
|
+
export function tmuxBackend(opts) {
|
|
27
|
+
const exec = new TmuxExec(opts.socket);
|
|
28
|
+
// Validate the ref + produce both the tmux-internal target and the public
|
|
29
|
+
// label. Throws InvalidSessionName for reserved-char names — used by the
|
|
30
|
+
// mutating/I-O verbs. Defense-in-depth: create() validates at the public
|
|
31
|
+
// entry; a direct tmuxBackend caller could still bypass that.
|
|
32
|
+
const refToTarget = (ref) => {
|
|
33
|
+
validateNamePart("namespace", ref.namespace);
|
|
34
|
+
validateNamePart("name", ref.name);
|
|
35
|
+
return { target: targetOf(ref.namespace, ref.name), label: formatSessionLabel(ref) };
|
|
36
|
+
};
|
|
37
|
+
// Non-throwing variant for the total query/idempotent verbs. Returns null
|
|
38
|
+
// when the ref is invalid — an invalid name cannot name a live session.
|
|
39
|
+
const tryRefToTarget = (ref) => {
|
|
40
|
+
try {
|
|
41
|
+
return refToTarget(ref);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err instanceof InvalidSessionName)
|
|
45
|
+
return null;
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
// The mutating/I-O methods are `async` so a synchronous `validateNamePart`
|
|
50
|
+
// throw becomes a promise *rejection* — consistent with their Promise
|
|
51
|
+
// return type (a consumer's `.catch` / `await … rejects` works uniformly).
|
|
52
|
+
return {
|
|
53
|
+
id: "tmux",
|
|
54
|
+
spawn: async (o) => {
|
|
55
|
+
validateNamePart("namespace", o.namespace);
|
|
56
|
+
validateNamePart("name", o.name);
|
|
57
|
+
await newSession(exec, {
|
|
58
|
+
namespace: o.namespace,
|
|
59
|
+
name: o.name,
|
|
60
|
+
cwd: o.cwd,
|
|
61
|
+
...(o.env ? { env: o.env } : {}),
|
|
62
|
+
cmd: o.cmd,
|
|
63
|
+
argv: o.argv,
|
|
64
|
+
label: formatSessionLabel({ namespace: o.namespace, name: o.name }),
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
kill: (ref) => {
|
|
68
|
+
const t = tryRefToTarget(ref);
|
|
69
|
+
if (t === null)
|
|
70
|
+
return Promise.resolve(); // invalid name → nothing to kill
|
|
71
|
+
return killSession(exec, t.target, t.label);
|
|
72
|
+
},
|
|
73
|
+
exists: (ref) => {
|
|
74
|
+
const t = tryRefToTarget(ref);
|
|
75
|
+
if (t === null)
|
|
76
|
+
return Promise.resolve(false); // invalid name can't be alive
|
|
77
|
+
return hasSession(exec, t.target, t.label);
|
|
78
|
+
},
|
|
79
|
+
list: async (namespace) => {
|
|
80
|
+
validateNamePart("namespace", namespace);
|
|
81
|
+
return listSessions(exec, namespace);
|
|
82
|
+
},
|
|
83
|
+
send: async (ref, payload) => {
|
|
84
|
+
const { target, label } = refToTarget(ref);
|
|
85
|
+
await (payload.kind === "paste"
|
|
86
|
+
? pasteText(exec, target, payload.text, label)
|
|
87
|
+
: sendKey(exec, target, payload.key, label));
|
|
88
|
+
},
|
|
89
|
+
capture: async (ref, o) => {
|
|
90
|
+
const { target, label } = refToTarget(ref);
|
|
91
|
+
return capturePane(exec, target, { ...o, label });
|
|
92
|
+
},
|
|
93
|
+
setSessionMeta: async (ref, key, value) => {
|
|
94
|
+
const { target, label } = refToTarget(ref);
|
|
95
|
+
await setSessionOption(exec, target, key, value, label);
|
|
96
|
+
},
|
|
97
|
+
getSessionMeta: async (ref, key) => {
|
|
98
|
+
// Total/best-effort, like exists/kill: an invalid name can't name a live
|
|
99
|
+
// session, so it has no metadata — return undefined rather than throw.
|
|
100
|
+
const t = tryRefToTarget(ref);
|
|
101
|
+
if (t === null)
|
|
102
|
+
return undefined;
|
|
103
|
+
return getSessionOption(exec, t.target, key, t.label);
|
|
104
|
+
},
|
|
105
|
+
onCommand: (h) => exec.onCommand(h),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/backends/tmux/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type TmuxExec } from "./exec.js";
|
|
2
|
+
/**
|
|
3
|
+
* Send a `paste` payload to a tmux session via `load-buffer + paste-buffer -p`.
|
|
4
|
+
*
|
|
5
|
+
* `-p` lets tmux emit bracketed-paste sequences if the receiver advertised
|
|
6
|
+
* support — empirically verified byte-perfect end-to-end against a passive
|
|
7
|
+
* sink on tmux 3.6 (see `docs/decisions/0001-tmux-paste-mechanism.md`). Body
|
|
8
|
+
* line terminators normalize
|
|
9
|
+
* to `\n` so claude (and other TUI agents that opt into bracketed paste)
|
|
10
|
+
* see literal newlines inside the bracket, not stray `\r`s.
|
|
11
|
+
*
|
|
12
|
+
* **Does NOT auto-append Enter.** Submission is a separate `key` call. This
|
|
13
|
+
* is the architectural lock-in from `engineer/wiki/tmux-private-server-bootstrap`'s
|
|
14
|
+
* sibling page — multi-line input cannot leak around the seam because
|
|
15
|
+
* `Backend.send` has no `sendRawText` primitive.
|
|
16
|
+
*
|
|
17
|
+
* **Pre-checks liveness.** `send-keys` returns exit 0 against a dead pane
|
|
18
|
+
* (silent input drop trap — see `engineer/wiki/tmux-pane-death-detection`).
|
|
19
|
+
* We check `has-session` first; if the session is gone, throw `SessionGone`
|
|
20
|
+
* rather than letting the paste land in the void.
|
|
21
|
+
*
|
|
22
|
+
* `label` is the user-facing identifier used in error messages (defaults to
|
|
23
|
+
* `target`); the wrapper in `tmuxBackend` passes the public label.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Strip control bytes from a paste body that could break out of the bracketed
|
|
27
|
+
* paste or inject terminal control. The danger (F48): a body containing the
|
|
28
|
+
* paste-END marker `ESC[201~` closes the bracket early, so its tail submits as
|
|
29
|
+
* *typed* input — content carrying terminal escapes (logs, diffs, adversarial
|
|
30
|
+
* input) could run commands. Keep `\n` (literal newlines — the point of bracketed
|
|
31
|
+
* paste) and `\t`; drop the bracketed-paste markers explicitly (no `[201~`
|
|
32
|
+
* residue) plus every other C0/DEL control byte (incl. bare ESC). Normalize CRs
|
|
33
|
+
* to `\n` BEFORE stripping so a lone `\r` becomes a newline, not nothing.
|
|
34
|
+
*/
|
|
35
|
+
export declare function sanitizePasteBody(text: string): string;
|
|
36
|
+
export declare function pasteText(exec: TmuxExec, target: string, text: string, label?: string): Promise<void>;
|
|
37
|
+
export declare function sendKey(exec: TmuxExec, target: string, key: "Enter" | "Escape" | "1" | "2" | "3" | "y" | "n", label?: string): Promise<void>;
|
|
38
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/keys.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,QAAQ,EAAiB,MAAM,WAAW,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUtD;AAED,wBAAsB,SAAS,CAC7B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED,wBAAsB,OAAO,CAC3B,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,EACrD,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAGf"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { SessionGone } from "../../errors.js";
|
|
3
|
+
import { runForSession } from "./exec.js";
|
|
4
|
+
import { hasSession } from "./sessions.js";
|
|
5
|
+
/**
|
|
6
|
+
* Send a `paste` payload to a tmux session via `load-buffer + paste-buffer -p`.
|
|
7
|
+
*
|
|
8
|
+
* `-p` lets tmux emit bracketed-paste sequences if the receiver advertised
|
|
9
|
+
* support — empirically verified byte-perfect end-to-end against a passive
|
|
10
|
+
* sink on tmux 3.6 (see `docs/decisions/0001-tmux-paste-mechanism.md`). Body
|
|
11
|
+
* line terminators normalize
|
|
12
|
+
* to `\n` so claude (and other TUI agents that opt into bracketed paste)
|
|
13
|
+
* see literal newlines inside the bracket, not stray `\r`s.
|
|
14
|
+
*
|
|
15
|
+
* **Does NOT auto-append Enter.** Submission is a separate `key` call. This
|
|
16
|
+
* is the architectural lock-in from `engineer/wiki/tmux-private-server-bootstrap`'s
|
|
17
|
+
* sibling page — multi-line input cannot leak around the seam because
|
|
18
|
+
* `Backend.send` has no `sendRawText` primitive.
|
|
19
|
+
*
|
|
20
|
+
* **Pre-checks liveness.** `send-keys` returns exit 0 against a dead pane
|
|
21
|
+
* (silent input drop trap — see `engineer/wiki/tmux-pane-death-detection`).
|
|
22
|
+
* We check `has-session` first; if the session is gone, throw `SessionGone`
|
|
23
|
+
* rather than letting the paste land in the void.
|
|
24
|
+
*
|
|
25
|
+
* `label` is the user-facing identifier used in error messages (defaults to
|
|
26
|
+
* `target`); the wrapper in `tmuxBackend` passes the public label.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Strip control bytes from a paste body that could break out of the bracketed
|
|
30
|
+
* paste or inject terminal control. The danger (F48): a body containing the
|
|
31
|
+
* paste-END marker `ESC[201~` closes the bracket early, so its tail submits as
|
|
32
|
+
* *typed* input — content carrying terminal escapes (logs, diffs, adversarial
|
|
33
|
+
* input) could run commands. Keep `\n` (literal newlines — the point of bracketed
|
|
34
|
+
* paste) and `\t`; drop the bracketed-paste markers explicitly (no `[201~`
|
|
35
|
+
* residue) plus every other C0/DEL control byte (incl. bare ESC). Normalize CRs
|
|
36
|
+
* to `\n` BEFORE stripping so a lone `\r` becomes a newline, not nothing.
|
|
37
|
+
*/
|
|
38
|
+
export function sanitizePasteBody(text) {
|
|
39
|
+
return (text
|
|
40
|
+
.replace(/\r\n/g, "\n")
|
|
41
|
+
.replace(/\r/g, "\n")
|
|
42
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point.
|
|
43
|
+
.replace(/\x1b\[20[01]~/g, "")
|
|
44
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point.
|
|
45
|
+
.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, ""));
|
|
46
|
+
}
|
|
47
|
+
export async function pasteText(exec, target, text, label = target) {
|
|
48
|
+
await ensureLive(exec, target, label);
|
|
49
|
+
const normalized = sanitizePasteBody(text);
|
|
50
|
+
const bufferName = `claudemux-${randomBytes(4).toString("hex")}`;
|
|
51
|
+
await runForSession(exec, ["load-buffer", "-b", bufferName, "-"], label, { input: normalized });
|
|
52
|
+
await runForSession(exec, ["paste-buffer", "-p", "-d", "-b", bufferName, "-t", target], label);
|
|
53
|
+
}
|
|
54
|
+
export async function sendKey(exec, target, key, label = target) {
|
|
55
|
+
await ensureLive(exec, target, label);
|
|
56
|
+
await runForSession(exec, ["send-keys", "-t", target, key], label);
|
|
57
|
+
}
|
|
58
|
+
async function ensureLive(exec, target, label) {
|
|
59
|
+
if (!(await hasSession(exec, target, label))) {
|
|
60
|
+
throw new SessionGone(label);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/backends/tmux/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAiB,aAAa,EAAE,MAAM,WAAW,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,CACL,IAAI;SACD,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC;SACtB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;QACrB,wFAAwF;SACvF,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;QAC9B,wFAAwF;SACvF,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAC5C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAc,EACd,MAAc,EACd,IAAY,EACZ,QAAgB,MAAM;IAEtB,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IAEtC,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,UAAU,GAAG,aAAa,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IAEjE,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC,aAAa,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;IAChG,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;AACjG,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,IAAc,EACd,MAAc,EACd,GAAqD,EACrD,QAAgB,MAAM;IAEtB,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACtC,MAAM,aAAa,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;AACrE,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAc,EAAE,MAAc,EAAE,KAAa;IACrE,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,WAAW,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type TmuxExec } from "./exec.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the argv prefix that sets all server-globals, ready to be `;`-joined
|
|
4
|
+
* with a `new-session` (or any other command) into one tmux invocation.
|
|
5
|
+
*
|
|
6
|
+
* Returns chunks separated by `;` markers — callers concat with the next
|
|
7
|
+
* command's argv.
|
|
8
|
+
*/
|
|
9
|
+
export declare function serverOptionsArgv(): string[];
|
|
10
|
+
/**
|
|
11
|
+
* Persist a session-scoped key/value as a tmux user option on `target`. The
|
|
12
|
+
* value is passed as a single argv element (no shell), so no escaping is
|
|
13
|
+
* needed. The option lives with the session and is dropped when the session
|
|
14
|
+
* is killed — no cleanup required.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setSessionOption(exec: TmuxExec, target: string, key: string, value: string, label?: string): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Read a session user option previously set via {@link setSessionOption}.
|
|
19
|
+
* Returns `undefined` when the option is unset or the session/server is
|
|
20
|
+
* unreadable — this is best-effort metadata, never a hard error. `-v` prints
|
|
21
|
+
* just the value; an unset user option yields empty output (trimmed → unset).
|
|
22
|
+
*/
|
|
23
|
+
export declare function getSessionOption(exec: TmuxExec, target: string, key: string, label?: string): Promise<string | undefined>;
|
|
24
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../../src/backends/tmux/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAuB,MAAM,WAAW,CAAC;AA4B/D;;;;;;GAMG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,EAAE,CAM5C;AAYD;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,KAAK,GAAE,MAAe,GACrB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAY7B"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { classifyTmuxFailure } from "./exec.js";
|
|
2
|
+
/**
|
|
3
|
+
* Argv chunks that set the substrate's four server-global tmux options.
|
|
4
|
+
* Joined into a multi-command invocation in front of `new-session` so the
|
|
5
|
+
* globals are set **in the same tmux client connection** that creates the
|
|
6
|
+
* session — see `engineer/wiki/tmux-private-server-bootstrap`.
|
|
7
|
+
*
|
|
8
|
+
* Why this shape: `start-server` does not keep the server alive on its own
|
|
9
|
+
* (tmux exits the server when no session remains). `set-option -g` against
|
|
10
|
+
* an empty server fails with "no server running." Combining all commands
|
|
11
|
+
* into one `tmux …` invocation with `;` separators avoids the dead-window
|
|
12
|
+
* between "server starts" and "first session lands."
|
|
13
|
+
*
|
|
14
|
+
* `history-limit` is allocated at pane creation, so it must be set as a
|
|
15
|
+
* `-g` window-option BEFORE the pane is created. The other three follow the
|
|
16
|
+
* same pattern for consistency.
|
|
17
|
+
*
|
|
18
|
+
* `LC_ALL=C.UTF-8` lives on the session env (`-e` on new-session), so it is
|
|
19
|
+
* not in this list.
|
|
20
|
+
*/
|
|
21
|
+
const SERVER_OPTION_COMMANDS = [
|
|
22
|
+
["set-option", "-g", "escape-time", "0"],
|
|
23
|
+
["set-option", "-g", "default-terminal", "tmux-256color"],
|
|
24
|
+
["set-window-option", "-g", "history-limit", "50000"],
|
|
25
|
+
["set-window-option", "-g", "remain-on-exit", "off"],
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Build the argv prefix that sets all server-globals, ready to be `;`-joined
|
|
29
|
+
* with a `new-session` (or any other command) into one tmux invocation.
|
|
30
|
+
*
|
|
31
|
+
* Returns chunks separated by `;` markers — callers concat with the next
|
|
32
|
+
* command's argv.
|
|
33
|
+
*/
|
|
34
|
+
export function serverOptionsArgv() {
|
|
35
|
+
const out = [];
|
|
36
|
+
for (const cmd of SERVER_OPTION_COMMANDS) {
|
|
37
|
+
out.push(...cmd, ";");
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Map a backend-neutral session-meta key to a tmux **session user option**.
|
|
43
|
+
* User option names must begin with `@`; we further namespace under
|
|
44
|
+
* `@claudemux-` so the substrate's keys never collide with a consumer's own
|
|
45
|
+
* tmux options on the shared server.
|
|
46
|
+
*/
|
|
47
|
+
function userOptionName(key) {
|
|
48
|
+
return `@claudemux-${key}`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Persist a session-scoped key/value as a tmux user option on `target`. The
|
|
52
|
+
* value is passed as a single argv element (no shell), so no escaping is
|
|
53
|
+
* needed. The option lives with the session and is dropped when the session
|
|
54
|
+
* is killed — no cleanup required.
|
|
55
|
+
*/
|
|
56
|
+
export async function setSessionOption(exec, target, key, value, label = target) {
|
|
57
|
+
const args = ["set-option", "-t", target, userOptionName(key), value];
|
|
58
|
+
const r = await exec.run(args, { sessionName: label });
|
|
59
|
+
const err = classifyTmuxFailure(label, ["tmux", ...args], r);
|
|
60
|
+
if (err)
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Read a session user option previously set via {@link setSessionOption}.
|
|
65
|
+
* Returns `undefined` when the option is unset or the session/server is
|
|
66
|
+
* unreadable — this is best-effort metadata, never a hard error. `-v` prints
|
|
67
|
+
* just the value; an unset user option yields empty output (trimmed → unset).
|
|
68
|
+
*/
|
|
69
|
+
export async function getSessionOption(exec, target, key, label = target) {
|
|
70
|
+
let r;
|
|
71
|
+
try {
|
|
72
|
+
r = await exec.run(["show-options", "-t", target, "-v", userOptionName(key)], {
|
|
73
|
+
sessionName: label,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined; // session gone / no server / wedged — treat as "unset"
|
|
78
|
+
}
|
|
79
|
+
if (r.exit !== 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
const v = r.stdout.trim();
|
|
82
|
+
return v.length > 0 ? v : undefined;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.js","sourceRoot":"","sources":["../../../src/backends/tmux/options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAE/D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,sBAAsB,GAAqC;IAC/D,CAAC,YAAY,EAAE,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC;IACxC,CAAC,YAAY,EAAE,IAAI,EAAE,kBAAkB,EAAE,eAAe,CAAC;IACzD,CAAC,mBAAmB,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,CAAC;IACrD,CAAC,mBAAmB,EAAE,IAAI,EAAE,gBAAgB,EAAE,KAAK,CAAC;CACrD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,sBAAsB,EAAE,CAAC;QACzC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;GAKG;AACH,SAAS,cAAc,CAAC,GAAW;IACjC,OAAO,cAAc,GAAG,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAc,EACd,MAAc,EACd,GAAW,EACX,KAAa,EACb,QAAgB,MAAM;IAEtB,MAAM,IAAI,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC,CAAC;IACtE,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,mBAAmB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;IAC7D,IAAI,GAAG;QAAE,MAAM,GAAG,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAAc,EACd,MAAc,EACd,GAAW,EACX,QAAgB,MAAM;IAEtB,IAAI,CAAuC,CAAC;IAC5C,IAAI,CAAC;QACH,CAAC,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,CAAC,EAAE;YAC5E,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC,CAAC,uDAAuD;IAC3E,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IACnC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IAC1B,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtC,CAAC"}
|