@burdenoff/vibe-agent 1.0.0 → 1.0.2

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.
Files changed (94) hide show
  1. package/README.md +14 -14
  2. package/dist/app.d.ts +4 -4
  3. package/dist/app.d.ts.map +1 -1
  4. package/dist/app.js +144 -130
  5. package/dist/app.js.map +1 -1
  6. package/dist/cli.d.ts +1 -1
  7. package/dist/cli.js +342 -332
  8. package/dist/cli.js.map +1 -1
  9. package/dist/db/schema.d.ts +15 -15
  10. package/dist/db/schema.d.ts.map +1 -1
  11. package/dist/db/schema.js +65 -62
  12. package/dist/db/schema.js.map +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/index.js +18 -18
  15. package/dist/index.js.map +1 -1
  16. package/dist/middleware/ModuleAuth.d.ts +2 -2
  17. package/dist/middleware/ModuleAuth.d.ts.map +1 -1
  18. package/dist/middleware/ModuleAuth.js +32 -29
  19. package/dist/middleware/ModuleAuth.js.map +1 -1
  20. package/dist/middleware/auth.d.ts +1 -1
  21. package/dist/middleware/auth.d.ts.map +1 -1
  22. package/dist/middleware/auth.js +4 -4
  23. package/dist/middleware/auth.js.map +1 -1
  24. package/dist/migrations/remove-notes-prompts.d.ts.map +1 -1
  25. package/dist/migrations/remove-notes-prompts.js +26 -26
  26. package/dist/migrations/remove-notes-prompts.js.map +1 -1
  27. package/dist/routes/bookmarks.d.ts +1 -1
  28. package/dist/routes/bookmarks.d.ts.map +1 -1
  29. package/dist/routes/bookmarks.js +53 -44
  30. package/dist/routes/bookmarks.js.map +1 -1
  31. package/dist/routes/config.d.ts +1 -1
  32. package/dist/routes/config.d.ts.map +1 -1
  33. package/dist/routes/config.js +29 -27
  34. package/dist/routes/config.js.map +1 -1
  35. package/dist/routes/files.d.ts +1 -1
  36. package/dist/routes/files.d.ts.map +1 -1
  37. package/dist/routes/files.js +175 -134
  38. package/dist/routes/files.js.map +1 -1
  39. package/dist/routes/git.d.ts +1 -1
  40. package/dist/routes/git.d.ts.map +1 -1
  41. package/dist/routes/git.js +183 -169
  42. package/dist/routes/git.js.map +1 -1
  43. package/dist/routes/moduleRegistry.d.ts +3 -3
  44. package/dist/routes/moduleRegistry.d.ts.map +1 -1
  45. package/dist/routes/moduleRegistry.js +58 -58
  46. package/dist/routes/moduleRegistry.js.map +1 -1
  47. package/dist/routes/notifications.d.ts +1 -1
  48. package/dist/routes/notifications.d.ts.map +1 -1
  49. package/dist/routes/notifications.js +69 -64
  50. package/dist/routes/notifications.js.map +1 -1
  51. package/dist/routes/port-forward.d.ts +1 -1
  52. package/dist/routes/port-forward.d.ts.map +1 -1
  53. package/dist/routes/port-forward.js +59 -50
  54. package/dist/routes/port-forward.js.map +1 -1
  55. package/dist/routes/projects.d.ts +1 -1
  56. package/dist/routes/projects.d.ts.map +1 -1
  57. package/dist/routes/projects.js +134 -120
  58. package/dist/routes/projects.js.map +1 -1
  59. package/dist/routes/ssh.d.ts +1 -1
  60. package/dist/routes/ssh.d.ts.map +1 -1
  61. package/dist/routes/ssh.js +47 -47
  62. package/dist/routes/ssh.js.map +1 -1
  63. package/dist/routes/tasks.d.ts +1 -1
  64. package/dist/routes/tasks.d.ts.map +1 -1
  65. package/dist/routes/tasks.js +53 -49
  66. package/dist/routes/tasks.js.map +1 -1
  67. package/dist/routes/tmux.d.ts +1 -1
  68. package/dist/routes/tmux.d.ts.map +1 -1
  69. package/dist/routes/tmux.js +337 -241
  70. package/dist/routes/tmux.js.map +1 -1
  71. package/dist/routes/tunnel.d.ts +2 -2
  72. package/dist/routes/tunnel.d.ts.map +1 -1
  73. package/dist/routes/tunnel.js +115 -74
  74. package/dist/routes/tunnel.js.map +1 -1
  75. package/dist/services/ModulePermissions.d.ts +2 -2
  76. package/dist/services/ModulePermissions.d.ts.map +1 -1
  77. package/dist/services/ModulePermissions.js +50 -40
  78. package/dist/services/ModulePermissions.js.map +1 -1
  79. package/dist/services/ModuleRegistryService.d.ts +10 -10
  80. package/dist/services/ModuleRegistryService.d.ts.map +1 -1
  81. package/dist/services/ModuleRegistryService.js +156 -131
  82. package/dist/services/ModuleRegistryService.js.map +1 -1
  83. package/dist/services/agent.service.d.ts.map +1 -1
  84. package/dist/services/agent.service.js +24 -21
  85. package/dist/services/agent.service.js.map +1 -1
  86. package/dist/services/bootstrap.d.ts +1 -1
  87. package/dist/services/bootstrap.d.ts.map +1 -1
  88. package/dist/services/bootstrap.js +146 -69
  89. package/dist/services/bootstrap.js.map +1 -1
  90. package/dist/services/service-manager.d.ts +2 -2
  91. package/dist/services/service-manager.d.ts.map +1 -1
  92. package/dist/services/service-manager.js +75 -63
  93. package/dist/services/service-manager.js.map +1 -1
  94. package/package.json +2 -2
@@ -1,19 +1,19 @@
1
- import { spawn } from 'child_process';
2
- import { getAgentTunnelUrl } from './tunnel.js';
1
+ import { spawn } from "child_process";
2
+ import { getAgentTunnelUrl } from "./tunnel.js";
3
3
  export const tmuxRoutes = async (fastify) => {
4
4
  // Get all tmux sessions
5
- fastify.get('/', async (_request, _reply) => {
5
+ fastify.get("/", async (_request, _reply) => {
6
6
  const sessions = fastify.db.getAllTmuxSessions();
7
7
  return { sessions };
8
8
  });
9
9
  // Get all system tmux sessions (including unmanaged ones)
10
- fastify.get('/system', async (_request, reply) => {
10
+ fastify.get("/system", async (_request, reply) => {
11
11
  try {
12
12
  const systemSessions = await getSystemTmuxSessions();
13
13
  const managedSessions = fastify.db.getAllTmuxSessions();
14
14
  // Combine system sessions with managed session metadata
15
- const enrichedSessions = systemSessions.map(systemSession => {
16
- const managedSession = managedSessions.find(ms => ms.sessionName === systemSession.name);
15
+ const enrichedSessions = systemSessions.map((systemSession) => {
16
+ const managedSession = managedSessions.find((ms) => ms.sessionName === systemSession.name);
17
17
  return {
18
18
  ...systemSession,
19
19
  isManaged: !!managedSession,
@@ -28,29 +28,29 @@ export const tmuxRoutes = async (fastify) => {
28
28
  }
29
29
  catch (error) {
30
30
  return reply.code(500).send({
31
- error: 'Failed to get system tmux sessions',
32
- details: error instanceof Error ? error.message : 'Unknown error'
31
+ error: "Failed to get system tmux sessions",
32
+ details: error instanceof Error ? error.message : "Unknown error",
33
33
  });
34
34
  }
35
35
  });
36
36
  // Get all system ttyd processes
37
- fastify.get('/system/ttyd', async (_request, reply) => {
37
+ fastify.get("/system/ttyd", async (_request, reply) => {
38
38
  try {
39
39
  const ttydProcesses = await getSystemTtydProcesses();
40
40
  return { processes: ttydProcesses };
41
41
  }
42
42
  catch (error) {
43
43
  return reply.code(500).send({
44
- error: 'Failed to get system ttyd processes',
45
- details: error instanceof Error ? error.message : 'Unknown error'
44
+ error: "Failed to get system ttyd processes",
45
+ details: error instanceof Error ? error.message : "Unknown error",
46
46
  });
47
47
  }
48
48
  });
49
49
  // Kill system tmux sessions (bulk)
50
- fastify.post('/system/kill', async (request, reply) => {
50
+ fastify.post("/system/kill", async (request, reply) => {
51
51
  const { sessionNames, force } = request.body;
52
52
  if (!sessionNames || !Array.isArray(sessionNames)) {
53
- return reply.code(400).send({ error: 'sessionNames array is required' });
53
+ return reply.code(400).send({ error: "sessionNames array is required" });
54
54
  }
55
55
  try {
56
56
  const results = await killSystemTmuxSessions(sessionNames, force);
@@ -58,16 +58,16 @@ export const tmuxRoutes = async (fastify) => {
58
58
  }
59
59
  catch (error) {
60
60
  return reply.code(500).send({
61
- error: 'Failed to kill system tmux sessions',
62
- details: error instanceof Error ? error.message : 'Unknown error'
61
+ error: "Failed to kill system tmux sessions",
62
+ details: error instanceof Error ? error.message : "Unknown error",
63
63
  });
64
64
  }
65
65
  });
66
66
  // Kill system ttyd processes (bulk)
67
- fastify.post('/system/ttyd/kill', async (request, reply) => {
67
+ fastify.post("/system/ttyd/kill", async (request, reply) => {
68
68
  const { pids, force } = request.body;
69
69
  if (!pids || !Array.isArray(pids)) {
70
- return reply.code(400).send({ error: 'pids array is required' });
70
+ return reply.code(400).send({ error: "pids array is required" });
71
71
  }
72
72
  try {
73
73
  const results = await killSystemTtydProcesses(pids, force);
@@ -75,17 +75,17 @@ export const tmuxRoutes = async (fastify) => {
75
75
  }
76
76
  catch (error) {
77
77
  return reply.code(500).send({
78
- error: 'Failed to kill system ttyd processes',
79
- details: error instanceof Error ? error.message : 'Unknown error'
78
+ error: "Failed to kill system ttyd processes",
79
+ details: error instanceof Error ? error.message : "Unknown error",
80
80
  });
81
81
  }
82
82
  });
83
83
  // Get session by ID
84
- fastify.get('/:id', async (request, reply) => {
84
+ fastify.get("/:id", async (request, reply) => {
85
85
  const { id } = request.params;
86
86
  const session = fastify.db.getTmuxSession(id);
87
87
  if (!session) {
88
- return reply.code(404).send({ error: 'Session not found' });
88
+ return reply.code(404).send({ error: "Session not found" });
89
89
  }
90
90
  return { session };
91
91
  });
@@ -93,7 +93,7 @@ export const tmuxRoutes = async (fastify) => {
93
93
  // POST /health-check — accepts an array of session IDs and returns their
94
94
  // actual status by checking if the tmux session and ttyd process are alive.
95
95
  // Called periodically by the backend to reconcile DB state.
96
- fastify.post('/health-check', async (request, _reply) => {
96
+ fastify.post("/health-check", async (request, _reply) => {
97
97
  const { sessionIds } = request.body;
98
98
  if (!sessionIds || !Array.isArray(sessionIds)) {
99
99
  return { results: [] };
@@ -102,16 +102,26 @@ export const tmuxRoutes = async (fastify) => {
102
102
  for (const sessionId of sessionIds) {
103
103
  const dbSession = fastify.db.getTmuxSession(sessionId);
104
104
  if (!dbSession) {
105
- results.push({ sessionId, tmuxAlive: false, ttydAlive: false, ttydPort: null, status: 'unknown' });
105
+ results.push({
106
+ sessionId,
107
+ tmuxAlive: false,
108
+ ttydAlive: false,
109
+ ttydPort: null,
110
+ status: "unknown",
111
+ });
106
112
  continue;
107
113
  }
108
114
  // Check if tmux session is alive
109
115
  let tmuxAlive = false;
110
116
  try {
111
- const checkProc = spawn('tmux', ['has-session', '-t', dbSession.sessionName]);
117
+ const checkProc = spawn("tmux", [
118
+ "has-session",
119
+ "-t",
120
+ dbSession.sessionName,
121
+ ]);
112
122
  tmuxAlive = await new Promise((resolve) => {
113
- checkProc.on('close', (code) => resolve(code === 0));
114
- checkProc.on('error', () => resolve(false));
123
+ checkProc.on("close", (code) => resolve(code === 0));
124
+ checkProc.on("error", () => resolve(false));
115
125
  });
116
126
  }
117
127
  catch {
@@ -128,7 +138,7 @@ export const tmuxRoutes = async (fastify) => {
128
138
  ttydAlive = false;
129
139
  }
130
140
  }
131
- const status = tmuxAlive ? 'running' : 'dead';
141
+ const status = tmuxAlive ? "running" : "dead";
132
142
  results.push({
133
143
  sessionId,
134
144
  tmuxAlive,
@@ -140,19 +150,23 @@ export const tmuxRoutes = async (fastify) => {
140
150
  return { results };
141
151
  });
142
152
  // Create new tmux session (idempotent — re-adopts existing tmux sessions)
143
- fastify.post('/create', async (request, reply) => {
144
- const { sessionId, projectId, sessionName, windowName, command, startDirectory } = request.body;
153
+ fastify.post("/create", async (request, reply) => {
154
+ const { sessionId, projectId, sessionName, windowName, command, startDirectory, } = request.body;
145
155
  let finalSessionName = sessionName || `vibecontrols-${Date.now()}`;
146
- const finalWindowName = windowName || 'main';
156
+ const finalWindowName = windowName || "main";
147
157
  try {
148
158
  // Check if we already have this sessionId in our DB (idempotent re-call)
149
159
  const existingDbSession = fastify.db.getTmuxSession(sessionId);
150
160
  if (existingDbSession) {
151
161
  // Already tracked — verify the tmux session still exists
152
- const checkProc = spawn('tmux', ['has-session', '-t', existingDbSession.sessionName]);
162
+ const checkProc = spawn("tmux", [
163
+ "has-session",
164
+ "-t",
165
+ existingDbSession.sessionName,
166
+ ]);
153
167
  const checkOk = await new Promise((resolve) => {
154
- checkProc.on('close', (code) => resolve(code === 0));
155
- checkProc.on('error', () => resolve(false));
168
+ checkProc.on("close", (code) => resolve(code === 0));
169
+ checkProc.on("error", () => resolve(false));
156
170
  });
157
171
  if (checkOk) {
158
172
  return { session: existingDbSession, reused: true };
@@ -162,10 +176,10 @@ export const tmuxRoutes = async (fastify) => {
162
176
  }
163
177
  // Check if tmux session with this name already exists in the system
164
178
  // (e.g. from a previous agent run before DB was wiped)
165
- const hasProc = spawn('tmux', ['has-session', '-t', finalSessionName]);
179
+ const hasProc = spawn("tmux", ["has-session", "-t", finalSessionName]);
166
180
  const tmuxExists = await new Promise((resolve) => {
167
- hasProc.on('close', (code) => resolve(code === 0));
168
- hasProc.on('error', () => resolve(false));
181
+ hasProc.on("close", (code) => resolve(code === 0));
182
+ hasProc.on("error", () => resolve(false));
169
183
  });
170
184
  if (tmuxExists) {
171
185
  // tmux session exists but NOT in our DB — adopt it
@@ -174,38 +188,42 @@ export const tmuxRoutes = async (fastify) => {
174
188
  else {
175
189
  // Check if the name conflicts with another DB entry, make unique
176
190
  const existingSessions = fastify.db.getAllTmuxSessions();
177
- const existingNames = existingSessions.map(s => s.sessionName);
191
+ const existingNames = existingSessions.map((s) => s.sessionName);
178
192
  if (existingNames.includes(finalSessionName)) {
179
193
  finalSessionName = `${finalSessionName}-${Date.now()}`;
180
194
  }
181
195
  // Create new tmux session
182
- const tmuxCmd = ['tmux', 'new-session', '-d', '-s', finalSessionName];
196
+ const tmuxCmd = ["tmux", "new-session", "-d", "-s", finalSessionName];
183
197
  if (startDirectory)
184
- tmuxCmd.push('-c', startDirectory);
198
+ tmuxCmd.push("-c", startDirectory);
185
199
  if (command)
186
200
  tmuxCmd.push(command);
187
201
  const tmuxProcess = spawn(tmuxCmd[0], tmuxCmd.slice(1));
188
202
  await new Promise((resolve, reject) => {
189
- tmuxProcess.on('close', (code) => {
203
+ tmuxProcess.on("close", (code) => {
190
204
  if (code === 0)
191
205
  resolve(undefined);
192
206
  else
193
207
  reject(new Error(`Tmux exited with code ${code}`));
194
208
  });
195
- tmuxProcess.on('error', reject);
209
+ tmuxProcess.on("error", reject);
196
210
  });
197
211
  // Wait for tmux to be ready
198
- await new Promise(resolve => setTimeout(resolve, 100));
212
+ await new Promise((resolve) => setTimeout(resolve, 100));
199
213
  // Verify
200
- const verifyProcess = spawn('tmux', ['has-session', '-t', finalSessionName]);
214
+ const verifyProcess = spawn("tmux", [
215
+ "has-session",
216
+ "-t",
217
+ finalSessionName,
218
+ ]);
201
219
  await new Promise((resolve, reject) => {
202
- verifyProcess.on('close', (code) => {
220
+ verifyProcess.on("close", (code) => {
203
221
  if (code === 0)
204
222
  resolve(undefined);
205
223
  else
206
224
  reject(new Error(`Failed to verify tmux session ${finalSessionName}`));
207
225
  });
208
- verifyProcess.on('error', reject);
226
+ verifyProcess.on("error", reject);
209
227
  });
210
228
  }
211
229
  // Upsert database entry (handles both new creation and adoption)
@@ -214,7 +232,7 @@ export const tmuxRoutes = async (fastify) => {
214
232
  // Update existing record
215
233
  fastify.db.updateTmuxSession(sessionId, {
216
234
  sessionName: finalSessionName,
217
- status: 'active',
235
+ status: "active",
218
236
  });
219
237
  session = fastify.db.getTmuxSession(sessionId);
220
238
  }
@@ -225,171 +243,192 @@ export const tmuxRoutes = async (fastify) => {
225
243
  projectId,
226
244
  windowName: finalWindowName,
227
245
  command,
228
- status: 'active',
246
+ status: "active",
229
247
  ttydPort: undefined,
230
248
  ttydPid: undefined,
231
249
  });
232
250
  }
233
251
  // Emit session created event
234
- fastify.io.emit('session:created', session);
252
+ fastify.io.emit("session:created", session);
235
253
  return { session, reused: tmuxExists };
236
254
  }
237
255
  catch (error) {
238
256
  return reply.code(500).send({
239
- error: 'Failed to create tmux session',
240
- details: error instanceof Error ? error.message : 'Unknown error'
257
+ error: "Failed to create tmux session",
258
+ details: error instanceof Error ? error.message : "Unknown error",
241
259
  });
242
260
  }
243
261
  });
244
262
  // Execute command in session
245
- fastify.post('/:sessionId/command', async (request, reply) => {
263
+ fastify.post("/:sessionId/command", async (request, reply) => {
246
264
  const { sessionId } = request.params;
247
265
  const { command } = request.body;
248
266
  try {
249
267
  const session = fastify.db.getTmuxSession(sessionId);
250
268
  if (!session) {
251
- return reply.code(404).send({ error: 'Session not found' });
269
+ return reply.code(404).send({ error: "Session not found" });
252
270
  }
253
271
  // Send command to tmux session
254
- const tmuxProcess = spawn('tmux', ['send-keys', '-t', session.sessionName, command, 'Enter']);
272
+ const tmuxProcess = spawn("tmux", [
273
+ "send-keys",
274
+ "-t",
275
+ session.sessionName,
276
+ command,
277
+ "Enter",
278
+ ]);
255
279
  await new Promise((resolve, reject) => {
256
- tmuxProcess.on('close', (code) => {
280
+ tmuxProcess.on("close", (code) => {
257
281
  if (code === 0)
258
282
  resolve(undefined);
259
283
  else
260
284
  reject(new Error(`Tmux exited with code ${code}`));
261
285
  });
262
- tmuxProcess.on('error', reject);
286
+ tmuxProcess.on("error", reject);
263
287
  });
264
288
  // Emit command executed event
265
- fastify.io.emit('session:command', { sessionId, command });
289
+ fastify.io.emit("session:command", { sessionId, command });
266
290
  return { success: true };
267
291
  }
268
292
  catch (error) {
269
293
  return reply.code(500).send({
270
- error: 'Failed to execute command',
271
- details: error instanceof Error ? error.message : 'Unknown error'
294
+ error: "Failed to execute command",
295
+ details: error instanceof Error ? error.message : "Unknown error",
272
296
  });
273
297
  }
274
298
  });
275
299
  // Send keys to session
276
- fastify.post('/:sessionId/keys', async (request, reply) => {
300
+ fastify.post("/:sessionId/keys", async (request, reply) => {
277
301
  const { sessionId } = request.params;
278
302
  const { keys } = request.body;
279
303
  try {
280
304
  const session = fastify.db.getTmuxSession(sessionId);
281
305
  if (!session) {
282
- return reply.code(404).send({ error: 'Session not found' });
306
+ return reply.code(404).send({ error: "Session not found" });
283
307
  }
284
308
  // Send keys to tmux session
285
- const tmuxProcess = spawn('tmux', ['send-keys', '-t', session.sessionName, keys]);
309
+ const tmuxProcess = spawn("tmux", [
310
+ "send-keys",
311
+ "-t",
312
+ session.sessionName,
313
+ keys,
314
+ ]);
286
315
  await new Promise((resolve, reject) => {
287
- tmuxProcess.on('close', (code) => {
316
+ tmuxProcess.on("close", (code) => {
288
317
  if (code === 0)
289
318
  resolve(undefined);
290
319
  else
291
320
  reject(new Error(`Tmux exited with code ${code}`));
292
321
  });
293
- tmuxProcess.on('error', reject);
322
+ tmuxProcess.on("error", reject);
294
323
  });
295
324
  return { success: true };
296
325
  }
297
326
  catch (error) {
298
327
  return reply.code(500).send({
299
- error: 'Failed to send keys',
300
- details: error instanceof Error ? error.message : 'Unknown error'
328
+ error: "Failed to send keys",
329
+ details: error instanceof Error ? error.message : "Unknown error",
301
330
  });
302
331
  }
303
332
  });
304
333
  // Send interrupt (Ctrl+C) to session
305
- fastify.post('/:sessionId/interrupt', async (request, reply) => {
334
+ fastify.post("/:sessionId/interrupt", async (request, reply) => {
306
335
  const { sessionId } = request.params;
307
336
  try {
308
337
  const session = fastify.db.getTmuxSession(sessionId);
309
338
  if (!session) {
310
- return reply.code(404).send({ error: 'Session not found' });
339
+ return reply.code(404).send({ error: "Session not found" });
311
340
  }
312
341
  // Send Ctrl+C to tmux session
313
- const tmuxProcess = spawn('tmux', ['send-keys', '-t', session.sessionName, 'C-c']);
342
+ const tmuxProcess = spawn("tmux", [
343
+ "send-keys",
344
+ "-t",
345
+ session.sessionName,
346
+ "C-c",
347
+ ]);
314
348
  await new Promise((resolve, reject) => {
315
- tmuxProcess.on('close', (code) => {
349
+ tmuxProcess.on("close", (code) => {
316
350
  if (code === 0)
317
351
  resolve(undefined);
318
352
  else
319
353
  reject(new Error(`Tmux exited with code ${code}`));
320
354
  });
321
- tmuxProcess.on('error', reject);
355
+ tmuxProcess.on("error", reject);
322
356
  });
323
357
  return { success: true };
324
358
  }
325
359
  catch (error) {
326
360
  return reply.code(500).send({
327
- error: 'Failed to send interrupt',
328
- details: error instanceof Error ? error.message : 'Unknown error'
361
+ error: "Failed to send interrupt",
362
+ details: error instanceof Error ? error.message : "Unknown error",
329
363
  });
330
364
  }
331
365
  });
332
366
  // Capture session output
333
- fastify.get('/:sessionId/capture', async (request, reply) => {
367
+ fastify.get("/:sessionId/capture", async (request, reply) => {
334
368
  const { sessionId } = request.params;
335
369
  try {
336
370
  const session = fastify.db.getTmuxSession(sessionId);
337
371
  if (!session) {
338
- return reply.code(404).send({ error: 'Session not found' });
372
+ return reply.code(404).send({ error: "Session not found" });
339
373
  }
340
374
  // Capture tmux pane content
341
- const captureProcess = spawn('tmux', ['capture-pane', '-t', session.sessionName, '-p']);
342
- let output = '';
343
- captureProcess.stdout.on('data', (data) => {
375
+ const captureProcess = spawn("tmux", [
376
+ "capture-pane",
377
+ "-t",
378
+ session.sessionName,
379
+ "-p",
380
+ ]);
381
+ let output = "";
382
+ captureProcess.stdout.on("data", (data) => {
344
383
  output += data.toString();
345
384
  });
346
385
  await new Promise((resolve, reject) => {
347
- captureProcess.on('close', (code) => {
386
+ captureProcess.on("close", (code) => {
348
387
  if (code === 0)
349
388
  resolve(undefined);
350
389
  else
351
390
  reject(new Error(`Tmux exited with code ${code}`));
352
391
  });
353
- captureProcess.on('error', reject);
392
+ captureProcess.on("error", reject);
354
393
  });
355
394
  return { output };
356
395
  }
357
396
  catch (error) {
358
397
  return reply.code(500).send({
359
- error: 'Failed to capture output',
360
- details: error instanceof Error ? error.message : 'Unknown error'
398
+ error: "Failed to capture output",
399
+ details: error instanceof Error ? error.message : "Unknown error",
361
400
  });
362
401
  }
363
402
  });
364
403
  // Kill session
365
- fastify.delete('/:sessionId', async (request, reply) => {
404
+ fastify.delete("/:sessionId", async (request, reply) => {
366
405
  const { sessionId } = request.params;
367
406
  const startTime = Date.now();
368
407
  try {
369
408
  const session = fastify.db.getTmuxSession(sessionId);
370
409
  if (!session) {
371
410
  console.warn(`[Session Termination] Session not found: ${sessionId}`);
372
- return reply.code(404).send({ error: 'Session not found' });
411
+ return reply.code(404).send({ error: "Session not found" });
373
412
  }
374
413
  console.log(`[Session Termination] Starting termination process for session ${session.sessionName} (ID: ${sessionId})`);
375
414
  const cleanup = {
376
415
  ttydKilled: false,
377
416
  orphanedTtydCleaned: false,
378
417
  tmuxKilled: false,
379
- databaseUpdated: false
418
+ databaseUpdated: false,
380
419
  };
381
420
  // Enhanced TTYd process cleanup with verification
382
421
  if (session.ttydPid) {
383
422
  console.log(`[Session Termination] Killing TTYd process ${session.ttydPid} for session ${session.sessionName}`);
384
423
  try {
385
424
  // First try graceful termination
386
- process.kill(session.ttydPid, 'SIGTERM');
425
+ process.kill(session.ttydPid, "SIGTERM");
387
426
  // Wait a moment and verify process is killed
388
- await new Promise(resolve => setTimeout(resolve, 1000));
427
+ await new Promise((resolve) => setTimeout(resolve, 1000));
389
428
  try {
390
429
  process.kill(session.ttydPid, 0); // Check if process still exists
391
430
  console.warn(`[Session Termination] TTYd process ${session.ttydPid} still running after SIGTERM, using SIGKILL`);
392
- process.kill(session.ttydPid, 'SIGKILL');
431
+ process.kill(session.ttydPid, "SIGKILL");
393
432
  }
394
433
  catch (killError) {
395
434
  // Process doesn't exist anymore, which is what we want
@@ -415,17 +454,21 @@ export const tmuxRoutes = async (fastify) => {
415
454
  // Kill tmux session with verification
416
455
  console.log(`[Session Termination] Killing tmux session ${session.sessionName}`);
417
456
  try {
418
- const tmuxProcess = spawn('tmux', ['kill-session', '-t', session.sessionName]);
457
+ const tmuxProcess = spawn("tmux", [
458
+ "kill-session",
459
+ "-t",
460
+ session.sessionName,
461
+ ]);
419
462
  await new Promise((resolve, reject) => {
420
- let tmuxOutput = '';
421
- let tmuxError = '';
422
- tmuxProcess.stdout?.on('data', (data) => {
463
+ let tmuxOutput = "";
464
+ let tmuxError = "";
465
+ tmuxProcess.stdout?.on("data", (data) => {
423
466
  tmuxOutput += data.toString();
424
467
  });
425
- tmuxProcess.stderr?.on('data', (data) => {
468
+ tmuxProcess.stderr?.on("data", (data) => {
426
469
  tmuxError += data.toString();
427
470
  });
428
- tmuxProcess.on('close', (code) => {
471
+ tmuxProcess.on("close", (code) => {
429
472
  if (code === 0) {
430
473
  console.log(`[Session Termination] Tmux session ${session.sessionName} successfully killed`);
431
474
  resolve(undefined);
@@ -435,18 +478,22 @@ export const tmuxRoutes = async (fastify) => {
435
478
  reject(new Error(`Tmux exited with code ${code}. Error: ${tmuxError}`));
436
479
  }
437
480
  });
438
- tmuxProcess.on('error', (error) => {
481
+ tmuxProcess.on("error", (error) => {
439
482
  console.error(`[Session Termination] Tmux process error:`, error);
440
483
  reject(error);
441
484
  });
442
485
  });
443
486
  cleanup.tmuxKilled = true;
444
487
  // Verify tmux session is actually gone
445
- await new Promise(resolve => setTimeout(resolve, 500));
488
+ await new Promise((resolve) => setTimeout(resolve, 500));
446
489
  try {
447
- const verifyProcess = spawn('tmux', ['has-session', '-t', session.sessionName]);
490
+ const verifyProcess = spawn("tmux", [
491
+ "has-session",
492
+ "-t",
493
+ session.sessionName,
494
+ ]);
448
495
  await new Promise((resolve, reject) => {
449
- verifyProcess.on('close', (code) => {
496
+ verifyProcess.on("close", (code) => {
450
497
  if (code === 0) {
451
498
  console.warn(`[Session Termination] WARNING: Tmux session ${session.sessionName} still exists after kill command`);
452
499
  }
@@ -455,7 +502,7 @@ export const tmuxRoutes = async (fastify) => {
455
502
  }
456
503
  resolve(undefined);
457
504
  });
458
- verifyProcess.on('error', () => resolve(undefined));
505
+ verifyProcess.on("error", () => resolve(undefined));
459
506
  });
460
507
  }
461
508
  catch (verifyError) {
@@ -470,9 +517,9 @@ export const tmuxRoutes = async (fastify) => {
470
517
  console.log(`[Session Termination] Updating database status for session ${session.sessionName}`);
471
518
  try {
472
519
  fastify.db.updateTmuxSession(session.id, {
473
- status: 'terminated',
520
+ status: "terminated",
474
521
  ttydPort: undefined,
475
- ttydPid: undefined
522
+ ttydPid: undefined,
476
523
  });
477
524
  cleanup.databaseUpdated = true;
478
525
  console.log(`[Session Termination] Database updated for session ${session.sessionName}`);
@@ -482,7 +529,10 @@ export const tmuxRoutes = async (fastify) => {
482
529
  throw error;
483
530
  }
484
531
  // Emit session terminated event
485
- fastify.io.emit('session:terminated', { sessionId, sessionName: session.sessionName });
532
+ fastify.io.emit("session:terminated", {
533
+ sessionId,
534
+ sessionName: session.sessionName,
535
+ });
486
536
  // Final verification that everything is cleaned up
487
537
  const verification = await verifySessionTermination(sessionId, session.sessionName, session.ttydPid);
488
538
  const executionTime = Date.now() - startTime;
@@ -495,39 +545,44 @@ export const tmuxRoutes = async (fastify) => {
495
545
  cleanup,
496
546
  verification,
497
547
  executionTime,
498
- sessionName: session.sessionName
548
+ sessionName: session.sessionName,
499
549
  };
500
550
  }
501
551
  catch (error) {
502
552
  const executionTime = Date.now() - startTime;
503
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
553
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
504
554
  console.error(`[Session Termination] Failed to terminate session ${sessionId} after ${executionTime}ms:`, errorMessage);
505
555
  return reply.code(500).send({
506
- error: 'Failed to kill session',
556
+ error: "Failed to kill session",
507
557
  details: errorMessage,
508
- executionTime
558
+ executionTime,
509
559
  });
510
560
  }
511
561
  });
512
562
  // Rename session by ID
513
- fastify.put('/:sessionId/rename', async (request, reply) => {
563
+ fastify.put("/:sessionId/rename", async (request, reply) => {
514
564
  const { sessionId } = request.params;
515
565
  const { newName } = request.body;
516
566
  try {
517
567
  const session = fastify.db.getTmuxSession(sessionId);
518
568
  if (!session) {
519
- return reply.code(404).send({ error: 'Session not found' });
569
+ return reply.code(404).send({ error: "Session not found" });
520
570
  }
521
571
  // Rename tmux session using current sessionName
522
- const tmuxProcess = spawn('tmux', ['rename-session', '-t', session.sessionName, newName]);
572
+ const tmuxProcess = spawn("tmux", [
573
+ "rename-session",
574
+ "-t",
575
+ session.sessionName,
576
+ newName,
577
+ ]);
523
578
  await new Promise((resolve, reject) => {
524
- tmuxProcess.on('close', (code) => {
579
+ tmuxProcess.on("close", (code) => {
525
580
  if (code === 0)
526
581
  resolve(undefined);
527
582
  else
528
583
  reject(new Error(`Tmux exited with code ${code}`));
529
584
  });
530
- tmuxProcess.on('error', reject);
585
+ tmuxProcess.on("error", reject);
531
586
  });
532
587
  // Update database
533
588
  fastify.db.updateTmuxSession(session.id, { sessionName: newName });
@@ -535,49 +590,56 @@ export const tmuxRoutes = async (fastify) => {
535
590
  }
536
591
  catch (error) {
537
592
  return reply.code(500).send({
538
- error: 'Failed to rename session',
539
- details: error instanceof Error ? error.message : 'Unknown error'
593
+ error: "Failed to rename session",
594
+ details: error instanceof Error ? error.message : "Unknown error",
540
595
  });
541
596
  }
542
597
  });
543
598
  // Toggle mouse mode in tmux session
544
- fastify.post('/:sessionId/toggle-mouse', async (request, reply) => {
599
+ fastify.post("/:sessionId/toggle-mouse", async (request, reply) => {
545
600
  const { sessionId } = request.params;
546
601
  try {
547
602
  const session = fastify.db.getTmuxSession(sessionId);
548
603
  if (!session) {
549
- return reply.code(404).send({ error: 'Session not found' });
604
+ return reply.code(404).send({ error: "Session not found" });
550
605
  }
551
606
  // Toggle mouse mode
552
- const tmuxProcess = spawn('tmux', ['set-option', '-t', session.sessionName, 'mouse']);
607
+ const tmuxProcess = spawn("tmux", [
608
+ "set-option",
609
+ "-t",
610
+ session.sessionName,
611
+ "mouse",
612
+ ]);
553
613
  await new Promise((resolve, reject) => {
554
- tmuxProcess.on('close', (code) => {
614
+ tmuxProcess.on("close", (code) => {
555
615
  if (code === 0)
556
616
  resolve(undefined);
557
617
  else
558
618
  reject(new Error(`Tmux exited with code ${code}`));
559
619
  });
560
- tmuxProcess.on('error', reject);
620
+ tmuxProcess.on("error", reject);
561
621
  });
562
622
  return { success: true };
563
623
  }
564
624
  catch (error) {
565
625
  return reply.code(500).send({
566
- error: 'Failed to toggle mouse mode',
567
- details: error instanceof Error ? error.message : 'Unknown error'
626
+ error: "Failed to toggle mouse mode",
627
+ details: error instanceof Error ? error.message : "Unknown error",
568
628
  });
569
629
  }
570
630
  });
571
631
  // Get ttyd URL for session
572
- fastify.get('/:sessionId/ttyd', async (request, reply) => {
632
+ fastify.get("/:sessionId/ttyd", async (request, reply) => {
573
633
  const { sessionId } = request.params;
574
634
  try {
575
635
  const session = fastify.db.getTmuxSession(sessionId);
576
636
  if (!session) {
577
- return reply.code(404).send({ error: 'Session not found' });
637
+ return reply.code(404).send({ error: "Session not found" });
578
638
  }
579
639
  if (!session.ttydPort || !session.ttydPid) {
580
- return reply.code(404).send({ error: 'TTYd not running for this session' });
640
+ return reply
641
+ .code(404)
642
+ .send({ error: "TTYd not running for this session" });
581
643
  }
582
644
  // Check if process is still running
583
645
  try {
@@ -585,45 +647,50 @@ export const tmuxRoutes = async (fastify) => {
585
647
  return {
586
648
  port: session.ttydPort,
587
649
  url: `http://localhost:${session.ttydPort}`,
588
- pid: session.ttydPid
650
+ pid: session.ttydPid,
589
651
  };
590
652
  }
591
653
  catch {
592
654
  // Process is not running, clean up
593
655
  fastify.db.updateTmuxSession(session.id, {
594
656
  ttydPort: undefined,
595
- ttydPid: undefined
657
+ ttydPid: undefined,
596
658
  });
597
- return reply.code(404).send({ error: 'TTYd process is no longer running' });
659
+ return reply
660
+ .code(404)
661
+ .send({ error: "TTYd process is no longer running" });
598
662
  }
599
663
  }
600
664
  catch (error) {
601
665
  return reply.code(500).send({
602
- error: 'Failed to get ttyd URL',
603
- details: error instanceof Error ? error.message : 'Unknown error'
666
+ error: "Failed to get ttyd URL",
667
+ details: error instanceof Error ? error.message : "Unknown error",
604
668
  });
605
669
  }
606
670
  });
607
671
  // Start ttyd for browser terminal access
608
- fastify.post('/:sessionId/ttyd', async (request, reply) => {
672
+ fastify.post("/:sessionId/ttyd", async (request, reply) => {
609
673
  const { sessionId } = request.params;
610
674
  try {
611
675
  const session = fastify.db.getTmuxSession(sessionId);
612
676
  if (!session) {
613
- return reply.code(404).send({ error: 'Session not found' });
677
+ return reply.code(404).send({ error: "Session not found" });
614
678
  }
615
679
  // Check if ttyd is already running for this session
616
680
  if (session.ttydPort && session.ttydPid) {
617
681
  // Check if process is still running
618
682
  try {
619
683
  process.kill(session.ttydPid, 0);
620
- return { port: session.ttydPort, url: `http://localhost:${session.ttydPort}` };
684
+ return {
685
+ port: session.ttydPort,
686
+ url: `http://localhost:${session.ttydPort}`,
687
+ };
621
688
  }
622
689
  catch {
623
690
  // Process is not running, clean up database
624
691
  fastify.db.updateTmuxSession(session.id, {
625
692
  ttydPort: undefined,
626
- ttydPid: undefined
693
+ ttydPid: undefined,
627
694
  });
628
695
  }
629
696
  }
@@ -638,33 +705,43 @@ export const tmuxRoutes = async (fastify) => {
638
705
  try {
639
706
  port = await findAvailablePort(7700 + attempts, 7800);
640
707
  // Verify tmux session exists before starting TTYd
641
- const tmuxCheckProcess = spawn('tmux', ['has-session', '-t', session.sessionName]);
708
+ const tmuxCheckProcess = spawn("tmux", [
709
+ "has-session",
710
+ "-t",
711
+ session.sessionName,
712
+ ]);
642
713
  await new Promise((resolve, reject) => {
643
- tmuxCheckProcess.on('close', (code) => {
714
+ tmuxCheckProcess.on("close", (code) => {
644
715
  if (code === 0)
645
716
  resolve(undefined);
646
717
  else
647
718
  reject(new Error(`Tmux session ${session.sessionName} does not exist`));
648
719
  });
649
- tmuxCheckProcess.on('error', reject);
720
+ tmuxCheckProcess.on("error", reject);
650
721
  });
651
722
  // Start ttyd with proper error capturing and safer theme parameter
652
- ttydProcess = spawn('ttyd', [
653
- '-p', port.toString(),
654
- '-t', 'fontSize=14',
655
- '-t', 'theme={"background":"#1e1e1e","foreground":"#cccccc"}',
656
- '--writable',
657
- 'tmux', 'attach-session', '-t', session.sessionName
723
+ ttydProcess = spawn("ttyd", [
724
+ "-p",
725
+ port.toString(),
726
+ "-t",
727
+ "fontSize=14",
728
+ "-t",
729
+ 'theme={"background":"#1e1e1e","foreground":"#cccccc"}',
730
+ "--writable",
731
+ "tmux",
732
+ "attach-session",
733
+ "-t",
734
+ session.sessionName,
658
735
  ], {
659
736
  detached: true,
660
- stdio: ['ignore', 'pipe', 'pipe'],
737
+ stdio: ["ignore", "pipe", "pipe"],
661
738
  env: { ...process.env, PATH: process.env.PATH }, // Ensure proper environment
662
- cwd: process.cwd() // Ensure proper working directory
739
+ cwd: process.cwd(), // Ensure proper working directory
663
740
  });
664
741
  // Capture stderr for debugging
665
- let stderrData = '';
742
+ let stderrData = "";
666
743
  if (ttydProcess.stderr) {
667
- ttydProcess.stderr.on('data', (data) => {
744
+ ttydProcess.stderr.on("data", (data) => {
668
745
  stderrData += data.toString();
669
746
  });
670
747
  }
@@ -677,14 +754,14 @@ export const tmuxRoutes = async (fastify) => {
677
754
  resolve(true); // Assume success if no immediate error
678
755
  }
679
756
  }, 2000); // Increased timeout to 2 seconds
680
- ttydProcess.on('error', (err) => {
757
+ ttydProcess.on("error", (err) => {
681
758
  if (!resolved) {
682
759
  resolved = true;
683
760
  clearTimeout(timeout);
684
761
  reject(new Error(`TTYd process error: ${err.message}. Stderr: ${stderrData}`));
685
762
  }
686
763
  });
687
- ttydProcess.on('exit', (code) => {
764
+ ttydProcess.on("exit", (code) => {
688
765
  if (!resolved && code !== 0) {
689
766
  resolved = true;
690
767
  clearTimeout(timeout);
@@ -696,10 +773,10 @@ export const tmuxRoutes = async (fastify) => {
696
773
  // Update database with ttyd info
697
774
  fastify.db.updateTmuxSession(session.id, {
698
775
  ttydPort: port,
699
- ttydPid: ttydProcess.pid
776
+ ttydPid: ttydProcess.pid,
700
777
  });
701
778
  // Emit ttyd started event
702
- fastify.io.emit('session:ttyd-started', { sessionId, port });
779
+ fastify.io.emit("session:ttyd-started", { sessionId, port });
703
780
  // Build the proxy URL: the agent's single tunnel + /terminal/:sessionId/
704
781
  // This replaces per-session cloudflared tunnels — the agent proxies ttyd
705
782
  // through its own API at /terminal/:sessionId
@@ -707,7 +784,7 @@ export const tmuxRoutes = async (fastify) => {
707
784
  const terminalProxyUrl = agentTunnel
708
785
  ? `${agentTunnel}/terminal/${sessionId}/`
709
786
  : null;
710
- console.log(`[TTYd] Started on port ${port}, proxy URL: ${terminalProxyUrl || 'no tunnel (local only)'}`);
787
+ console.log(`[TTYd] Started on port ${port}, proxy URL: ${terminalProxyUrl || "no tunnel (local only)"}`);
711
788
  return {
712
789
  port,
713
790
  pid: ttydProcess.pid,
@@ -720,7 +797,7 @@ export const tmuxRoutes = async (fastify) => {
720
797
  attempts++;
721
798
  if (ttydProcess && ttydProcess.pid) {
722
799
  try {
723
- process.kill(ttydProcess.pid, 'SIGTERM');
800
+ process.kill(ttydProcess.pid, "SIGTERM");
724
801
  }
725
802
  catch {
726
803
  // Ignore kill errors
@@ -730,24 +807,24 @@ export const tmuxRoutes = async (fastify) => {
730
807
  throw error;
731
808
  }
732
809
  // Wait before retry
733
- await new Promise(resolve => setTimeout(resolve, 500));
810
+ await new Promise((resolve) => setTimeout(resolve, 500));
734
811
  }
735
812
  }
736
813
  }
737
814
  catch (error) {
738
815
  return reply.code(500).send({
739
- error: 'Failed to start ttyd',
740
- details: error instanceof Error ? error.message : 'Unknown error'
816
+ error: "Failed to start ttyd",
817
+ details: error instanceof Error ? error.message : "Unknown error",
741
818
  });
742
819
  }
743
820
  });
744
821
  // Verify session termination status
745
- fastify.get('/:sessionId/termination-status', async (request, reply) => {
822
+ fastify.get("/:sessionId/termination-status", async (request, reply) => {
746
823
  const { sessionId } = request.params;
747
824
  try {
748
825
  const session = fastify.db.getTmuxSession(sessionId);
749
826
  if (!session) {
750
- return reply.code(404).send({ error: 'Session not found' });
827
+ return reply.code(404).send({ error: "Session not found" });
751
828
  }
752
829
  const verification = await verifySessionTermination(sessionId, session.sessionName, session.ttydPid);
753
830
  return {
@@ -755,30 +832,32 @@ export const tmuxRoutes = async (fastify) => {
755
832
  sessionName: session.sessionName,
756
833
  databaseStatus: session.status,
757
834
  verification,
758
- isFullyTerminated: verification.allCleaned && session.status === 'terminated'
835
+ isFullyTerminated: verification.allCleaned && session.status === "terminated",
759
836
  };
760
837
  }
761
838
  catch (error) {
762
839
  return reply.code(500).send({
763
- error: 'Failed to verify session termination',
764
- details: error instanceof Error ? error.message : 'Unknown error'
840
+ error: "Failed to verify session termination",
841
+ details: error instanceof Error ? error.message : "Unknown error",
765
842
  });
766
843
  }
767
844
  });
768
845
  // Stop ttyd for session
769
- fastify.post('/:sessionId/ttyd/stop', async (request, reply) => {
846
+ fastify.post("/:sessionId/ttyd/stop", async (request, reply) => {
770
847
  const { sessionId } = request.params;
771
848
  try {
772
849
  const session = fastify.db.getTmuxSession(sessionId);
773
850
  if (!session) {
774
- return reply.code(404).send({ error: 'Session not found' });
851
+ return reply.code(404).send({ error: "Session not found" });
775
852
  }
776
853
  if (!session.ttydPid) {
777
- return reply.code(400).send({ error: 'No ttyd process running for this session' });
854
+ return reply
855
+ .code(400)
856
+ .send({ error: "No ttyd process running for this session" });
778
857
  }
779
858
  // Kill ttyd process
780
859
  try {
781
- process.kill(session.ttydPid, 'SIGTERM');
860
+ process.kill(session.ttydPid, "SIGTERM");
782
861
  }
783
862
  catch {
784
863
  // Process might already be dead
@@ -786,49 +865,52 @@ export const tmuxRoutes = async (fastify) => {
786
865
  // Update database
787
866
  fastify.db.updateTmuxSession(session.id, {
788
867
  ttydPort: undefined,
789
- ttydPid: undefined
868
+ ttydPid: undefined,
790
869
  });
791
870
  // Emit ttyd stopped event
792
- fastify.io.emit('session:ttyd-stopped', { sessionId });
871
+ fastify.io.emit("session:ttyd-stopped", { sessionId });
793
872
  return { success: true };
794
873
  }
795
874
  catch (error) {
796
875
  return reply.code(500).send({
797
- error: 'Failed to stop ttyd',
798
- details: error instanceof Error ? error.message : 'Unknown error'
876
+ error: "Failed to stop ttyd",
877
+ details: error instanceof Error ? error.message : "Unknown error",
799
878
  });
800
879
  }
801
880
  });
802
881
  };
803
882
  // Helper function to clean up orphaned TTYd processes
804
883
  async function cleanupOrphanedTtydProcesses(sessionName) {
805
- const { spawn } = await import('child_process');
884
+ const { spawn } = await import("child_process");
806
885
  try {
807
886
  console.log(`[TTYd Cleanup] Searching for orphaned TTYd processes for session ${sessionName}`);
808
887
  // Find TTYd processes for this session
809
- const findProcess = spawn('pgrep', ['-f', `ttyd.*${sessionName}`]);
810
- let processIds = '';
811
- findProcess.stdout.on('data', (data) => {
888
+ const findProcess = spawn("pgrep", ["-f", `ttyd.*${sessionName}`]);
889
+ let processIds = "";
890
+ findProcess.stdout.on("data", (data) => {
812
891
  processIds += data.toString();
813
892
  });
814
893
  await new Promise((resolve) => {
815
- findProcess.on('close', resolve);
894
+ findProcess.on("close", resolve);
816
895
  });
817
896
  if (processIds.trim()) {
818
- const pids = processIds.trim().split('\n').filter(pid => pid);
897
+ const pids = processIds
898
+ .trim()
899
+ .split("\n")
900
+ .filter((pid) => pid);
819
901
  console.log(`[TTYd Cleanup] Found ${pids.length} orphaned TTYd processes for session ${sessionName}, cleaning up...`);
820
902
  for (const pid of pids) {
821
903
  try {
822
904
  const pidNum = parseInt(pid);
823
905
  console.log(`[TTYd Cleanup] Killing orphaned TTYd process ${pidNum}`);
824
906
  // First try graceful termination
825
- process.kill(pidNum, 'SIGTERM');
907
+ process.kill(pidNum, "SIGTERM");
826
908
  // Wait a moment and verify process is killed
827
- await new Promise(resolve => setTimeout(resolve, 1000));
909
+ await new Promise((resolve) => setTimeout(resolve, 1000));
828
910
  try {
829
911
  process.kill(pidNum, 0); // Check if process still exists
830
912
  console.warn(`[TTYd Cleanup] TTYd process ${pidNum} still running after SIGTERM, using SIGKILL`);
831
- process.kill(pidNum, 'SIGKILL');
913
+ process.kill(pidNum, "SIGKILL");
832
914
  }
833
915
  catch (killError) {
834
916
  // Process doesn't exist anymore, which is what we want
@@ -840,20 +922,23 @@ async function cleanupOrphanedTtydProcesses(sessionName) {
840
922
  }
841
923
  }
842
924
  // Wait a moment for processes to terminate
843
- await new Promise(resolve => setTimeout(resolve, 500));
925
+ await new Promise((resolve) => setTimeout(resolve, 500));
844
926
  // Verify all processes are gone
845
927
  try {
846
- const verifyProcess = spawn('pgrep', ['-f', `ttyd.*${sessionName}`]);
847
- let remainingProcesses = '';
848
- verifyProcess.stdout.on('data', (data) => {
928
+ const verifyProcess = spawn("pgrep", ["-f", `ttyd.*${sessionName}`]);
929
+ let remainingProcesses = "";
930
+ verifyProcess.stdout.on("data", (data) => {
849
931
  remainingProcesses += data.toString();
850
932
  });
851
933
  await new Promise((resolve) => {
852
- verifyProcess.on('close', resolve);
934
+ verifyProcess.on("close", resolve);
853
935
  });
854
936
  if (remainingProcesses.trim()) {
855
- const remainingPids = remainingProcesses.trim().split('\n').filter(pid => pid);
856
- console.warn(`[TTYd Cleanup] WARNING: ${remainingPids.length} TTYd processes still running after cleanup: ${remainingPids.join(', ')}`);
937
+ const remainingPids = remainingProcesses
938
+ .trim()
939
+ .split("\n")
940
+ .filter((pid) => pid);
941
+ console.warn(`[TTYd Cleanup] WARNING: ${remainingPids.length} TTYd processes still running after cleanup: ${remainingPids.join(", ")}`);
857
942
  }
858
943
  else {
859
944
  console.log(`[TTYd Cleanup] All orphaned TTYd processes for session ${sessionName} successfully cleaned up`);
@@ -884,14 +969,14 @@ async function isProcessRunning(pid) {
884
969
  }
885
970
  // Helper function to check if a tmux session exists
886
971
  async function doesTmuxSessionExist(sessionName) {
887
- const { spawn } = await import('child_process');
972
+ const { spawn } = await import("child_process");
888
973
  try {
889
- const checkProcess = spawn('tmux', ['has-session', '-t', sessionName]);
974
+ const checkProcess = spawn("tmux", ["has-session", "-t", sessionName]);
890
975
  return new Promise((resolve) => {
891
- checkProcess.on('close', (code) => {
976
+ checkProcess.on("close", (code) => {
892
977
  resolve(code === 0);
893
978
  });
894
- checkProcess.on('error', () => {
979
+ checkProcess.on("error", () => {
895
980
  resolve(false);
896
981
  });
897
982
  });
@@ -902,18 +987,22 @@ async function doesTmuxSessionExist(sessionName) {
902
987
  }
903
988
  // Helper function to get all running TTYd processes for a session
904
989
  async function getRunningTtydProcesses(sessionName) {
905
- const { spawn } = await import('child_process');
990
+ const { spawn } = await import("child_process");
906
991
  try {
907
- const findProcess = spawn('pgrep', ['-f', `ttyd.*${sessionName}`]);
908
- let processIds = '';
909
- findProcess.stdout.on('data', (data) => {
992
+ const findProcess = spawn("pgrep", ["-f", `ttyd.*${sessionName}`]);
993
+ let processIds = "";
994
+ findProcess.stdout.on("data", (data) => {
910
995
  processIds += data.toString();
911
996
  });
912
997
  await new Promise((resolve) => {
913
- findProcess.on('close', resolve);
998
+ findProcess.on("close", resolve);
914
999
  });
915
1000
  if (processIds.trim()) {
916
- return processIds.trim().split('\n').filter(pid => pid).map(pid => parseInt(pid));
1001
+ return processIds
1002
+ .trim()
1003
+ .split("\n")
1004
+ .filter((pid) => pid)
1005
+ .map((pid) => parseInt(pid));
917
1006
  }
918
1007
  return [];
919
1008
  }
@@ -933,32 +1022,32 @@ async function verifySessionTermination(sessionId, sessionName, ttydPid) {
933
1022
  tmuxExists,
934
1023
  ttydProcesses,
935
1024
  targetTtydRunning,
936
- allCleaned
1025
+ allCleaned,
937
1026
  });
938
1027
  return {
939
1028
  tmuxExists,
940
1029
  ttydProcesses,
941
1030
  targetTtydRunning,
942
- allCleaned
1031
+ allCleaned,
943
1032
  };
944
1033
  }
945
1034
  // Helper function to find available port with better detection
946
1035
  async function findAvailablePort(startPort, endPort) {
947
- const net = await import('net');
948
- const { spawn } = await import('child_process');
1036
+ const net = await import("net");
1037
+ const { spawn } = await import("child_process");
949
1038
  for (let port = startPort; port <= endPort; port++) {
950
1039
  // First check if the port is bound using netstat/lsof
951
1040
  const isPortOccupied = await new Promise((resolve) => {
952
- const checkProcess = spawn('lsof', ['-i', `:${port}`, '-t']);
1041
+ const checkProcess = spawn("lsof", ["-i", `:${port}`, "-t"]);
953
1042
  let hasOutput = false;
954
- checkProcess.stdout.on('data', (_data) => {
1043
+ checkProcess.stdout.on("data", (_data) => {
955
1044
  hasOutput = true;
956
1045
  });
957
- checkProcess.on('close', (code) => {
1046
+ checkProcess.on("close", (code) => {
958
1047
  // If lsof found processes using the port, it's occupied
959
1048
  resolve(hasOutput);
960
1049
  });
961
- checkProcess.on('error', () => {
1050
+ checkProcess.on("error", () => {
962
1051
  // If lsof fails, fall back to basic check
963
1052
  resolve(false);
964
1053
  });
@@ -974,12 +1063,12 @@ async function findAvailablePort(startPort, endPort) {
974
1063
  // Double-check by trying to bind to the port
975
1064
  const available = await new Promise((resolve) => {
976
1065
  const server = net.createServer();
977
- server.once('error', () => resolve(false));
978
- server.once('listening', () => {
1066
+ server.once("error", () => resolve(false));
1067
+ server.once("listening", () => {
979
1068
  server.close();
980
1069
  resolve(true);
981
1070
  });
982
- server.listen(port, 'localhost');
1071
+ server.listen(port, "localhost");
983
1072
  });
984
1073
  if (available) {
985
1074
  return port;
@@ -989,26 +1078,30 @@ async function findAvailablePort(startPort, endPort) {
989
1078
  }
990
1079
  // Get all tmux sessions on the system
991
1080
  async function getSystemTmuxSessions() {
992
- const { spawn } = await import('child_process');
1081
+ const { spawn } = await import("child_process");
993
1082
  try {
994
- console.log('[System Sessions] Discovering all tmux sessions on system');
995
- const listProcess = spawn('tmux', ['list-sessions', '-F', '#{session_name}|#{session_attached}|#{session_windows}|#{session_created}|#{session_activity}|#{session_id}']);
996
- let output = '';
997
- let stderr = '';
998
- listProcess.stdout.on('data', (data) => {
1083
+ console.log("[System Sessions] Discovering all tmux sessions on system");
1084
+ const listProcess = spawn("tmux", [
1085
+ "list-sessions",
1086
+ "-F",
1087
+ "#{session_name}|#{session_attached}|#{session_windows}|#{session_created}|#{session_activity}|#{session_id}",
1088
+ ]);
1089
+ let output = "";
1090
+ let stderr = "";
1091
+ listProcess.stdout.on("data", (data) => {
999
1092
  output += data.toString();
1000
1093
  });
1001
- listProcess.stderr.on('data', (data) => {
1094
+ listProcess.stderr.on("data", (data) => {
1002
1095
  stderr += data.toString();
1003
1096
  });
1004
1097
  await new Promise((resolve, reject) => {
1005
- listProcess.on('close', (code) => {
1098
+ listProcess.on("close", (code) => {
1006
1099
  if (code === 0) {
1007
1100
  resolve(undefined);
1008
1101
  }
1009
1102
  else {
1010
1103
  // If no sessions exist, tmux returns exit code 1
1011
- if (code === 1 && stderr.includes('no server running')) {
1104
+ if (code === 1 && stderr.includes("no server running")) {
1012
1105
  resolve(undefined);
1013
1106
  }
1014
1107
  else {
@@ -1016,17 +1109,20 @@ async function getSystemTmuxSessions() {
1016
1109
  }
1017
1110
  }
1018
1111
  });
1019
- listProcess.on('error', reject);
1112
+ listProcess.on("error", reject);
1020
1113
  });
1021
1114
  if (!output.trim()) {
1022
- console.log('[System Sessions] No tmux sessions found on system');
1115
+ console.log("[System Sessions] No tmux sessions found on system");
1023
1116
  return [];
1024
1117
  }
1025
- const sessions = output.trim().split('\n').map(line => {
1026
- const [name, attached, windows, created, lastActivity, sessionId] = line.split('|');
1118
+ const sessions = output
1119
+ .trim()
1120
+ .split("\n")
1121
+ .map((line) => {
1122
+ const [name, attached, windows, created, lastActivity, sessionId] = line.split("|");
1027
1123
  return {
1028
1124
  name,
1029
- attached: attached === '1',
1125
+ attached: attached === "1",
1030
1126
  windows: parseInt(windows) || 0,
1031
1127
  created: new Date(parseInt(created) * 1000).toISOString(),
1032
1128
  lastActivity: new Date(parseInt(lastActivity) * 1000).toISOString(),
@@ -1037,45 +1133,45 @@ async function getSystemTmuxSessions() {
1037
1133
  return sessions;
1038
1134
  }
1039
1135
  catch (error) {
1040
- console.error('[System Sessions] Error getting system tmux sessions:', error);
1136
+ console.error("[System Sessions] Error getting system tmux sessions:", error);
1041
1137
  // Return empty array instead of throwing - no sessions is not an error
1042
1138
  return [];
1043
1139
  }
1044
1140
  }
1045
1141
  // Get all ttyd processes on the system
1046
1142
  async function getSystemTtydProcesses() {
1047
- const { spawn } = await import('child_process');
1143
+ const { spawn } = await import("child_process");
1048
1144
  try {
1049
- console.log('[System Processes] Discovering all ttyd processes on system');
1145
+ console.log("[System Processes] Discovering all ttyd processes on system");
1050
1146
  // Use ps to find ttyd processes with detailed information
1051
- const psProcess = spawn('ps', ['aux']);
1052
- let output = '';
1053
- psProcess.stdout.on('data', (data) => {
1147
+ const psProcess = spawn("ps", ["aux"]);
1148
+ let output = "";
1149
+ psProcess.stdout.on("data", (data) => {
1054
1150
  output += data.toString();
1055
1151
  });
1056
1152
  await new Promise((resolve, reject) => {
1057
- psProcess.on('close', (code) => {
1153
+ psProcess.on("close", (code) => {
1058
1154
  if (code === 0)
1059
1155
  resolve(undefined);
1060
1156
  else
1061
1157
  reject(new Error(`ps command failed with code ${code}`));
1062
1158
  });
1063
- psProcess.on('error', reject);
1159
+ psProcess.on("error", reject);
1064
1160
  });
1065
1161
  // Parse ps output to find ttyd processes
1066
- const lines = output.split('\n');
1162
+ const lines = output.split("\n");
1067
1163
  const ttydProcesses = [];
1068
1164
  for (const line of lines) {
1069
- if (line.includes('ttyd') && !line.includes('grep')) {
1165
+ if (line.includes("ttyd") && !line.includes("grep")) {
1070
1166
  const parts = line.trim().split(/\s+/);
1071
1167
  if (parts.length >= 11) {
1072
1168
  const user = parts[0];
1073
1169
  const pid = parseInt(parts[1]);
1074
1170
  const startTime = parts[8];
1075
- const command = parts.slice(10).join(' ');
1171
+ const command = parts.slice(10).join(" ");
1076
1172
  // Extract port from command line
1077
1173
  let port = 0;
1078
- let sessionName = '';
1174
+ let sessionName = "";
1079
1175
  const portMatch = command.match(/-p\s+(\d+)/);
1080
1176
  if (portMatch) {
1081
1177
  port = parseInt(portMatch[1]);
@@ -1100,25 +1196,25 @@ async function getSystemTtydProcesses() {
1100
1196
  return ttydProcesses;
1101
1197
  }
1102
1198
  catch (error) {
1103
- console.error('[System Processes] Error getting system ttyd processes:', error);
1199
+ console.error("[System Processes] Error getting system ttyd processes:", error);
1104
1200
  return [];
1105
1201
  }
1106
1202
  }
1107
1203
  // Kill multiple tmux sessions
1108
1204
  async function killSystemTmuxSessions(sessionNames, force = false) {
1109
- const { spawn } = await import('child_process');
1205
+ const { spawn } = await import("child_process");
1110
1206
  const results = [];
1111
1207
  console.log(`[System Sessions] Killing ${sessionNames.length} tmux sessions, force: ${force}`);
1112
1208
  for (const sessionName of sessionNames) {
1113
1209
  try {
1114
1210
  console.log(`[System Sessions] Killing tmux session: ${sessionName}`);
1115
- const killProcess = spawn('tmux', ['kill-session', '-t', sessionName]);
1211
+ const killProcess = spawn("tmux", ["kill-session", "-t", sessionName]);
1116
1212
  await new Promise((resolve, reject) => {
1117
- let stderr = '';
1118
- killProcess.stderr?.on('data', (data) => {
1213
+ let stderr = "";
1214
+ killProcess.stderr?.on("data", (data) => {
1119
1215
  stderr += data.toString();
1120
1216
  });
1121
- killProcess.on('close', (code) => {
1217
+ killProcess.on("close", (code) => {
1122
1218
  if (code === 0) {
1123
1219
  resolve(undefined);
1124
1220
  }
@@ -1126,7 +1222,7 @@ async function killSystemTmuxSessions(sessionNames, force = false) {
1126
1222
  reject(new Error(`tmux kill-session failed with code ${code}: ${stderr}`));
1127
1223
  }
1128
1224
  });
1129
- killProcess.on('error', reject);
1225
+ killProcess.on("error", reject);
1130
1226
  });
1131
1227
  results.push({
1132
1228
  target: sessionName,
@@ -1135,7 +1231,7 @@ async function killSystemTmuxSessions(sessionNames, force = false) {
1135
1231
  console.log(`[System Sessions] Successfully killed tmux session: ${sessionName}`);
1136
1232
  }
1137
1233
  catch (error) {
1138
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1234
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1139
1235
  console.error(`[System Sessions] Failed to kill tmux session ${sessionName}:`, errorMessage);
1140
1236
  results.push({
1141
1237
  target: sessionName,
@@ -1153,15 +1249,15 @@ async function killSystemTtydProcesses(pids, force = false) {
1153
1249
  for (const pid of pids) {
1154
1250
  try {
1155
1251
  console.log(`[System Processes] Killing ttyd process: ${pid}`);
1156
- const signal = force ? 'SIGKILL' : 'SIGTERM';
1252
+ const signal = force ? "SIGKILL" : "SIGTERM";
1157
1253
  process.kill(pid, signal);
1158
1254
  // Wait a moment and verify process is killed
1159
- await new Promise(resolve => setTimeout(resolve, 1000));
1255
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1160
1256
  try {
1161
1257
  process.kill(pid, 0); // Check if process still exists
1162
1258
  if (!force) {
1163
1259
  console.warn(`[System Processes] Process ${pid} still running after SIGTERM, using SIGKILL`);
1164
- process.kill(pid, 'SIGKILL');
1260
+ process.kill(pid, "SIGKILL");
1165
1261
  }
1166
1262
  else {
1167
1263
  throw new Error(`Process ${pid} still running after SIGKILL`);
@@ -1177,7 +1273,7 @@ async function killSystemTtydProcesses(pids, force = false) {
1177
1273
  });
1178
1274
  }
1179
1275
  catch (error) {
1180
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1276
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
1181
1277
  console.error(`[System Processes] Failed to kill ttyd process ${pid}:`, errorMessage);
1182
1278
  results.push({
1183
1279
  target: pid.toString(),