@made-by-moonlight/athene-plugin-runtime-tmux 0.9.1

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/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Composio, Inc.
4
+ Copyright (c) 2026 slievr (Athene fork)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # @agent-orchestrator/plugin-runtime-tmux
2
+
3
+ Runtime plugin for executing agent sessions in tmux.
4
+
5
+ ## What This Does
6
+
7
+ Creates isolated tmux sessions for each agent. Each session runs in a separate tmux session with:
8
+
9
+ - Working directory set to workspace path
10
+ - Environment variables from config
11
+ - Agent launch command executed automatically
12
+
13
+ ## How It Works
14
+
15
+ ### Creating a Session
16
+
17
+ ```typescript
18
+ const handle = await runtime.create({
19
+ sessionId: "my-app-3",
20
+ workspacePath: "/Users/dev/.worktrees/my-app/my-app-3",
21
+ launchCommand: "claude -p 'Fix bug in auth module'",
22
+ environment: {
23
+ AO_SESSION_ID: "my-app-3",
24
+ AO_PROJECT_ID: "my-app",
25
+ },
26
+ });
27
+ ```
28
+
29
+ **What happens:**
30
+
31
+ 1. Validates `sessionId` (only alphanumeric, dash, underscore allowed)
32
+ 2. Creates detached tmux session: `tmux new-session -d -s my-app-3 -c /path/to/workspace`
33
+ 3. Sets environment variables: `tmux ... -e KEY=VALUE`
34
+ 4. Sends launch command: `tmux send-keys -t my-app-3 "claude -p '...'" Enter`
35
+ 5. Returns RuntimeHandle with tmux session name
36
+
37
+ ### Sending Messages
38
+
39
+ ```typescript
40
+ await runtime.sendMessage(handle, "Fix the test failure in auth.test.ts");
41
+ ```
42
+
43
+ **What happens:**
44
+
45
+ 1. Clears partial input: `tmux send-keys -t my-app-3 C-u`
46
+ 2. For short messages (<200 chars, no newlines): sends directly with `-l` flag (literal mode)
47
+ 3. For long/multiline messages: writes to temp file → `tmux load-buffer` → `tmux paste-buffer`
48
+ 4. Waits 300ms (let tmux process the text)
49
+ 5. Sends Enter: `tmux send-keys -t my-app-3 Enter`
50
+
51
+ **Why the complexity?**
52
+
53
+ - `send-keys` without `-l` interprets special strings ("Enter", "Space") as key names
54
+ - Long strings can overflow tmux's command buffer
55
+ - Multiline strings need special handling
56
+
57
+ ### Getting Output
58
+
59
+ ```typescript
60
+ const output = await runtime.getOutput(handle, 50); // last 50 lines
61
+ ```
62
+
63
+ Uses `tmux capture-pane -t my-app-3 -p -S -50` to capture terminal buffer.
64
+
65
+ ### Checking if Alive
66
+
67
+ ```typescript
68
+ const alive = await runtime.isAlive(handle);
69
+ ```
70
+
71
+ Uses `tmux has-session -t my-app-3` (exit code 0 = exists, 1 = doesn't exist).
72
+
73
+ ### Destroying
74
+
75
+ ```typescript
76
+ await runtime.destroy(handle);
77
+ ```
78
+
79
+ Kills tmux session: `tmux kill-session -t my-app-3` (ignores errors if already dead).
80
+
81
+ ## Attaching to Sessions
82
+
83
+ For Terminal plugins (iTerm2, web):
84
+
85
+ ```typescript
86
+ const attachInfo = await runtime.getAttachInfo(handle);
87
+ // Returns: { type: "tmux", target: "my-app-3", command: "tmux attach -t my-app-3" }
88
+ ```
89
+
90
+ ## Security
91
+
92
+ **Session ID validation:**
93
+
94
+ ```typescript
95
+ const SAFE_SESSION_ID = /^[a-zA-Z0-9_-]+$/;
96
+ ```
97
+
98
+ Only allows safe characters. Prevents shell injection via session name (used in tmux commands).
99
+
100
+ ## Error Handling
101
+
102
+ - **Session creation fails** → cleans up (kills session) before throwing
103
+ - **Message send fails** → throws (caller should handle)
104
+ - **Session already dead** → `destroy()` silently succeeds (idempotent)
105
+
106
+ ## Metrics
107
+
108
+ ```typescript
109
+ const metrics = await runtime.getMetrics(handle);
110
+ // Returns: { uptimeMs: 123456 }
111
+ ```
112
+
113
+ Tracks uptime (stored in RuntimeHandle.data.createdAt).
114
+
115
+ ## Testing
116
+
117
+ This plugin is tested indirectly via `packages/core/src/__tests__/tmux.test.ts` (utility functions) and integration tests.
118
+
119
+ To test manually:
120
+
121
+ ```bash
122
+ # Start a test session
123
+ tmux new-session -d -s test-session -c /tmp
124
+ tmux send-keys -t test-session "echo hello" Enter
125
+
126
+ # Capture output
127
+ tmux capture-pane -t test-session -p
128
+
129
+ # Kill session
130
+ tmux kill-session -t test-session
131
+ ```
132
+
133
+ ## Common Issues
134
+
135
+ ### tmux not installed
136
+
137
+ If tmux is not in PATH, all operations fail. Install via:
138
+
139
+ - macOS: `brew install tmux`
140
+ - Linux: `apt-get install tmux` or `yum install tmux`
141
+
142
+ ### Session name conflicts
143
+
144
+ If a session with the same ID already exists, `create()` fails. The orchestrator should ensure unique session IDs.
145
+
146
+ ### Detached sessions persist after orchestrator crashes
147
+
148
+ tmux sessions keep running even if the orchestrator dies. Use `tmux list-sessions` to find orphans, `tmux kill-session -t <name>` to clean up.
149
+
150
+ ## Limitations
151
+
152
+ - **macOS/Linux only** — tmux is not available natively on Windows. On Windows, use the `runtime-process` plugin (the default there); it provides native PTY support via ConPTY and `node-pty`. WSL is not required.
153
+ - **Terminal buffer size** — `getOutput()` limited by tmux buffer size (default 2000 lines)
154
+ - **No resource limits** — agents can consume unlimited CPU/memory (use docker/k8s runtimes for isolation)
155
+
156
+ ## Architecture Notes
157
+
158
+ **Why tmux over raw processes?**
159
+
160
+ - Sessions persist across orchestrator restarts
161
+ - Easy to attach for debugging: `tmux attach -t session-name`
162
+ - Terminal emulation (colors, ANSI codes work)
163
+ - Works well with interactive AI tools (Claude Code, Aider)
164
+
165
+ **Why detached mode?**
166
+
167
+ - Orchestrator doesn't block waiting for agent
168
+ - Multiple agents can run in parallel
169
+ - Humans can attach later without interrupting agent
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,466 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import * as childProcess from "node:child_process";
3
+ import * as fs from "node:fs";
4
+ // Mock node:child_process with custom promisify support
5
+ vi.mock("node:child_process", () => {
6
+ const mockExecFile = vi.fn();
7
+ // promisify(execFile) checks for a custom promisify symbol. Set it so
8
+ // await execFileAsync(...) returns { stdout, stderr } properly.
9
+ mockExecFile[Symbol.for("nodejs.util.promisify.custom")] = vi.fn();
10
+ return { execFile: mockExecFile };
11
+ });
12
+ // Mock node:crypto for deterministic UUIDs
13
+ vi.mock("node:crypto", () => ({
14
+ randomUUID: () => "test-uuid-1234",
15
+ }));
16
+ // Mock node:fs for writeFileSync / unlinkSync
17
+ vi.mock("node:fs", () => ({
18
+ writeFileSync: vi.fn(),
19
+ unlinkSync: vi.fn(),
20
+ }));
21
+ // Get reference to the promisify-custom mock — this is what the plugin actually calls
22
+ const mockExecFileCustom = childProcess.execFile[Symbol.for("nodejs.util.promisify.custom")];
23
+ const expectedTmuxOptions = { timeout: 5_000 };
24
+ /** Queue a successful tmux command with the given stdout. */
25
+ function mockTmuxSuccess(stdout = "") {
26
+ mockExecFileCustom.mockResolvedValueOnce({ stdout: stdout + "\n", stderr: "" });
27
+ }
28
+ /** Queue a failed tmux command. */
29
+ function mockTmuxError(message) {
30
+ mockExecFileCustom.mockRejectedValueOnce(new Error(message));
31
+ }
32
+ /** Create a RuntimeHandle for testing. */
33
+ function makeHandle(id, createdAt) {
34
+ return {
35
+ id,
36
+ runtimeName: "tmux",
37
+ data: {
38
+ createdAt: createdAt ?? 1000,
39
+ workspacePath: "/tmp/workspace",
40
+ },
41
+ };
42
+ }
43
+ // Import after mocks are set up
44
+ import tmuxPlugin, { manifest, create } from "../index.js";
45
+ beforeEach(() => {
46
+ vi.clearAllMocks();
47
+ });
48
+ describe("manifest", () => {
49
+ it("has name 'tmux' and slot 'runtime'", () => {
50
+ expect(manifest.name).toBe("tmux");
51
+ expect(manifest.slot).toBe("runtime");
52
+ expect(manifest.version).toBe("0.1.0");
53
+ expect(manifest.description).toBe("Runtime plugin: tmux sessions");
54
+ });
55
+ it("default export includes manifest and create", () => {
56
+ expect(tmuxPlugin.manifest).toBe(manifest);
57
+ expect(tmuxPlugin.create).toBe(create);
58
+ });
59
+ });
60
+ describe("create()", () => {
61
+ it("returns a Runtime with name 'tmux'", () => {
62
+ const runtime = create();
63
+ expect(runtime.name).toBe("tmux");
64
+ });
65
+ });
66
+ describe("runtime.create()", () => {
67
+ it("calls new-session with correct args", async () => {
68
+ const runtime = create();
69
+ // 1: new-session (with launch command as initial), 2: set-option status off
70
+ mockTmuxSuccess();
71
+ mockTmuxSuccess();
72
+ const handle = await runtime.create({
73
+ sessionId: "test-session",
74
+ workspacePath: "/tmp/workspace",
75
+ launchCommand: "echo hello",
76
+ environment: {},
77
+ });
78
+ expect(handle.id).toBe("test-session");
79
+ expect(handle.runtimeName).toBe("tmux");
80
+ expect(handle.data.workspacePath).toBe("/tmp/workspace");
81
+ // First call: new-session — launch command has the keep-alive shell tail
82
+ // appended so the tmux session survives agent exit (issue #1756).
83
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", [
84
+ "new-session",
85
+ "-d",
86
+ "-s",
87
+ "test-session",
88
+ "-c",
89
+ "/tmp/workspace",
90
+ 'echo hello\nexec "${SHELL:-/bin/bash}" -i',
91
+ ], expectedTmuxOptions);
92
+ });
93
+ it("disables the tmux status bar immediately after new-session", async () => {
94
+ const runtime = create();
95
+ // 1: new-session, 2: set-option status off
96
+ mockTmuxSuccess();
97
+ mockTmuxSuccess();
98
+ await runtime.create({
99
+ sessionId: "status-bar-off",
100
+ workspacePath: "/tmp/ws",
101
+ launchCommand: "echo hi",
102
+ environment: {},
103
+ });
104
+ // Second call must be set-option ... status off, scoped to the session
105
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(2, "tmux", ["set-option", "-t", "status-bar-off", "status", "off"], expectedTmuxOptions);
106
+ });
107
+ it("includes -e KEY=VALUE flags for environment variables", async () => {
108
+ const runtime = create();
109
+ mockTmuxSuccess();
110
+ mockTmuxSuccess();
111
+ await runtime.create({
112
+ sessionId: "env-session",
113
+ workspacePath: "/tmp/ws",
114
+ launchCommand: "bash",
115
+ environment: { AO_SESSION: "env-session", FOO: "bar" },
116
+ });
117
+ // First call: new-session with env args
118
+ const firstCallArgs = mockExecFileCustom.mock.calls[0];
119
+ const args = firstCallArgs[1];
120
+ expect(args).toContain("-e");
121
+ expect(args).toContain("AO_SESSION=env-session");
122
+ expect(args).toContain("FOO=bar");
123
+ expect(args.at(-1)).toBe('bash\nexec "${SHELL:-/bin/bash}" -i');
124
+ });
125
+ it("starts the launch command as the initial tmux pane command", async () => {
126
+ const runtime = create();
127
+ mockTmuxSuccess();
128
+ mockTmuxSuccess();
129
+ await runtime.create({
130
+ sessionId: "launch-test",
131
+ workspacePath: "/tmp/ws",
132
+ launchCommand: "claude --session abc",
133
+ environment: {},
134
+ });
135
+ // First call: new-session passes the launch command as the pane's initial
136
+ // command, with the keep-alive shell tail appended.
137
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", [
138
+ "new-session",
139
+ "-d",
140
+ "-s",
141
+ "launch-test",
142
+ "-c",
143
+ "/tmp/ws",
144
+ 'claude --session abc\nexec "${SHELL:-/bin/bash}" -i',
145
+ ], expectedTmuxOptions);
146
+ });
147
+ it("appends an interactive shell tail so the tmux pane survives agent exit (regression for #1756)", async () => {
148
+ const runtime = create();
149
+ mockTmuxSuccess();
150
+ mockTmuxSuccess();
151
+ await runtime.create({
152
+ sessionId: "keep-alive",
153
+ workspacePath: "/tmp/ws",
154
+ launchCommand: "claude --session abc",
155
+ environment: {},
156
+ });
157
+ const finalArg = mockExecFileCustom.mock.calls[0][1].at(-1);
158
+ expect(finalArg).toContain("claude --session abc");
159
+ expect(finalArg).toMatch(/exec "\$\{SHELL:-\/bin\/bash\}" -i\s*$/);
160
+ });
161
+ it("keeps the keep-alive tail in the temp script for long launch commands", async () => {
162
+ const runtime = create();
163
+ const longCommand = "x".repeat(250);
164
+ // 1: new-session (with bash invocation as initial command), 2: set-option
165
+ mockTmuxSuccess();
166
+ mockTmuxSuccess();
167
+ await runtime.create({
168
+ sessionId: "launch-long",
169
+ workspacePath: "/tmp/ws",
170
+ launchCommand: longCommand,
171
+ environment: {},
172
+ });
173
+ expect(fs.writeFileSync).toHaveBeenCalledWith(expect.stringContaining("ao-launch-test-uuid-1234.sh"), expect.stringContaining(longCommand), { encoding: "utf-8", mode: 0o700 });
174
+ // The script body includes the interactive shell tail too — without it
175
+ // long-command sessions would still nuke tmux on agent exit (#1756).
176
+ const writeCall = fs.writeFileSync.mock
177
+ .calls[0];
178
+ expect(writeCall[1]).toMatch(/exec "\$\{SHELL:-\/bin\/bash\}" -i/);
179
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(1, "tmux", [
180
+ "new-session",
181
+ "-d",
182
+ "-s",
183
+ "launch-long",
184
+ "-c",
185
+ "/tmp/ws",
186
+ expect.stringContaining("bash "),
187
+ ], expectedTmuxOptions);
188
+ });
189
+ it("surfaces tmux new-session failures", async () => {
190
+ const runtime = create();
191
+ // new-session itself fails — no further tmux calls happen
192
+ mockTmuxError("new-session failed");
193
+ await expect(runtime.create({
194
+ sessionId: "fail-session",
195
+ workspacePath: "/tmp/ws",
196
+ launchCommand: "bad-command",
197
+ environment: {},
198
+ })).rejects.toThrow("new-session failed");
199
+ expect(mockExecFileCustom).toHaveBeenCalledTimes(1);
200
+ });
201
+ it("cleans up session if set-option fails", async () => {
202
+ const runtime = create();
203
+ // 1: new-session succeeds
204
+ mockTmuxSuccess();
205
+ // 2: set-option fails (e.g. tmux command timeout on a slow host)
206
+ mockTmuxError("set-option timed out");
207
+ // 3: kill-session (cleanup attempt)
208
+ mockTmuxSuccess();
209
+ await expect(runtime.create({
210
+ sessionId: "setopt-fail",
211
+ workspacePath: "/tmp/ws",
212
+ launchCommand: "echo hi",
213
+ environment: {},
214
+ })).rejects.toThrow('Failed to configure or launch session "setopt-fail"');
215
+ // kill-session must run so we don't leave an orphaned tmux session
216
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", ["kill-session", "-t", "setopt-fail"], expectedTmuxOptions);
217
+ });
218
+ it("rejects invalid session IDs with special characters", async () => {
219
+ const runtime = create();
220
+ await expect(runtime.create({
221
+ sessionId: "bad session!",
222
+ workspacePath: "/tmp/ws",
223
+ launchCommand: "echo",
224
+ environment: {},
225
+ })).rejects.toThrow('Invalid session ID "bad session!"');
226
+ });
227
+ it("rejects session IDs with dots", async () => {
228
+ const runtime = create();
229
+ await expect(runtime.create({
230
+ sessionId: "bad.session",
231
+ workspacePath: "/tmp/ws",
232
+ launchCommand: "echo",
233
+ environment: {},
234
+ })).rejects.toThrow("Invalid session ID");
235
+ });
236
+ it("accepts valid session IDs with hyphens and underscores", async () => {
237
+ const runtime = create();
238
+ mockTmuxSuccess();
239
+ mockTmuxSuccess();
240
+ const handle = await runtime.create({
241
+ sessionId: "valid-session_123",
242
+ workspacePath: "/tmp/ws",
243
+ launchCommand: "echo",
244
+ environment: {},
245
+ });
246
+ expect(handle.id).toBe("valid-session_123");
247
+ });
248
+ it("handles no environment (undefined)", async () => {
249
+ const runtime = create();
250
+ mockTmuxSuccess();
251
+ mockTmuxSuccess();
252
+ await runtime.create({
253
+ sessionId: "no-env",
254
+ workspacePath: "/tmp/ws",
255
+ launchCommand: "echo hi",
256
+ });
257
+ // First call should not contain -e flags
258
+ const firstCallArgs = mockExecFileCustom.mock.calls[0][1];
259
+ expect(firstCallArgs).toEqual([
260
+ "new-session",
261
+ "-d",
262
+ "-s",
263
+ "no-env",
264
+ "-c",
265
+ "/tmp/ws",
266
+ 'echo hi\nexec "${SHELL:-/bin/bash}" -i',
267
+ ]);
268
+ });
269
+ });
270
+ describe("runtime.destroy()", () => {
271
+ it("calls kill-session with the handle id", async () => {
272
+ const runtime = create();
273
+ const handle = makeHandle("destroy-test");
274
+ mockTmuxSuccess();
275
+ await runtime.destroy(handle);
276
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", ["kill-session", "-t", "destroy-test"], expectedTmuxOptions);
277
+ });
278
+ it("does not throw if session is already gone", async () => {
279
+ const runtime = create();
280
+ const handle = makeHandle("already-dead");
281
+ mockTmuxError("session not found: already-dead");
282
+ // Should not throw
283
+ await expect(runtime.destroy(handle)).resolves.toBeUndefined();
284
+ });
285
+ });
286
+ describe("runtime.sendMessage()", () => {
287
+ it("sends short text with send-keys -l (literal) + Enter", async () => {
288
+ const runtime = create();
289
+ const handle = makeHandle("msg-short");
290
+ // 1: send-keys C-u (clear), 2: send-keys -l text, 3: send-keys Enter
291
+ mockTmuxSuccess();
292
+ mockTmuxSuccess();
293
+ mockTmuxSuccess();
294
+ await runtime.sendMessage(handle, "hello world");
295
+ expect(mockExecFileCustom).toHaveBeenCalledTimes(3);
296
+ // Call 0: Clear partial input
297
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(1, "tmux", ["send-keys", "-t", "msg-short", "C-u"], expectedTmuxOptions);
298
+ // Call 1: Literal text
299
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(2, "tmux", ["send-keys", "-t", "msg-short", "-l", "hello world"], expectedTmuxOptions);
300
+ // Call 2: Enter
301
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(3, "tmux", ["send-keys", "-t", "msg-short", "Enter"], expectedTmuxOptions);
302
+ });
303
+ it("uses load-buffer + paste-buffer for long text (> 200 chars)", async () => {
304
+ const runtime = create();
305
+ const handle = makeHandle("msg-long");
306
+ const longText = "x".repeat(250);
307
+ // 1: C-u, 2: load-buffer, 3: paste-buffer, 4: unlinkSync (sync), 5: delete-buffer, 6: Enter
308
+ mockTmuxSuccess(); // C-u
309
+ mockTmuxSuccess(); // load-buffer
310
+ mockTmuxSuccess(); // paste-buffer
311
+ mockTmuxSuccess(); // delete-buffer (finally block)
312
+ mockTmuxSuccess(); // Enter
313
+ await runtime.sendMessage(handle, longText);
314
+ expect(mockExecFileCustom).toHaveBeenCalledTimes(5);
315
+ // Call 0: clear
316
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(1, "tmux", ["send-keys", "-t", "msg-long", "C-u"], expectedTmuxOptions);
317
+ // Call 1: load-buffer with named buffer
318
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(2, "tmux", [
319
+ "load-buffer",
320
+ "-b",
321
+ "ao-test-uuid-1234",
322
+ expect.stringContaining("ao-send-test-uuid-1234.txt"),
323
+ ], expectedTmuxOptions);
324
+ // Call 2: paste-buffer
325
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(3, "tmux", ["paste-buffer", "-b", "ao-test-uuid-1234", "-t", "msg-long", "-d"], expectedTmuxOptions);
326
+ // Verify writeFileSync was called with the message
327
+ expect(fs.writeFileSync).toHaveBeenCalledWith(expect.stringContaining("ao-send-test-uuid-1234.txt"), longText, { encoding: "utf-8", mode: 0o600 });
328
+ // Verify unlinkSync was called for cleanup
329
+ expect(fs.unlinkSync).toHaveBeenCalledWith(expect.stringContaining("ao-send-test-uuid-1234.txt"));
330
+ });
331
+ it("uses load-buffer for multiline text", async () => {
332
+ const runtime = create();
333
+ const handle = makeHandle("msg-multi");
334
+ mockTmuxSuccess(); // C-u
335
+ mockTmuxSuccess(); // load-buffer
336
+ mockTmuxSuccess(); // paste-buffer
337
+ mockTmuxSuccess(); // delete-buffer (finally)
338
+ mockTmuxSuccess(); // Enter
339
+ await runtime.sendMessage(handle, "line1\nline2\nline3");
340
+ // Should use buffer path, not send-keys -l
341
+ expect(mockExecFileCustom).toHaveBeenNthCalledWith(2, "tmux", [
342
+ "load-buffer",
343
+ "-b",
344
+ "ao-test-uuid-1234",
345
+ expect.stringContaining("ao-send-test-uuid-1234.txt"),
346
+ ], expectedTmuxOptions);
347
+ expect(fs.writeFileSync).toHaveBeenCalledWith(expect.stringContaining("ao-send-test-uuid-1234.txt"), "line1\nline2\nline3", { encoding: "utf-8", mode: 0o600 });
348
+ });
349
+ it("cleans up buffer and temp file on paste failure", async () => {
350
+ const runtime = create();
351
+ const handle = makeHandle("msg-fail");
352
+ const longText = "y".repeat(250);
353
+ mockTmuxSuccess(); // C-u
354
+ mockTmuxSuccess(); // load-buffer succeeds
355
+ mockTmuxError("paste-buffer failed"); // paste-buffer fails
356
+ // finally block:
357
+ // unlinkSync is sync (mocked)
358
+ mockTmuxSuccess(); // delete-buffer in finally
359
+ // After finally, the error propagates — no Enter call
360
+ await expect(runtime.sendMessage(handle, longText)).rejects.toThrow("paste-buffer failed");
361
+ // unlinkSync should still be called for temp file cleanup
362
+ expect(fs.unlinkSync).toHaveBeenCalledWith(expect.stringContaining("ao-send-test-uuid-1234.txt"));
363
+ // delete-buffer should be called in finally block
364
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", ["delete-buffer", "-b", "ao-test-uuid-1234"], expectedTmuxOptions);
365
+ });
366
+ });
367
+ describe("runtime.getOutput()", () => {
368
+ it("calls capture-pane with correct args and default lines", async () => {
369
+ const runtime = create();
370
+ const handle = makeHandle("output-test");
371
+ mockTmuxSuccess("some output\nfrom tmux");
372
+ const output = await runtime.getOutput(handle);
373
+ expect(output).toBe("some output\nfrom tmux");
374
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", ["capture-pane", "-t", "output-test", "-p", "-S", "-50"], expectedTmuxOptions);
375
+ });
376
+ it("passes custom line count", async () => {
377
+ const runtime = create();
378
+ const handle = makeHandle("output-custom");
379
+ mockTmuxSuccess("output");
380
+ await runtime.getOutput(handle, 100);
381
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", ["capture-pane", "-t", "output-custom", "-p", "-S", "-100"], expectedTmuxOptions);
382
+ });
383
+ it("returns empty string on error", async () => {
384
+ const runtime = create();
385
+ const handle = makeHandle("output-err");
386
+ mockTmuxError("session not found");
387
+ const output = await runtime.getOutput(handle);
388
+ expect(output).toBe("");
389
+ });
390
+ });
391
+ describe("runtime.isAlive()", () => {
392
+ it("returns true when has-session succeeds", async () => {
393
+ const runtime = create();
394
+ const handle = makeHandle("alive-test");
395
+ mockTmuxSuccess();
396
+ const alive = await runtime.isAlive(handle);
397
+ expect(alive).toBe(true);
398
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", ["has-session", "-t", "alive-test"], expectedTmuxOptions);
399
+ });
400
+ it("returns false when has-session fails", async () => {
401
+ const runtime = create();
402
+ const handle = makeHandle("dead-test");
403
+ mockTmuxError("session not found");
404
+ const alive = await runtime.isAlive(handle);
405
+ expect(alive).toBe(false);
406
+ });
407
+ });
408
+ describe("runtime.getMetrics()", () => {
409
+ it("returns uptimeMs based on createdAt", async () => {
410
+ const runtime = create();
411
+ const now = Date.now();
412
+ const handle = makeHandle("metrics-test", now - 5000);
413
+ const metrics = await runtime.getMetrics(handle);
414
+ // uptimeMs should be approximately 5000ms (allow some wiggle room)
415
+ expect(metrics.uptimeMs).toBeGreaterThanOrEqual(5000);
416
+ expect(metrics.uptimeMs).toBeLessThan(6000);
417
+ });
418
+ it("handles missing createdAt by using Date.now()", async () => {
419
+ const runtime = create();
420
+ const handle = {
421
+ id: "metrics-no-created",
422
+ runtimeName: "tmux",
423
+ data: {},
424
+ };
425
+ const metrics = await runtime.getMetrics(handle);
426
+ // uptimeMs should be very close to 0 since createdAt defaults to Date.now()
427
+ expect(metrics.uptimeMs).toBeGreaterThanOrEqual(0);
428
+ expect(metrics.uptimeMs).toBeLessThan(1000);
429
+ });
430
+ });
431
+ describe("runtime.getAttachInfo()", () => {
432
+ it("returns tmux type and attach command", async () => {
433
+ const runtime = create();
434
+ const handle = makeHandle("attach-test");
435
+ const info = await runtime.getAttachInfo(handle);
436
+ expect(info).toEqual({
437
+ type: "tmux",
438
+ target: "attach-test",
439
+ command: "tmux attach -t attach-test",
440
+ });
441
+ });
442
+ });
443
+ describe("runtime.preflight()", () => {
444
+ it("resolves when `tmux -V` succeeds", async () => {
445
+ mockTmuxSuccess("tmux 3.4");
446
+ const runtime = create();
447
+ await expect(runtime.preflight({})).resolves.toBeUndefined();
448
+ expect(mockExecFileCustom).toHaveBeenCalledWith("tmux", ["-V"], expectedTmuxOptions);
449
+ });
450
+ it("throws with platform-specific install hint when tmux is missing", async () => {
451
+ mockTmuxError("ENOENT");
452
+ const runtime = create();
453
+ const err = (await runtime.preflight({}).catch((e) => e));
454
+ expect(err).toBeInstanceOf(Error);
455
+ expect(err.message).toContain("tmux is not installed");
456
+ expect(err.message).toContain("Install it:");
457
+ // Hint must include something runnable for the host platform.
458
+ if (process.platform === "darwin")
459
+ expect(err.message).toContain("brew install tmux");
460
+ else if (process.platform === "win32")
461
+ expect(err.message).toContain("WSL");
462
+ else
463
+ expect(err.message).toMatch(/apt install tmux|dnf install tmux/);
464
+ });
465
+ });
466
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { type Runtime } from "@made-by-moonlight/athene-core";
2
+ export declare const manifest: {
3
+ name: string;
4
+ slot: "runtime";
5
+ description: string;
6
+ version: string;
7
+ };
8
+ export declare function create(): Runtime;
9
+ declare const _default: {
10
+ manifest: {
11
+ name: string;
12
+ slot: "runtime";
13
+ description: string;
14
+ version: string;
15
+ };
16
+ create: typeof create;
17
+ };
18
+ export default _default;
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,OAAO,EAMb,MAAM,gCAAgC,CAAC;AAKxC,eAAO,MAAM,QAAQ;;;;;CAKpB,CAAC;AA4CF,wBAAgB,MAAM,IAAI,OAAO,CAwKhC;;;;;;;;;;AAED,wBAAoE"}
package/dist/index.js ADDED
@@ -0,0 +1,206 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { setTimeout as sleep } from "node:timers/promises";
4
+ import { randomUUID } from "node:crypto";
5
+ import { writeFileSync, unlinkSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
8
+ import { shellEscape, } from "@made-by-moonlight/athene-core";
9
+ const execFileAsync = promisify(execFile);
10
+ const TMUX_COMMAND_TIMEOUT_MS = 5_000;
11
+ export const manifest = {
12
+ name: "tmux",
13
+ slot: "runtime",
14
+ description: "Runtime plugin: tmux sessions",
15
+ version: "0.1.0",
16
+ };
17
+ /** Only allow safe characters in session IDs */
18
+ const SAFE_SESSION_ID = /^[a-zA-Z0-9_-]+$/;
19
+ function assertValidSessionId(id) {
20
+ if (!SAFE_SESSION_ID.test(id)) {
21
+ throw new Error(`Invalid session ID "${id}": must match ${SAFE_SESSION_ID}`);
22
+ }
23
+ }
24
+ /**
25
+ * Shell snippet appended after the agent launch command so the tmux pane
26
+ * (and therefore the tmux session) survives agent exit. Without this, the
27
+ * pane closes when the agent process exits, the only window goes away, and
28
+ * the whole tmux session dies — leaving the dashboard with a phantom
29
+ * "runtime lost" state and the user with no way to do anything in that
30
+ * workspace (issue #1756).
31
+ *
32
+ * `exec` replaces the wrapping sh/bash with the user's interactive shell,
33
+ * so the lifecycle manager still detects agent termination via
34
+ * `agent.isProcessRunning` and transitions the session correctly.
35
+ */
36
+ const KEEP_ALIVE_SHELL = `exec "\${SHELL:-/bin/bash}" -i`;
37
+ function withKeepAliveShell(command) {
38
+ return `${command.replace(/\n+$/, "")}\n${KEEP_ALIVE_SHELL}`;
39
+ }
40
+ function writeLaunchScript(command) {
41
+ const scriptPath = join(tmpdir(), `ao-launch-${randomUUID()}.sh`);
42
+ const content = `#!/usr/bin/env bash\nrm -- "$0" 2>/dev/null || true\n${withKeepAliveShell(command)}\n`;
43
+ writeFileSync(scriptPath, content, { encoding: "utf-8", mode: 0o700 });
44
+ return `bash ${shellEscape(scriptPath)}`;
45
+ }
46
+ /** Run a tmux command and return stdout */
47
+ async function tmux(...args) {
48
+ const { stdout } = await execFileAsync("tmux", args, {
49
+ timeout: TMUX_COMMAND_TIMEOUT_MS,
50
+ });
51
+ return stdout.trimEnd();
52
+ }
53
+ export function create() {
54
+ return {
55
+ name: "tmux",
56
+ async create(config) {
57
+ assertValidSessionId(config.sessionId);
58
+ const sessionName = config.sessionId;
59
+ // Build environment flags: -e KEY=VALUE for each env var
60
+ const envArgs = [];
61
+ for (const [key, value] of Object.entries(config.environment ?? {})) {
62
+ envArgs.push("-e", `${key}=${value}`);
63
+ }
64
+ // Re-export PATH inside the launch script. macOS zsh runs path_helper
65
+ // during shell startup which resets PATH, wiping entries set via tmux -e.
66
+ // Including the export in the launched shell command avoids terminal
67
+ // buffer issues with long PATH values (1000+ chars).
68
+ const pathValue = config.environment?.["PATH"];
69
+ let launchCommand = config.launchCommand;
70
+ if (pathValue) {
71
+ // Use printf with JSON-escaped value to avoid shell injection if
72
+ // PATH contains single quotes or other shell metacharacters.
73
+ launchCommand = `export PATH=$(printf '%s' ${JSON.stringify(pathValue)})\n${launchCommand}`;
74
+ }
75
+ // Start the launch command as the pane's initial command instead of
76
+ // typing into a live shell. A dashboard attach can trigger terminal
77
+ // device responses; if those race with tmux send-keys, they become
78
+ // literal shell input and corrupt the launch path. The keep-alive
79
+ // tail is appended in both code paths — see KEEP_ALIVE_SHELL.
80
+ const shellCommand = launchCommand.length > 200
81
+ ? writeLaunchScript(launchCommand)
82
+ : withKeepAliveShell(launchCommand);
83
+ await tmux("new-session", "-d", "-s", sessionName, "-c", config.workspacePath, ...envArgs, shellCommand);
84
+ // Hide the tmux status bar — sessions are embedded in the web terminal,
85
+ // and the green bar at the bottom is visual noise (and racy with the
86
+ // web layer's own set-option call, which only fires on WebSocket connect).
87
+ // Kill the session if this fails so we don't leave an orphaned tmux process.
88
+ try {
89
+ await tmux("set-option", "-t", sessionName, "status", "off");
90
+ }
91
+ catch (err) {
92
+ try {
93
+ await tmux("kill-session", "-t", sessionName);
94
+ }
95
+ catch {
96
+ // Best-effort cleanup
97
+ }
98
+ const msg = err instanceof Error ? err.message : String(err);
99
+ throw new Error(`Failed to configure or launch session "${sessionName}": ${msg}`, {
100
+ cause: err,
101
+ });
102
+ }
103
+ return {
104
+ id: sessionName,
105
+ runtimeName: "tmux",
106
+ data: {
107
+ createdAt: Date.now(),
108
+ workspacePath: config.workspacePath,
109
+ },
110
+ };
111
+ },
112
+ async destroy(handle) {
113
+ try {
114
+ await tmux("kill-session", "-t", handle.id);
115
+ }
116
+ catch {
117
+ // Session may already be dead — that's fine
118
+ }
119
+ },
120
+ async sendMessage(handle, message) {
121
+ // Clear any partial input
122
+ await tmux("send-keys", "-t", handle.id, "C-u");
123
+ // For long or multiline messages, use load-buffer + paste-buffer
124
+ // Use randomUUID to avoid temp file collisions on concurrent sends
125
+ if (message.includes("\n") || message.length > 200) {
126
+ const bufferName = `ao-${randomUUID()}`;
127
+ const tmpPath = join(tmpdir(), `ao-send-${randomUUID()}.txt`);
128
+ writeFileSync(tmpPath, message, { encoding: "utf-8", mode: 0o600 });
129
+ try {
130
+ await tmux("load-buffer", "-b", bufferName, tmpPath);
131
+ await tmux("paste-buffer", "-b", bufferName, "-t", handle.id, "-d");
132
+ }
133
+ finally {
134
+ // Clean up temp file and tmux buffer (in case paste-buffer failed
135
+ // and the -d flag didn't delete it)
136
+ try {
137
+ unlinkSync(tmpPath);
138
+ }
139
+ catch {
140
+ // ignore cleanup errors
141
+ }
142
+ try {
143
+ await tmux("delete-buffer", "-b", bufferName);
144
+ }
145
+ catch {
146
+ // Buffer may already be deleted by -d flag — that's fine
147
+ }
148
+ }
149
+ }
150
+ else {
151
+ // Use -l (literal) so text like "Enter" or "Space" isn't interpreted
152
+ // as tmux key names
153
+ await tmux("send-keys", "-t", handle.id, "-l", message);
154
+ }
155
+ // Small delay to let tmux process the pasted text before pressing Enter.
156
+ // Without this, Enter can arrive before the text is fully rendered.
157
+ await sleep(300);
158
+ await tmux("send-keys", "-t", handle.id, "Enter");
159
+ },
160
+ async getOutput(handle, lines = 50) {
161
+ try {
162
+ return await tmux("capture-pane", "-t", handle.id, "-p", "-S", `-${lines}`);
163
+ }
164
+ catch {
165
+ return "";
166
+ }
167
+ },
168
+ async isAlive(handle) {
169
+ try {
170
+ await tmux("has-session", "-t", handle.id);
171
+ return true;
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ },
177
+ async getMetrics(handle) {
178
+ const createdAt = handle.data.createdAt ?? Date.now();
179
+ return {
180
+ uptimeMs: Date.now() - createdAt,
181
+ };
182
+ },
183
+ async getAttachInfo(handle) {
184
+ return {
185
+ type: "tmux",
186
+ target: handle.id,
187
+ command: `tmux attach -t ${handle.id}`,
188
+ };
189
+ },
190
+ async preflight() {
191
+ try {
192
+ await execFileAsync("tmux", ["-V"], { timeout: TMUX_COMMAND_TIMEOUT_MS });
193
+ }
194
+ catch {
195
+ const hint = process.platform === "darwin"
196
+ ? "brew install tmux"
197
+ : process.platform === "win32"
198
+ ? "tmux is not available on Windows. Use WSL: wsl --install, then: sudo apt install tmux"
199
+ : "sudo apt install tmux (Debian/Ubuntu) or sudo dnf install tmux (Fedora)";
200
+ throw new Error(`tmux is not installed. Install it: ${hint}`);
201
+ }
202
+ },
203
+ };
204
+ }
205
+ export default { manifest, create };
206
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@made-by-moonlight/athene-plugin-runtime-tmux",
3
+ "version": "0.9.1",
4
+ "description": "Runtime plugin: tmux sessions",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/slievr/Athene.git",
21
+ "directory": "packages/plugins/runtime-tmux"
22
+ },
23
+ "homepage": "https://github.com/slievr/Athene",
24
+ "bugs": {
25
+ "url": "https://github.com/slievr/Athene/issues"
26
+ },
27
+ "engines": {
28
+ "node": ">=20.0.0"
29
+ },
30
+ "dependencies": {
31
+ "@made-by-moonlight/athene-core": "0.9.1"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^25.2.3",
35
+ "typescript": "^5.7.0",
36
+ "vitest": "^3.0.0"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc",
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "clean": "rm -rf dist"
47
+ }
48
+ }