ttyd-mux 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -1
- package/dist/caddy/client.d.ts +3 -55
- package/dist/caddy/client.d.ts.map +1 -1
- package/dist/caddy/client.js +0 -73
- package/dist/caddy/client.js.map +1 -1
- package/dist/caddy/route-builder.d.ts +49 -0
- package/dist/caddy/route-builder.d.ts.map +1 -0
- package/dist/caddy/route-builder.js +175 -0
- package/dist/caddy/route-builder.js.map +1 -0
- package/dist/caddy/types.d.ts +27 -0
- package/dist/caddy/types.d.ts.map +1 -0
- package/dist/caddy/types.js +3 -0
- package/dist/caddy/types.js.map +1 -0
- package/dist/client/api-client.d.ts +26 -0
- package/dist/client/api-client.d.ts.map +1 -0
- package/dist/client/api-client.js +62 -0
- package/dist/client/api-client.js.map +1 -0
- package/dist/client/daemon-client.d.ts +48 -0
- package/dist/client/daemon-client.d.ts.map +1 -0
- package/dist/client/daemon-client.js +205 -0
- package/dist/client/daemon-client.js.map +1 -0
- package/dist/client/index.d.ts +2 -10
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -136
- package/dist/client/index.js.map +1 -1
- package/dist/commands/attach.js +3 -4
- package/dist/commands/attach.js.map +1 -1
- package/dist/commands/caddy.d.ts +2 -1
- package/dist/commands/caddy.d.ts.map +1 -1
- package/dist/commands/caddy.js +227 -75
- package/dist/commands/caddy.js.map +1 -1
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/deploy.d.ts +7 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +100 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/doctor.d.ts +8 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +180 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +11 -0
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/reload.d.ts +14 -0
- package/dist/commands/reload.d.ts.map +1 -0
- package/dist/commands/reload.js +50 -0
- package/dist/commands/reload.js.map +1 -0
- package/dist/commands/shutdown.d.ts +2 -1
- package/dist/commands/shutdown.d.ts.map +1 -1
- package/dist/commands/shutdown.js +8 -2
- package/dist/commands/shutdown.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +16 -3
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/stop.js.map +1 -1
- package/dist/commands/up.js.map +1 -1
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +9 -2
- package/dist/config/config.js.map +1 -1
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +6 -3
- package/dist/config/index.js.map +1 -1
- package/dist/config/state-store.d.ts +27 -0
- package/dist/config/state-store.d.ts.map +1 -0
- package/dist/config/state-store.js +55 -0
- package/dist/config/state-store.js.map +1 -0
- package/dist/config/state.d.ts +6 -0
- package/dist/config/state.d.ts.map +1 -1
- package/dist/config/state.js +49 -14
- package/dist/config/state.js.map +1 -1
- package/dist/config/types.d.ts +35 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +23 -1
- package/dist/config/types.js.map +1 -1
- package/dist/daemon/api-handler.d.ts +5 -0
- package/dist/daemon/api-handler.d.ts.map +1 -0
- package/dist/daemon/api-handler.js +97 -0
- package/dist/daemon/api-handler.js.map +1 -0
- package/dist/daemon/config-manager.d.ts +43 -0
- package/dist/daemon/config-manager.d.ts.map +1 -0
- package/dist/daemon/config-manager.js +154 -0
- package/dist/daemon/config-manager.js.map +1 -0
- package/dist/daemon/http-proxy.d.ts +27 -0
- package/dist/daemon/http-proxy.d.ts.map +1 -0
- package/dist/daemon/http-proxy.js +110 -0
- package/dist/daemon/http-proxy.js.map +1 -0
- package/dist/daemon/ime-helper.d.ts +1 -1
- package/dist/daemon/ime-helper.d.ts.map +1 -1
- package/dist/daemon/ime-helper.js +284 -10
- package/dist/daemon/ime-helper.js.map +1 -1
- package/dist/daemon/index.d.ts.map +1 -1
- package/dist/daemon/index.js +134 -29
- package/dist/daemon/index.js.map +1 -1
- package/dist/daemon/portal-utils.d.ts +20 -0
- package/dist/daemon/portal-utils.d.ts.map +1 -0
- package/dist/daemon/portal-utils.js +109 -0
- package/dist/daemon/portal-utils.js.map +1 -0
- package/dist/daemon/portal.d.ts.map +1 -1
- package/dist/daemon/portal.js +20 -77
- package/dist/daemon/portal.js.map +1 -1
- package/dist/daemon/pwa.d.ts +52 -0
- package/dist/daemon/pwa.d.ts.map +1 -0
- package/dist/daemon/pwa.js +229 -0
- package/dist/daemon/pwa.js.map +1 -0
- package/dist/daemon/router.d.ts +15 -0
- package/dist/daemon/router.d.ts.map +1 -0
- package/dist/daemon/router.js +164 -0
- package/dist/daemon/router.js.map +1 -0
- package/dist/daemon/server.d.ts +15 -3
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +23 -271
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/session-manager.d.ts +44 -10
- package/dist/daemon/session-manager.d.ts.map +1 -1
- package/dist/daemon/session-manager.js +125 -49
- package/dist/daemon/session-manager.js.map +1 -1
- package/dist/daemon/session-resolver.d.ts +1 -1
- package/dist/daemon/session-resolver.d.ts.map +1 -1
- package/dist/daemon/session-resolver.js.map +1 -1
- package/dist/daemon/toolbar/config.d.ts +13 -0
- package/dist/daemon/toolbar/config.d.ts.map +1 -0
- package/dist/daemon/toolbar/config.js +13 -0
- package/dist/daemon/toolbar/config.js.map +1 -0
- package/dist/daemon/toolbar/index.d.ts +43 -0
- package/dist/daemon/toolbar/index.d.ts.map +1 -0
- package/dist/daemon/toolbar/index.js +835 -0
- package/dist/daemon/toolbar/index.js.map +1 -0
- package/dist/daemon/toolbar/styles.d.ts +5 -0
- package/dist/daemon/toolbar/styles.d.ts.map +1 -0
- package/dist/daemon/toolbar/styles.js +278 -0
- package/dist/daemon/toolbar/styles.js.map +1 -0
- package/dist/daemon/toolbar/template.d.ts +6 -0
- package/dist/daemon/toolbar/template.d.ts.map +1 -0
- package/dist/daemon/toolbar/template.js +45 -0
- package/dist/daemon/toolbar/template.js.map +1 -0
- package/dist/daemon/ws-proxy.d.ts +17 -0
- package/dist/daemon/ws-proxy.d.ts.map +1 -0
- package/dist/daemon/ws-proxy.js +95 -0
- package/dist/daemon/ws-proxy.js.map +1 -0
- package/dist/deploy/caddyfile.d.ts +8 -0
- package/dist/deploy/caddyfile.d.ts.map +1 -0
- package/dist/deploy/caddyfile.js +62 -0
- package/dist/deploy/caddyfile.js.map +1 -0
- package/dist/deploy/deploy-script.d.ts +8 -0
- package/dist/deploy/deploy-script.d.ts.map +1 -0
- package/dist/deploy/deploy-script.js +72 -0
- package/dist/deploy/deploy-script.js.map +1 -0
- package/dist/deploy/static-portal.d.ts +3 -0
- package/dist/deploy/static-portal.d.ts.map +1 -0
- package/dist/deploy/static-portal.js +59 -0
- package/dist/deploy/static-portal.js.map +1 -0
- package/dist/index.js +38 -9
- package/dist/index.js.map +1 -1
- package/dist/test-setup.d.ts +19 -0
- package/dist/test-setup.d.ts.map +1 -0
- package/dist/test-setup.js +33 -0
- package/dist/test-setup.js.map +1 -0
- package/dist/tmux.d.ts +28 -1
- package/dist/tmux.d.ts.map +1 -1
- package/dist/tmux.js +37 -32
- package/dist/tmux.js.map +1 -1
- package/dist/ui.d.ts +2 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +16 -9
- package/dist/ui.js.map +1 -1
- package/dist/utils/errors.d.ts +4 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +9 -1
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +53 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/process-runner.d.ts +50 -0
- package/dist/utils/process-runner.d.ts.map +1 -0
- package/dist/utils/process-runner.js +73 -0
- package/dist/utils/process-runner.js.map +1 -0
- package/dist/utils/socket-client.d.ts +24 -0
- package/dist/utils/socket-client.d.ts.map +1 -0
- package/dist/utils/socket-client.js +30 -0
- package/dist/utils/socket-client.js.map +1 -0
- package/dist/utils/tmux-client.d.ts +57 -0
- package/dist/utils/tmux-client.d.ts.map +1 -0
- package/dist/utils/tmux-client.js +117 -0
- package/dist/utils/tmux-client.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -0
- package/package.json +6 -2
- package/dist/daemon/proxy.d.ts +0 -7
- package/dist/daemon/proxy.d.ts.map +0 -1
- package/dist/daemon/proxy.js +0 -17
- package/dist/daemon/proxy.js.map +0 -1
package/dist/daemon/server.js
CHANGED
|
@@ -1,281 +1,33 @@
|
|
|
1
1
|
import { createServer } from 'node:http';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
// Create proxy server for HTTP only
|
|
12
|
-
const proxy = httpProxy.createProxyServer({
|
|
13
|
-
changeOrigin: true,
|
|
14
|
-
xfwd: true
|
|
15
|
-
});
|
|
16
|
-
// Handle proxy errors
|
|
17
|
-
proxy.on('error', (err, _req, res) => {
|
|
18
|
-
console.error('Proxy error:', err.message);
|
|
19
|
-
if (res && 'writeHead' in res && typeof res.writeHead === 'function') {
|
|
20
|
-
const httpRes = res;
|
|
21
|
-
if (!httpRes.headersSent) {
|
|
22
|
-
httpRes.writeHead(502, { 'Content-Type': 'text/plain' });
|
|
23
|
-
httpRes.end('Bad Gateway');
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
// Handle selfHandleResponse for HTML injection
|
|
28
|
-
proxy.on('proxyRes', (proxyRes, req, res) => {
|
|
29
|
-
const httpRes = res;
|
|
30
|
-
// Check if this is a self-handled HTML response
|
|
31
|
-
const contentType = proxyRes.headers['content-type'] ?? '';
|
|
32
|
-
if (!contentType.includes('text/html')) {
|
|
33
|
-
// Not HTML, just pipe through
|
|
34
|
-
httpRes.writeHead(proxyRes.statusCode ?? 200, proxyRes.headers);
|
|
35
|
-
proxyRes.pipe(httpRes);
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// Check if client supports gzip (stored in custom header before deletion)
|
|
39
|
-
const acceptEncoding = req.originalAcceptEncoding ?? '';
|
|
40
|
-
const supportsGzip = acceptEncoding.includes('gzip');
|
|
41
|
-
// Collect HTML body and inject IME helper
|
|
42
|
-
const chunks = [];
|
|
43
|
-
proxyRes.on('data', (chunk) => chunks.push(chunk));
|
|
44
|
-
proxyRes.on('end', () => {
|
|
45
|
-
const originalHtml = Buffer.concat(chunks).toString('utf-8');
|
|
46
|
-
const modifiedHtml = injectImeHelper(originalHtml);
|
|
47
|
-
// Update headers
|
|
48
|
-
const headers = { ...proxyRes.headers };
|
|
49
|
-
delete headers['content-encoding'];
|
|
50
|
-
if (supportsGzip) {
|
|
51
|
-
// Compress with gzip
|
|
52
|
-
const compressed = gzipSync(modifiedHtml);
|
|
53
|
-
headers['content-encoding'] = 'gzip';
|
|
54
|
-
headers['content-length'] = String(compressed.length);
|
|
55
|
-
httpRes.writeHead(proxyRes.statusCode ?? 200, headers);
|
|
56
|
-
httpRes.end(compressed);
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
// Send uncompressed
|
|
60
|
-
headers['content-length'] = String(Buffer.byteLength(modifiedHtml));
|
|
61
|
-
httpRes.writeHead(proxyRes.statusCode ?? 200, headers);
|
|
62
|
-
httpRes.end(modifiedHtml);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
export function findSessionForPath(config, path) {
|
|
67
|
-
const sessions = listSessions();
|
|
68
|
-
const basePath = normalizeBasePath(config.base_path);
|
|
69
|
-
for (const session of sessions) {
|
|
70
|
-
const sessionFullPath = `${basePath}${session.path}`;
|
|
71
|
-
if (path.startsWith(`${sessionFullPath}/`) || path === sessionFullPath) {
|
|
72
|
-
return session;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
function sendJson(res, status, data) {
|
|
78
|
-
const body = generateJsonResponse(data);
|
|
79
|
-
res.writeHead(status, {
|
|
80
|
-
'Content-Type': 'application/json',
|
|
81
|
-
'Content-Length': Buffer.byteLength(body)
|
|
82
|
-
});
|
|
83
|
-
res.end(body);
|
|
84
|
-
}
|
|
85
|
-
function handleApiRequest(config, req, res) {
|
|
86
|
-
const basePath = normalizeBasePath(config.base_path);
|
|
87
|
-
const url = req.url ?? '/';
|
|
88
|
-
const path = url.slice(basePath.length);
|
|
89
|
-
const method = req.method ?? 'GET';
|
|
90
|
-
// GET /api/status
|
|
91
|
-
if (path === '/api/status' && method === 'GET') {
|
|
92
|
-
const daemon = getDaemonState();
|
|
93
|
-
const sessions = listSessions().map((s) => ({
|
|
94
|
-
...s,
|
|
95
|
-
fullPath: getFullPath(config, s.path)
|
|
96
|
-
}));
|
|
97
|
-
sendJson(res, 200, { daemon, sessions });
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
// GET /api/sessions
|
|
101
|
-
if (path === '/api/sessions' && method === 'GET') {
|
|
102
|
-
const sessions = listSessions().map((s) => ({
|
|
103
|
-
...s,
|
|
104
|
-
fullPath: getFullPath(config, s.path)
|
|
105
|
-
}));
|
|
106
|
-
sendJson(res, 200, sessions);
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
// POST /api/sessions
|
|
110
|
-
if (path === '/api/sessions' && method === 'POST') {
|
|
111
|
-
let body = '';
|
|
112
|
-
req.on('data', (chunk) => {
|
|
113
|
-
body += chunk.toString();
|
|
114
|
-
});
|
|
115
|
-
req.on('end', () => {
|
|
116
|
-
try {
|
|
117
|
-
const parsed = JSON.parse(body);
|
|
118
|
-
const name = parsed.name ?? sessionNameFromDir(parsed.dir);
|
|
119
|
-
const sessionPath = parsed.path ?? `/${name}`;
|
|
120
|
-
const port = allocatePort(config);
|
|
121
|
-
const fullPath = getFullPath(config, sessionPath);
|
|
122
|
-
const options = {
|
|
123
|
-
name,
|
|
124
|
-
dir: parsed.dir,
|
|
125
|
-
path: sessionPath,
|
|
126
|
-
port,
|
|
127
|
-
fullPath
|
|
128
|
-
};
|
|
129
|
-
const session = startSession(options);
|
|
130
|
-
sendJson(res, 201, { ...session, fullPath });
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
sendJson(res, 400, { error: getErrorMessage(error) });
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
// DELETE /api/sessions/:name
|
|
139
|
-
const deleteMatch = path.match(/^\/api\/sessions\/(.+)$/);
|
|
140
|
-
if (deleteMatch?.[1] && method === 'DELETE') {
|
|
141
|
-
const name = decodeURIComponent(deleteMatch[1]);
|
|
142
|
-
try {
|
|
143
|
-
stopSession(name);
|
|
144
|
-
sendJson(res, 200, { success: true });
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
sendJson(res, 400, { error: getErrorMessage(error) });
|
|
148
|
-
}
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
// POST /api/shutdown
|
|
152
|
-
if (path === '/api/shutdown' && method === 'POST') {
|
|
153
|
-
sendJson(res, 200, { success: true });
|
|
154
|
-
setTimeout(() => {
|
|
155
|
-
process.exit(0);
|
|
156
|
-
}, 100);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
// Not found
|
|
160
|
-
sendJson(res, 404, { error: 'API endpoint not found' });
|
|
161
|
-
}
|
|
162
|
-
function handleRequest(config, req, res) {
|
|
163
|
-
const url = req.url ?? '/';
|
|
164
|
-
const method = req.method ?? 'GET';
|
|
165
|
-
const basePath = normalizeBasePath(config.base_path);
|
|
166
|
-
// API routes
|
|
167
|
-
if (url.startsWith(`${basePath}/api/`)) {
|
|
168
|
-
handleApiRequest(config, req, res);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
// Portal page
|
|
172
|
-
if (url === basePath || url === `${basePath}/`) {
|
|
173
|
-
if (method === 'GET') {
|
|
174
|
-
const sessions = listSessions();
|
|
175
|
-
const html = generatePortalHtml(config, sessions);
|
|
176
|
-
res.writeHead(200, {
|
|
177
|
-
'Content-Type': 'text/html; charset=utf-8',
|
|
178
|
-
'Content-Length': Buffer.byteLength(html)
|
|
179
|
-
});
|
|
180
|
-
res.end(html);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
// Try to proxy to a session
|
|
185
|
-
const session = findSessionForPath(config, url);
|
|
186
|
-
if (session) {
|
|
187
|
-
const target = `http://localhost:${session.port}`;
|
|
188
|
-
// Store original Accept-Encoding before deletion (for gzip re-compression)
|
|
189
|
-
req.originalAcceptEncoding =
|
|
190
|
-
req.headers['accept-encoding'];
|
|
191
|
-
// Remove Accept-Encoding to get uncompressed response for HTML injection
|
|
192
|
-
delete req.headers['accept-encoding'];
|
|
193
|
-
// Always use selfHandleResponse to avoid conflicts with proxyRes handler
|
|
194
|
-
proxy.web(req, res, { target, selfHandleResponse: true });
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
// Not found
|
|
198
|
-
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
199
|
-
res.end('Not Found');
|
|
200
|
-
}
|
|
201
|
-
// Create WebSocket server (noServer mode for manual upgrade handling)
|
|
202
|
-
const wss = new WebSocketServer({ noServer: true });
|
|
203
|
-
function handleUpgrade(config, req, socket, head) {
|
|
204
|
-
const url = req.url ?? '/';
|
|
205
|
-
const session = findSessionForPath(config, url);
|
|
206
|
-
if (!session) {
|
|
207
|
-
socket.destroy();
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
// Connect to backend WebSocket
|
|
211
|
-
const backendUrl = `ws://127.0.0.1:${session.port}${url}`;
|
|
212
|
-
const protocol = req.headers['sec-websocket-protocol'];
|
|
213
|
-
const backendWs = new WebSocket(backendUrl, protocol ? protocol.split(',').map((p) => p.trim()) : []);
|
|
214
|
-
backendWs.on('open', () => {
|
|
215
|
-
// Upgrade client connection once backend is ready
|
|
216
|
-
wss.handleUpgrade(req, socket, head, (clientWs) => {
|
|
217
|
-
let closed = false;
|
|
218
|
-
const cleanup = (initiator, code, reason) => {
|
|
219
|
-
if (closed)
|
|
220
|
-
return;
|
|
221
|
-
closed = true;
|
|
222
|
-
// Close the other side with proper code
|
|
223
|
-
const closeCode = code ?? 1000;
|
|
224
|
-
const closeReason = reason?.toString() ?? '';
|
|
225
|
-
if (initiator === 'client') {
|
|
226
|
-
if (backendWs.readyState === WebSocket.OPEN) {
|
|
227
|
-
backendWs.close(closeCode, closeReason);
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
backendWs.terminate();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
if (clientWs.readyState === WebSocket.OPEN) {
|
|
235
|
-
clientWs.close(closeCode, closeReason);
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
clientWs.terminate();
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
// Forward messages bidirectionally
|
|
243
|
-
clientWs.on('message', (data, isBinary) => {
|
|
244
|
-
if (backendWs.readyState === WebSocket.OPEN) {
|
|
245
|
-
backendWs.send(data, { binary: isBinary });
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
backendWs.on('message', (data, isBinary) => {
|
|
249
|
-
if (clientWs.readyState === WebSocket.OPEN) {
|
|
250
|
-
clientWs.send(data, { binary: isBinary });
|
|
251
|
-
}
|
|
252
|
-
});
|
|
253
|
-
// Handle close events
|
|
254
|
-
clientWs.on('close', (code, reason) => cleanup('client', code, reason));
|
|
255
|
-
backendWs.on('close', (code, reason) => cleanup('backend', code, reason));
|
|
256
|
-
// Handle errors - terminate to ensure cleanup
|
|
257
|
-
clientWs.on('error', () => {
|
|
258
|
-
clientWs.terminate();
|
|
259
|
-
cleanup('client', 1006);
|
|
260
|
-
});
|
|
261
|
-
backendWs.on('error', () => {
|
|
262
|
-
backendWs.terminate();
|
|
263
|
-
cleanup('backend', 1006);
|
|
264
|
-
});
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
backendWs.on('error', (err) => {
|
|
268
|
-
console.error(`[WebSocket] Connection error: ${err.message}`);
|
|
269
|
-
backendWs.terminate();
|
|
270
|
-
socket.destroy();
|
|
271
|
-
});
|
|
2
|
+
import { handleRequest } from './router.js';
|
|
3
|
+
import { handleUpgrade } from './ws-proxy.js';
|
|
4
|
+
// Dynamic import to avoid circular dependency and allow fallback
|
|
5
|
+
let getConfigFunc = null;
|
|
6
|
+
/**
|
|
7
|
+
* Set the config getter function (called by daemon on startup)
|
|
8
|
+
*/
|
|
9
|
+
export function setConfigGetter(getter) {
|
|
10
|
+
getConfigFunc = getter;
|
|
272
11
|
}
|
|
273
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Create the daemon HTTP server with WebSocket support
|
|
14
|
+
*
|
|
15
|
+
* Note: When ConfigManager is initialized (via setConfigGetter), the server
|
|
16
|
+
* uses the dynamic config for each request to support hot-reloading.
|
|
17
|
+
* Otherwise, it falls back to the initialConfig (for testing).
|
|
18
|
+
*
|
|
19
|
+
* @param initialConfig - Initial config (used as fallback when ConfigManager not initialized)
|
|
20
|
+
*/
|
|
21
|
+
export function createDaemonServer(initialConfig) {
|
|
274
22
|
const server = createServer((req, res) => {
|
|
23
|
+
// Use dynamic config if available, otherwise fall back to initialConfig
|
|
24
|
+
const config = getConfigFunc ? getConfigFunc() : initialConfig;
|
|
275
25
|
handleRequest(config, req, res);
|
|
276
26
|
});
|
|
277
27
|
// Handle WebSocket upgrades
|
|
278
28
|
server.on('upgrade', (req, socket, head) => {
|
|
29
|
+
// Use dynamic config if available, otherwise fall back to initialConfig
|
|
30
|
+
const config = getConfigFunc ? getConfigFunc() : initialConfig;
|
|
279
31
|
handleUpgrade(config, req, socket, head);
|
|
280
32
|
});
|
|
281
33
|
return server;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,YAAY,EAAE,MAAM,WAAW,CAAC;AAItD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,iEAAiE;AACjE,IAAI,aAAa,GAA0B,IAAI,CAAC;AAEhD;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,MAAoB;IAClD,aAAa,GAAG,MAAM,CAAC;AACzB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAqB;IACtD,MAAM,MAAM,GAAG,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QACvC,wEAAwE;QACxE,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC/D,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,4BAA4B;IAC5B,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAoB,EAAE,MAAc,EAAE,IAAY,EAAE,EAAE;QAC1E,wEAAwE;QACxE,MAAM,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAC/D,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1,37 +1,71 @@
|
|
|
1
1
|
import { EventEmitter } from 'node:events';
|
|
2
|
-
import
|
|
2
|
+
import { type StateStore } from '../config/state-store.js';
|
|
3
|
+
import type { Config, SessionState, TmuxMode } from '../config/types.js';
|
|
4
|
+
import { type ProcessRunner } from '../utils/process-runner.js';
|
|
5
|
+
import { type TmuxClient } from '../utils/tmux-client.js';
|
|
3
6
|
export interface StartSessionOptions {
|
|
4
7
|
name: string;
|
|
5
8
|
dir: string;
|
|
6
9
|
path: string;
|
|
7
10
|
port: number;
|
|
8
11
|
fullPath: string;
|
|
12
|
+
tmuxMode?: TmuxMode;
|
|
9
13
|
}
|
|
10
14
|
export interface SessionEvents {
|
|
11
15
|
'session:start': (session: SessionState) => void;
|
|
12
16
|
'session:stop': (name: string) => void;
|
|
13
17
|
'session:exit': (name: string, code: number | null) => void;
|
|
14
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Dependencies for SessionManager (for testing)
|
|
21
|
+
*/
|
|
22
|
+
export interface SessionManagerDeps {
|
|
23
|
+
stateStore: StateStore;
|
|
24
|
+
processRunner: ProcessRunner;
|
|
25
|
+
tmuxClient: TmuxClient;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Default dependencies using real implementations
|
|
29
|
+
*/
|
|
30
|
+
export declare const defaultSessionManagerDeps: SessionManagerDeps;
|
|
15
31
|
/**
|
|
16
32
|
* Session manager with EventEmitter for proactive process monitoring
|
|
17
33
|
*/
|
|
18
|
-
declare class SessionManager extends EventEmitter {
|
|
34
|
+
export declare class SessionManager extends EventEmitter {
|
|
19
35
|
private runningProcesses;
|
|
20
|
-
|
|
36
|
+
private deps;
|
|
37
|
+
constructor(deps?: SessionManagerDeps);
|
|
38
|
+
startSession(options: StartSessionOptions): Promise<SessionState>;
|
|
21
39
|
stopSession(name: string): void;
|
|
22
40
|
isProcessRunning(pid: number): boolean;
|
|
23
41
|
listSessions(): SessionState[];
|
|
24
42
|
stopAllSessions(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Revalidate sessions after daemon restart.
|
|
45
|
+
* Checks if each session's ttyd process is still running.
|
|
46
|
+
* Removes dead sessions from state.
|
|
47
|
+
*/
|
|
48
|
+
revalidateSessions(): {
|
|
49
|
+
valid: SessionState[];
|
|
50
|
+
removed: string[];
|
|
51
|
+
};
|
|
25
52
|
private handleProcessExit;
|
|
26
53
|
private cleanup;
|
|
27
54
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export declare
|
|
32
|
-
|
|
33
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Shared session manager instance
|
|
57
|
+
*/
|
|
58
|
+
export declare const sessionManager: SessionManager;
|
|
59
|
+
/**
|
|
60
|
+
* Allocate next available port for a new session
|
|
61
|
+
*/
|
|
34
62
|
export declare function allocatePort(config: Config): number;
|
|
63
|
+
/**
|
|
64
|
+
* Extract session name from directory path
|
|
65
|
+
*/
|
|
35
66
|
export declare function sessionNameFromDir(dir: string): string;
|
|
36
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Create a SessionManager with custom dependencies (for testing)
|
|
69
|
+
*/
|
|
70
|
+
export declare function createSessionManager(deps?: Partial<SessionManagerDeps>): SessionManager;
|
|
37
71
|
//# sourceMappingURL=session-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/daemon/session-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/daemon/session-manager.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,yBAAyB,CAAC;AAC7E,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAExE,OAAO,EAAE,KAAK,aAAa,EAAwB,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,wBAAwB,CAAC;AAI5E,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAkBD,MAAM,WAAW,aAAa;IAC5B,eAAe,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,IAAI,CAAC;IACjD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC7D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,aAAa,EAAE,aAAa,CAAC;IAC7B,UAAU,EAAE,UAAU,CAAC;CACxB;AAED;;GAEG;AACH,eAAO,MAAM,yBAAyB,EAAE,kBAIvC,CAAC;AAEF;;GAEG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,gBAAgB,CAAmC;IAC3D,OAAO,CAAC,IAAI,CAAqB;gBAErB,IAAI,GAAE,kBAA8C;IAK1D,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,YAAY,CAAC;IA6EvE,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAuB/B,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAItC,YAAY,IAAI,YAAY,EAAE;IAiB9B,eAAe,IAAI,IAAI;IAavB;;;;OAIG;IACH,kBAAkB,IAAI;QAAE,KAAK,EAAE,YAAY,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE;IAoBlE,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,OAAO;CAKhB;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,gBAAuB,CAAC;AAEnD;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,OAAO,CAAC,kBAAkB,CAAM,GAAG,cAAc,CAK3F"}
|
|
@@ -1,28 +1,79 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
1
|
import { EventEmitter } from 'node:events';
|
|
3
|
-
import {
|
|
2
|
+
import { defaultStateStore } from '../config/state-store.js';
|
|
3
|
+
import { createLogger } from '../utils/logger.js';
|
|
4
|
+
import { defaultProcessRunner } from '../utils/process-runner.js';
|
|
5
|
+
import { defaultTmuxClient } from '../utils/tmux-client.js';
|
|
6
|
+
const log = createLogger('session');
|
|
7
|
+
/**
|
|
8
|
+
* Get tmux command arguments based on mode
|
|
9
|
+
*/
|
|
10
|
+
function getTmuxCommand(name, mode) {
|
|
11
|
+
switch (mode) {
|
|
12
|
+
case 'attach':
|
|
13
|
+
return ['tmux', 'attach-session', '-t', name];
|
|
14
|
+
case 'new':
|
|
15
|
+
return ['tmux', 'new-session', '-s', name];
|
|
16
|
+
default:
|
|
17
|
+
// For auto mode, session is pre-created by ensureSession
|
|
18
|
+
// Just attach to it
|
|
19
|
+
return ['tmux', 'attach-session', '-t', name];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Default dependencies using real implementations
|
|
24
|
+
*/
|
|
25
|
+
export const defaultSessionManagerDeps = {
|
|
26
|
+
stateStore: defaultStateStore,
|
|
27
|
+
processRunner: defaultProcessRunner,
|
|
28
|
+
tmuxClient: defaultTmuxClient
|
|
29
|
+
};
|
|
4
30
|
/**
|
|
5
31
|
* Session manager with EventEmitter for proactive process monitoring
|
|
6
32
|
*/
|
|
7
|
-
class SessionManager extends EventEmitter {
|
|
33
|
+
export class SessionManager extends EventEmitter {
|
|
8
34
|
runningProcesses = new Map();
|
|
9
|
-
|
|
10
|
-
|
|
35
|
+
deps;
|
|
36
|
+
constructor(deps = defaultSessionManagerDeps) {
|
|
37
|
+
super();
|
|
38
|
+
this.deps = deps;
|
|
39
|
+
}
|
|
40
|
+
async startSession(options) {
|
|
41
|
+
const { name, dir, path, port, fullPath, tmuxMode = 'auto' } = options;
|
|
42
|
+
const { stateStore, processRunner, tmuxClient } = this.deps;
|
|
43
|
+
log.info(`Starting session: ${name} (port=${port}, mode=${tmuxMode})`);
|
|
11
44
|
// Check if already running
|
|
12
|
-
const existing = getSession(name);
|
|
13
|
-
if (existing &&
|
|
14
|
-
|
|
45
|
+
const existing = stateStore.getSession(name);
|
|
46
|
+
if (existing && processRunner.isProcessRunning(existing.pid)) {
|
|
47
|
+
log.warn(`Session "${name}" is already running (pid=${existing.pid})`);
|
|
48
|
+
throw new Error(`Session "${name}" is already running on port ${existing.port}.\n To view running sessions: ttyd-mux status\n To stop this session: ttyd-mux down ${name}`);
|
|
49
|
+
}
|
|
50
|
+
// Check if port is available
|
|
51
|
+
const portAvailable = await processRunner.isPortAvailable(port);
|
|
52
|
+
if (!portAvailable) {
|
|
53
|
+
log.error(`Port ${port} is already in use`);
|
|
54
|
+
throw new Error(`Port ${port} is already in use by another process.\n To find the process: lsof -i :${port}\n To stop all sessions: ttyd-mux shutdown`);
|
|
15
55
|
}
|
|
56
|
+
// For auto mode, ensure tmux session exists before starting ttyd
|
|
57
|
+
if (tmuxMode === 'auto') {
|
|
58
|
+
log.debug(`Ensuring tmux session exists: ${name}`);
|
|
59
|
+
tmuxClient.ensureSession(name, dir);
|
|
60
|
+
}
|
|
61
|
+
// Get tmux command based on mode
|
|
62
|
+
const tmuxCmd = getTmuxCommand(name, tmuxMode);
|
|
63
|
+
const ttydArgs = ['-W', '-p', String(port), '-b', fullPath, ...tmuxCmd];
|
|
64
|
+
log.debug(`ttyd command: ttyd ${ttydArgs.join(' ')}`);
|
|
16
65
|
// Start ttyd process
|
|
17
|
-
const ttydProcess = spawn('ttyd',
|
|
66
|
+
const ttydProcess = processRunner.spawn('ttyd', ttydArgs, {
|
|
18
67
|
cwd: dir,
|
|
19
68
|
detached: true,
|
|
20
69
|
stdio: 'ignore'
|
|
21
70
|
});
|
|
22
71
|
ttydProcess.unref();
|
|
23
72
|
if (!ttydProcess.pid) {
|
|
24
|
-
|
|
73
|
+
log.error(`Failed to start ttyd for session "${name}"`);
|
|
74
|
+
throw new Error(`Failed to start ttyd for session "${name}".\n Possible causes:\n - ttyd is not installed\n - Directory "${dir}" does not exist\n - Permission denied\n Run 'ttyd-mux doctor' to check dependencies.`);
|
|
25
75
|
}
|
|
76
|
+
log.info(`ttyd started: pid=${ttydProcess.pid}`);
|
|
26
77
|
// Track the process
|
|
27
78
|
this.runningProcesses.set(name, ttydProcess);
|
|
28
79
|
// Proactive cleanup: listen for process exit
|
|
@@ -38,41 +89,40 @@ class SessionManager extends EventEmitter {
|
|
|
38
89
|
dir,
|
|
39
90
|
started_at: new Date().toISOString()
|
|
40
91
|
};
|
|
41
|
-
addSession(session);
|
|
92
|
+
stateStore.addSession(session);
|
|
42
93
|
this.emit('session:start', session);
|
|
43
94
|
return session;
|
|
44
95
|
}
|
|
45
96
|
stopSession(name) {
|
|
46
|
-
const
|
|
97
|
+
const { stateStore, processRunner } = this.deps;
|
|
98
|
+
log.info(`Stopping session: ${name}`);
|
|
99
|
+
const session = stateStore.getSession(name);
|
|
47
100
|
if (!session) {
|
|
48
|
-
|
|
101
|
+
log.warn(`Session "${name}" not found`);
|
|
102
|
+
throw new Error(`Session "${name}" not found.\n To view running sessions: ttyd-mux status`);
|
|
49
103
|
}
|
|
50
104
|
// Try to kill the process
|
|
51
105
|
try {
|
|
52
|
-
|
|
106
|
+
processRunner.kill(session.pid, 'SIGTERM');
|
|
107
|
+
log.info(`Sent SIGTERM to pid ${session.pid}`);
|
|
53
108
|
}
|
|
54
|
-
catch {
|
|
55
|
-
|
|
109
|
+
catch (err) {
|
|
110
|
+
log.debug(`Process ${session.pid} might already be dead: ${err}`);
|
|
56
111
|
}
|
|
57
112
|
// Clean up
|
|
58
113
|
this.cleanup(name);
|
|
59
114
|
this.emit('session:stop', name);
|
|
60
115
|
}
|
|
61
116
|
isProcessRunning(pid) {
|
|
62
|
-
|
|
63
|
-
process.kill(pid, 0);
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
117
|
+
return this.deps.processRunner.isProcessRunning(pid);
|
|
69
118
|
}
|
|
70
119
|
listSessions() {
|
|
71
|
-
const
|
|
120
|
+
const { stateStore, processRunner } = this.deps;
|
|
121
|
+
const sessions = stateStore.getAllSessions();
|
|
72
122
|
// Filter out dead sessions (lazy cleanup for sessions started before daemon)
|
|
73
123
|
const activeSessions = [];
|
|
74
124
|
for (const session of sessions) {
|
|
75
|
-
if (
|
|
125
|
+
if (processRunner.isProcessRunning(session.pid)) {
|
|
76
126
|
activeSessions.push(session);
|
|
77
127
|
}
|
|
78
128
|
else {
|
|
@@ -82,50 +132,76 @@ class SessionManager extends EventEmitter {
|
|
|
82
132
|
return activeSessions;
|
|
83
133
|
}
|
|
84
134
|
stopAllSessions() {
|
|
135
|
+
log.info('Stopping all sessions');
|
|
85
136
|
const sessions = this.listSessions();
|
|
86
137
|
for (const session of sessions) {
|
|
87
138
|
try {
|
|
88
139
|
this.stopSession(session.name);
|
|
89
140
|
}
|
|
90
|
-
catch {
|
|
91
|
-
|
|
141
|
+
catch (err) {
|
|
142
|
+
log.warn(`Error stopping session "${session.name}": ${err}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
log.info(`Stopped ${sessions.length} sessions`);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Revalidate sessions after daemon restart.
|
|
149
|
+
* Checks if each session's ttyd process is still running.
|
|
150
|
+
* Removes dead sessions from state.
|
|
151
|
+
*/
|
|
152
|
+
revalidateSessions() {
|
|
153
|
+
const { stateStore, processRunner } = this.deps;
|
|
154
|
+
const sessions = stateStore.getAllSessions();
|
|
155
|
+
const valid = [];
|
|
156
|
+
const removed = [];
|
|
157
|
+
for (const session of sessions) {
|
|
158
|
+
if (processRunner.isProcessRunning(session.pid)) {
|
|
159
|
+
valid.push(session);
|
|
160
|
+
log.debug(`Session "${session.name}" (pid=${session.pid}) is still running`);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
stateStore.removeSession(session.name);
|
|
164
|
+
removed.push(session.name);
|
|
165
|
+
log.info(`Session "${session.name}" (pid=${session.pid}) is dead, removed from state`);
|
|
92
166
|
}
|
|
93
167
|
}
|
|
168
|
+
return { valid, removed };
|
|
94
169
|
}
|
|
95
170
|
handleProcessExit(name, code) {
|
|
171
|
+
log.info(`Session "${name}" exited with code ${code}`);
|
|
96
172
|
this.cleanup(name);
|
|
97
173
|
this.emit('session:exit', name, code);
|
|
98
174
|
}
|
|
99
175
|
cleanup(name) {
|
|
176
|
+
log.debug(`Cleaning up session: ${name}`);
|
|
100
177
|
this.runningProcesses.delete(name);
|
|
101
|
-
removeSession(name);
|
|
178
|
+
this.deps.stateStore.removeSession(name);
|
|
102
179
|
}
|
|
103
180
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
export
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
sessionManager.stopSession(name);
|
|
112
|
-
}
|
|
113
|
-
export function isProcessRunning(pid) {
|
|
114
|
-
return sessionManager.isProcessRunning(pid);
|
|
115
|
-
}
|
|
116
|
-
export function listSessions() {
|
|
117
|
-
return sessionManager.listSessions();
|
|
118
|
-
}
|
|
119
|
-
export function stopAllSessions() {
|
|
120
|
-
sessionManager.stopAllSessions();
|
|
121
|
-
}
|
|
181
|
+
/**
|
|
182
|
+
* Shared session manager instance
|
|
183
|
+
*/
|
|
184
|
+
export const sessionManager = new SessionManager();
|
|
185
|
+
/**
|
|
186
|
+
* Allocate next available port for a new session
|
|
187
|
+
*/
|
|
122
188
|
export function allocatePort(config) {
|
|
123
|
-
return getNextPort(config.base_port);
|
|
189
|
+
return defaultStateStore.getNextPort(config.base_port);
|
|
124
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Extract session name from directory path
|
|
193
|
+
*/
|
|
125
194
|
export function sessionNameFromDir(dir) {
|
|
126
195
|
const parts = dir.split('/');
|
|
127
196
|
return parts[parts.length - 1] || 'default';
|
|
128
197
|
}
|
|
129
|
-
|
|
130
|
-
|
|
198
|
+
/**
|
|
199
|
+
* Create a SessionManager with custom dependencies (for testing)
|
|
200
|
+
*/
|
|
201
|
+
export function createSessionManager(deps = {}) {
|
|
202
|
+
return new SessionManager({
|
|
203
|
+
...defaultSessionManagerDeps,
|
|
204
|
+
...deps
|
|
205
|
+
});
|
|
206
|
+
}
|
|
131
207
|
//# sourceMappingURL=session-manager.js.map
|