@seflless/ghosttown 1.7.0 → 1.10.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/package.json +3 -2
- package/src/cli.js +216 -57
- package/src/session/history-replay.d.ts +118 -0
- package/src/session/history-replay.js +174 -0
- package/src/session/history-replay.js.map +1 -0
- package/src/session/index.d.ts +10 -0
- package/src/session/index.js +11 -0
- package/src/session/index.js.map +1 -0
- package/src/session/output-recorder.d.ts +131 -0
- package/src/session/output-recorder.js +247 -0
- package/src/session/output-recorder.js.map +1 -0
- package/src/session/session-manager.d.ts +147 -0
- package/src/session/session-manager.js +489 -0
- package/src/session/session-manager.js.map +1 -0
- package/src/session/types.d.ts +221 -0
- package/src/session/types.js +8 -0
- package/src/session/types.js.map +1 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Core session management without tmux. Handles:
|
|
5
|
+
* - Direct PTY spawning via node-pty
|
|
6
|
+
* - Session persistence to disk
|
|
7
|
+
* - Session restoration on server restart
|
|
8
|
+
* - Multi-client connections
|
|
9
|
+
*/
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import type { IPty } from '@lydell/node-pty';
|
|
12
|
+
import { HistoryReplay } from './history-replay.js';
|
|
13
|
+
import { type OutputChunk } from './output-recorder.js';
|
|
14
|
+
import type { CreateSessionOptions, Session, SessionId, SessionInfo, SessionManagerConfig, SessionPaths } from './types.js';
|
|
15
|
+
/**
|
|
16
|
+
* Get the default shell for the current platform.
|
|
17
|
+
*/
|
|
18
|
+
declare function getDefaultShell(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Get default environment variables for terminal sessions.
|
|
21
|
+
*/
|
|
22
|
+
declare function getDefaultEnv(): Record<string, string>;
|
|
23
|
+
/**
|
|
24
|
+
* SessionManager handles all session lifecycle operations.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const manager = new SessionManager();
|
|
29
|
+
* await manager.init();
|
|
30
|
+
*
|
|
31
|
+
* // Create a new session
|
|
32
|
+
* const session = await manager.createSession({ name: 'dev' });
|
|
33
|
+
*
|
|
34
|
+
* // Get the PTY for I/O
|
|
35
|
+
* const pty = manager.getPty(session.id);
|
|
36
|
+
* pty.onData((data) => console.log(data));
|
|
37
|
+
* pty.write('echo hello\n');
|
|
38
|
+
*
|
|
39
|
+
* // List all sessions
|
|
40
|
+
* const sessions = await manager.listSessions();
|
|
41
|
+
*
|
|
42
|
+
* // Delete a session
|
|
43
|
+
* await manager.deleteSession(session.id);
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
export declare class SessionManager extends EventEmitter {
|
|
47
|
+
private config;
|
|
48
|
+
private paths;
|
|
49
|
+
private sessions;
|
|
50
|
+
private ptyProcesses;
|
|
51
|
+
private outputRecorders;
|
|
52
|
+
private initialized;
|
|
53
|
+
constructor(config?: SessionManagerConfig);
|
|
54
|
+
/**
|
|
55
|
+
* Initialize the session manager.
|
|
56
|
+
* Loads persisted sessions from disk.
|
|
57
|
+
*/
|
|
58
|
+
init(): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* Create a new session.
|
|
61
|
+
*/
|
|
62
|
+
createSession(options?: CreateSessionOptions): Promise<Session>;
|
|
63
|
+
/**
|
|
64
|
+
* Get a session by ID.
|
|
65
|
+
*/
|
|
66
|
+
getSession(sessionId: SessionId): Session | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Get the PTY process for a session.
|
|
69
|
+
* Returns undefined if the process is not running.
|
|
70
|
+
*/
|
|
71
|
+
getPty(sessionId: SessionId): IPty | undefined;
|
|
72
|
+
/**
|
|
73
|
+
* List all sessions with summary info.
|
|
74
|
+
*/
|
|
75
|
+
listSessions(): Promise<SessionInfo[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Delete a session and all associated data.
|
|
78
|
+
*/
|
|
79
|
+
deleteSession(sessionId: SessionId): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Rename a session.
|
|
82
|
+
*/
|
|
83
|
+
renameSession(sessionId: SessionId, newName: string): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Resize a session's terminal.
|
|
86
|
+
*/
|
|
87
|
+
resizeSession(sessionId: SessionId, cols: number, rows: number): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Connect to a session, respawning the process if needed.
|
|
90
|
+
* Returns the PTY for I/O.
|
|
91
|
+
*/
|
|
92
|
+
connectToSession(sessionId: SessionId): Promise<IPty>;
|
|
93
|
+
/**
|
|
94
|
+
* Write data to a session's PTY.
|
|
95
|
+
*/
|
|
96
|
+
write(sessionId: SessionId, data: string): void;
|
|
97
|
+
/**
|
|
98
|
+
* Get the storage paths for session data.
|
|
99
|
+
*/
|
|
100
|
+
getPaths(): SessionPaths;
|
|
101
|
+
/**
|
|
102
|
+
* Get the buffered output for a session (for replay on reconnect).
|
|
103
|
+
* Returns the concatenated output as a single string.
|
|
104
|
+
* Note: This loads all scrollback into memory. For large histories,
|
|
105
|
+
* use getScrollbackChunks() instead.
|
|
106
|
+
*/
|
|
107
|
+
getScrollback(sessionId: SessionId): Promise<string>;
|
|
108
|
+
/**
|
|
109
|
+
* Get scrollback chunks for a session with pagination.
|
|
110
|
+
* @param sessionId Session ID
|
|
111
|
+
* @param offset Number of chunks to skip from the start
|
|
112
|
+
* @param limit Maximum number of chunks to return
|
|
113
|
+
*/
|
|
114
|
+
getScrollbackChunks(sessionId: SessionId, offset?: number, limit?: number): Promise<OutputChunk[]>;
|
|
115
|
+
/**
|
|
116
|
+
* Get the total number of scrollback chunks for a session.
|
|
117
|
+
*/
|
|
118
|
+
getScrollbackLength(sessionId: SessionId): number;
|
|
119
|
+
/**
|
|
120
|
+
* Create a history replay for streaming scrollback to a client.
|
|
121
|
+
* The replay emits 'data', 'progress', and 'complete' events.
|
|
122
|
+
*/
|
|
123
|
+
createHistoryReplay(sessionId: SessionId): Promise<HistoryReplay | null>;
|
|
124
|
+
/**
|
|
125
|
+
* Spawn a PTY process for a session.
|
|
126
|
+
*/
|
|
127
|
+
private spawnProcess;
|
|
128
|
+
/**
|
|
129
|
+
* Persist a session's metadata to disk.
|
|
130
|
+
*/
|
|
131
|
+
private persistSession;
|
|
132
|
+
/**
|
|
133
|
+
* Load persisted sessions from disk.
|
|
134
|
+
*/
|
|
135
|
+
private loadPersistedSessions;
|
|
136
|
+
/**
|
|
137
|
+
* Generate a display name for a new session.
|
|
138
|
+
* Returns the next available number (1, 2, 3, ...).
|
|
139
|
+
*/
|
|
140
|
+
private generateDisplayName;
|
|
141
|
+
/**
|
|
142
|
+
* Clean up all resources.
|
|
143
|
+
*/
|
|
144
|
+
destroy(): Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
export declare function getSessionManager(config?: SessionManagerConfig): SessionManager;
|
|
147
|
+
export { getDefaultShell, getDefaultEnv };
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Core session management without tmux. Handles:
|
|
5
|
+
* - Direct PTY spawning via node-pty
|
|
6
|
+
* - Session persistence to disk
|
|
7
|
+
* - Session restoration on server restart
|
|
8
|
+
* - Multi-client connections
|
|
9
|
+
*/
|
|
10
|
+
import { randomUUID } from 'crypto';
|
|
11
|
+
import { EventEmitter } from 'events';
|
|
12
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import fs from 'fs/promises';
|
|
16
|
+
import { HistoryReplay } from './history-replay.js';
|
|
17
|
+
import { OutputRecorder } from './output-recorder.js';
|
|
18
|
+
// Dynamic import for node-pty (CommonJS module)
|
|
19
|
+
let ptyModule = null;
|
|
20
|
+
async function getPty() {
|
|
21
|
+
if (!ptyModule) {
|
|
22
|
+
ptyModule = await import('@lydell/node-pty');
|
|
23
|
+
}
|
|
24
|
+
return ptyModule;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Default configuration values.
|
|
28
|
+
*/
|
|
29
|
+
const DEFAULTS = {
|
|
30
|
+
storageDir: path.join(homedir(), '.config', 'ghosttown', 'sessions'),
|
|
31
|
+
scrollbackLimit: 10000,
|
|
32
|
+
defaultShell: process.env.SHELL || '/bin/sh',
|
|
33
|
+
defaultCols: 80,
|
|
34
|
+
defaultRows: 24,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Get the default shell for the current platform.
|
|
38
|
+
*/
|
|
39
|
+
function getDefaultShell() {
|
|
40
|
+
if (process.platform === 'win32') {
|
|
41
|
+
return process.env.COMSPEC || 'cmd.exe';
|
|
42
|
+
}
|
|
43
|
+
return process.env.SHELL || '/bin/sh';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get default environment variables for terminal sessions.
|
|
47
|
+
*/
|
|
48
|
+
function getDefaultEnv() {
|
|
49
|
+
return {
|
|
50
|
+
TERM: 'xterm-256color',
|
|
51
|
+
COLORTERM: 'truecolor',
|
|
52
|
+
LANG: process.env.LANG || 'en_US.UTF-8',
|
|
53
|
+
LC_ALL: process.env.LC_ALL || 'en_US.UTF-8',
|
|
54
|
+
TERM_PROGRAM: 'ghosttown',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* SessionManager handles all session lifecycle operations.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const manager = new SessionManager();
|
|
63
|
+
* await manager.init();
|
|
64
|
+
*
|
|
65
|
+
* // Create a new session
|
|
66
|
+
* const session = await manager.createSession({ name: 'dev' });
|
|
67
|
+
*
|
|
68
|
+
* // Get the PTY for I/O
|
|
69
|
+
* const pty = manager.getPty(session.id);
|
|
70
|
+
* pty.onData((data) => console.log(data));
|
|
71
|
+
* pty.write('echo hello\n');
|
|
72
|
+
*
|
|
73
|
+
* // List all sessions
|
|
74
|
+
* const sessions = await manager.listSessions();
|
|
75
|
+
*
|
|
76
|
+
* // Delete a session
|
|
77
|
+
* await manager.deleteSession(session.id);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export class SessionManager extends EventEmitter {
|
|
81
|
+
constructor(config = {}) {
|
|
82
|
+
super();
|
|
83
|
+
this.sessions = new Map();
|
|
84
|
+
this.ptyProcesses = new Map();
|
|
85
|
+
this.outputRecorders = new Map(); // Disk-backed scrollback
|
|
86
|
+
this.initialized = false;
|
|
87
|
+
this.config = {
|
|
88
|
+
storageDir: config.storageDir ?? DEFAULTS.storageDir,
|
|
89
|
+
scrollbackLimit: config.scrollbackLimit ?? DEFAULTS.scrollbackLimit,
|
|
90
|
+
defaultShell: config.defaultShell ?? getDefaultShell(),
|
|
91
|
+
};
|
|
92
|
+
this.paths = {
|
|
93
|
+
baseDir: this.config.storageDir,
|
|
94
|
+
sessionDir: (id) => path.join(this.config.storageDir, id),
|
|
95
|
+
metadataFile: (id) => path.join(this.config.storageDir, id, 'metadata.json'),
|
|
96
|
+
scrollbackFile: (id) => path.join(this.config.storageDir, id, 'scrollback.jsonl'),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Initialize the session manager.
|
|
101
|
+
* Loads persisted sessions from disk.
|
|
102
|
+
*/
|
|
103
|
+
async init() {
|
|
104
|
+
if (this.initialized)
|
|
105
|
+
return;
|
|
106
|
+
// Ensure storage directory exists
|
|
107
|
+
if (!existsSync(this.paths.baseDir)) {
|
|
108
|
+
mkdirSync(this.paths.baseDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
// Load persisted sessions
|
|
111
|
+
await this.loadPersistedSessions();
|
|
112
|
+
this.initialized = true;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a new session.
|
|
116
|
+
*/
|
|
117
|
+
async createSession(options = {}) {
|
|
118
|
+
if (!this.initialized) {
|
|
119
|
+
throw new Error('SessionManager not initialized. Call init() first.');
|
|
120
|
+
}
|
|
121
|
+
const id = randomUUID();
|
|
122
|
+
const displayName = options.name ?? this.generateDisplayName();
|
|
123
|
+
const now = Date.now();
|
|
124
|
+
const session = {
|
|
125
|
+
id,
|
|
126
|
+
displayName,
|
|
127
|
+
createdAt: now,
|
|
128
|
+
lastActivity: now,
|
|
129
|
+
process: {
|
|
130
|
+
pid: null,
|
|
131
|
+
shell: options.shell ?? this.config.defaultShell,
|
|
132
|
+
args: options.args ?? [],
|
|
133
|
+
cwd: options.cwd ?? homedir(),
|
|
134
|
+
env: {
|
|
135
|
+
...getDefaultEnv(),
|
|
136
|
+
...options.env,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
terminal: {
|
|
140
|
+
cols: options.cols ?? DEFAULTS.defaultCols,
|
|
141
|
+
rows: options.rows ?? DEFAULTS.defaultRows,
|
|
142
|
+
cursorX: 0,
|
|
143
|
+
cursorY: 0,
|
|
144
|
+
cursorStyle: 'block',
|
|
145
|
+
cursorVisible: true,
|
|
146
|
+
},
|
|
147
|
+
scrollbackFile: 'scrollback.jsonl',
|
|
148
|
+
scrollbackLength: 0,
|
|
149
|
+
sharing: null,
|
|
150
|
+
};
|
|
151
|
+
// Create session directory
|
|
152
|
+
const sessionDir = this.paths.sessionDir(id);
|
|
153
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
154
|
+
// Spawn PTY process if requested (default: true)
|
|
155
|
+
// Web sessions use startProcess: false to wait for client connection
|
|
156
|
+
if (options.startProcess !== false) {
|
|
157
|
+
await this.spawnProcess(session);
|
|
158
|
+
}
|
|
159
|
+
// Save to memory and disk
|
|
160
|
+
this.sessions.set(id, session);
|
|
161
|
+
await this.persistSession(session);
|
|
162
|
+
this.emit('session:created', session);
|
|
163
|
+
return session;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get a session by ID.
|
|
167
|
+
*/
|
|
168
|
+
getSession(sessionId) {
|
|
169
|
+
return this.sessions.get(sessionId);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get the PTY process for a session.
|
|
173
|
+
* Returns undefined if the process is not running.
|
|
174
|
+
*/
|
|
175
|
+
getPty(sessionId) {
|
|
176
|
+
return this.ptyProcesses.get(sessionId);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* List all sessions with summary info.
|
|
180
|
+
*/
|
|
181
|
+
async listSessions() {
|
|
182
|
+
const sessions = [];
|
|
183
|
+
for (const session of this.sessions.values()) {
|
|
184
|
+
sessions.push({
|
|
185
|
+
id: session.id,
|
|
186
|
+
displayName: session.displayName,
|
|
187
|
+
lastActivity: session.lastActivity,
|
|
188
|
+
isRunning: session.process.pid !== null,
|
|
189
|
+
connectionCount: 0, // TODO: Track connections
|
|
190
|
+
isShared: session.sharing?.enabled ?? false,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// Sort by last activity (most recent first)
|
|
194
|
+
sessions.sort((a, b) => b.lastActivity - a.lastActivity);
|
|
195
|
+
return sessions;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Delete a session and all associated data.
|
|
199
|
+
*/
|
|
200
|
+
async deleteSession(sessionId) {
|
|
201
|
+
const session = this.sessions.get(sessionId);
|
|
202
|
+
if (!session) {
|
|
203
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
204
|
+
}
|
|
205
|
+
// Kill the PTY process if running
|
|
206
|
+
const pty = this.ptyProcesses.get(sessionId);
|
|
207
|
+
if (pty) {
|
|
208
|
+
pty.kill();
|
|
209
|
+
this.ptyProcesses.delete(sessionId);
|
|
210
|
+
}
|
|
211
|
+
// Close the output recorder
|
|
212
|
+
const recorder = this.outputRecorders.get(sessionId);
|
|
213
|
+
if (recorder) {
|
|
214
|
+
await recorder.close();
|
|
215
|
+
this.outputRecorders.delete(sessionId);
|
|
216
|
+
}
|
|
217
|
+
// Remove from memory
|
|
218
|
+
this.sessions.delete(sessionId);
|
|
219
|
+
// Remove from disk
|
|
220
|
+
const sessionDir = this.paths.sessionDir(sessionId);
|
|
221
|
+
if (existsSync(sessionDir)) {
|
|
222
|
+
await fs.rm(sessionDir, { recursive: true, force: true });
|
|
223
|
+
}
|
|
224
|
+
this.emit('session:deleted', sessionId);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Rename a session.
|
|
228
|
+
*/
|
|
229
|
+
async renameSession(sessionId, newName) {
|
|
230
|
+
const session = this.sessions.get(sessionId);
|
|
231
|
+
if (!session) {
|
|
232
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
233
|
+
}
|
|
234
|
+
session.displayName = newName;
|
|
235
|
+
session.lastActivity = Date.now();
|
|
236
|
+
await this.persistSession(session);
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Resize a session's terminal.
|
|
240
|
+
*/
|
|
241
|
+
async resizeSession(sessionId, cols, rows) {
|
|
242
|
+
const session = this.sessions.get(sessionId);
|
|
243
|
+
if (!session) {
|
|
244
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
245
|
+
}
|
|
246
|
+
// Update session state first
|
|
247
|
+
session.terminal.cols = cols;
|
|
248
|
+
session.terminal.rows = rows;
|
|
249
|
+
session.lastActivity = Date.now();
|
|
250
|
+
// Persist before attempting PTY resize (so state is saved even if PTY fails)
|
|
251
|
+
await this.persistSession(session);
|
|
252
|
+
// Try to resize the PTY (may fail if process exited)
|
|
253
|
+
const pty = this.ptyProcesses.get(sessionId);
|
|
254
|
+
if (pty) {
|
|
255
|
+
try {
|
|
256
|
+
pty.resize(cols, rows);
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
// PTY may have exited, that's ok - state is already updated
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Connect to a session, respawning the process if needed.
|
|
265
|
+
* Returns the PTY for I/O.
|
|
266
|
+
*/
|
|
267
|
+
async connectToSession(sessionId) {
|
|
268
|
+
const session = this.sessions.get(sessionId);
|
|
269
|
+
if (!session) {
|
|
270
|
+
throw new Error(`Session not found: ${sessionId}`);
|
|
271
|
+
}
|
|
272
|
+
// Respawn process if it's not running
|
|
273
|
+
const hasPty = this.ptyProcesses.has(sessionId);
|
|
274
|
+
if (!hasPty) {
|
|
275
|
+
await this.spawnProcess(session);
|
|
276
|
+
}
|
|
277
|
+
const pty = this.ptyProcesses.get(sessionId);
|
|
278
|
+
if (!pty) {
|
|
279
|
+
throw new Error(`Failed to get PTY for session: ${sessionId}`);
|
|
280
|
+
}
|
|
281
|
+
session.lastActivity = Date.now();
|
|
282
|
+
await this.persistSession(session);
|
|
283
|
+
return pty;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Write data to a session's PTY.
|
|
287
|
+
*/
|
|
288
|
+
write(sessionId, data) {
|
|
289
|
+
const pty = this.ptyProcesses.get(sessionId);
|
|
290
|
+
if (!pty) {
|
|
291
|
+
throw new Error(`No PTY for session: ${sessionId}`);
|
|
292
|
+
}
|
|
293
|
+
pty.write(data);
|
|
294
|
+
const session = this.sessions.get(sessionId);
|
|
295
|
+
if (session) {
|
|
296
|
+
session.lastActivity = Date.now();
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Get the storage paths for session data.
|
|
301
|
+
*/
|
|
302
|
+
getPaths() {
|
|
303
|
+
return this.paths;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Get the buffered output for a session (for replay on reconnect).
|
|
307
|
+
* Returns the concatenated output as a single string.
|
|
308
|
+
* Note: This loads all scrollback into memory. For large histories,
|
|
309
|
+
* use getScrollbackChunks() instead.
|
|
310
|
+
*/
|
|
311
|
+
async getScrollback(sessionId) {
|
|
312
|
+
const recorder = this.outputRecorders.get(sessionId);
|
|
313
|
+
if (!recorder) {
|
|
314
|
+
return '';
|
|
315
|
+
}
|
|
316
|
+
const chunks = await recorder.readAll();
|
|
317
|
+
return chunks.map((c) => c.d).join('');
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get scrollback chunks for a session with pagination.
|
|
321
|
+
* @param sessionId Session ID
|
|
322
|
+
* @param offset Number of chunks to skip from the start
|
|
323
|
+
* @param limit Maximum number of chunks to return
|
|
324
|
+
*/
|
|
325
|
+
async getScrollbackChunks(sessionId, offset = 0, limit = 1000) {
|
|
326
|
+
const recorder = this.outputRecorders.get(sessionId);
|
|
327
|
+
if (!recorder) {
|
|
328
|
+
return [];
|
|
329
|
+
}
|
|
330
|
+
return recorder.read(offset, limit);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get the total number of scrollback chunks for a session.
|
|
334
|
+
*/
|
|
335
|
+
getScrollbackLength(sessionId) {
|
|
336
|
+
const recorder = this.outputRecorders.get(sessionId);
|
|
337
|
+
return recorder ? recorder.getChunkCount() : 0;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Create a history replay for streaming scrollback to a client.
|
|
341
|
+
* The replay emits 'data', 'progress', and 'complete' events.
|
|
342
|
+
*/
|
|
343
|
+
async createHistoryReplay(sessionId) {
|
|
344
|
+
const recorder = this.outputRecorders.get(sessionId);
|
|
345
|
+
if (!recorder) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
const chunks = await recorder.readAll();
|
|
349
|
+
const replay = new HistoryReplay({
|
|
350
|
+
chunkSize: 100,
|
|
351
|
+
batchDelay: 10,
|
|
352
|
+
});
|
|
353
|
+
// Start replay in next tick to allow event handler setup
|
|
354
|
+
setImmediate(() => {
|
|
355
|
+
replay.start(chunks).catch((err) => replay.emit('error', err));
|
|
356
|
+
});
|
|
357
|
+
return replay;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Spawn a PTY process for a session.
|
|
361
|
+
*/
|
|
362
|
+
async spawnProcess(session) {
|
|
363
|
+
const pty = await getPty();
|
|
364
|
+
const ptyProcess = pty.spawn(session.process.shell, session.process.args, {
|
|
365
|
+
name: 'xterm-256color',
|
|
366
|
+
cols: session.terminal.cols,
|
|
367
|
+
rows: session.terminal.rows,
|
|
368
|
+
cwd: session.process.cwd,
|
|
369
|
+
env: {
|
|
370
|
+
...process.env,
|
|
371
|
+
...session.process.env,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
session.process.pid = ptyProcess.pid;
|
|
375
|
+
this.ptyProcesses.set(session.id, ptyProcess);
|
|
376
|
+
// Initialize output recorder for scrollback (disk-backed)
|
|
377
|
+
if (!this.outputRecorders.has(session.id)) {
|
|
378
|
+
const recorder = new OutputRecorder({
|
|
379
|
+
filePath: this.paths.scrollbackFile(session.id),
|
|
380
|
+
maxChunks: this.config.scrollbackLimit,
|
|
381
|
+
});
|
|
382
|
+
await recorder.init();
|
|
383
|
+
this.outputRecorders.set(session.id, recorder);
|
|
384
|
+
}
|
|
385
|
+
// Handle output
|
|
386
|
+
ptyProcess.onData((data) => {
|
|
387
|
+
// Record output to disk-backed scrollback
|
|
388
|
+
const recorder = this.outputRecorders.get(session.id);
|
|
389
|
+
if (recorder) {
|
|
390
|
+
recorder.record(data);
|
|
391
|
+
}
|
|
392
|
+
this.emit('session:output', session.id, data);
|
|
393
|
+
session.lastActivity = Date.now();
|
|
394
|
+
});
|
|
395
|
+
// Handle exit
|
|
396
|
+
ptyProcess.onExit(({ exitCode }) => {
|
|
397
|
+
session.process.pid = null;
|
|
398
|
+
this.ptyProcesses.delete(session.id);
|
|
399
|
+
// Flush scrollback before persisting session
|
|
400
|
+
const recorder = this.outputRecorders.get(session.id);
|
|
401
|
+
if (recorder) {
|
|
402
|
+
recorder.flush().catch(console.error);
|
|
403
|
+
}
|
|
404
|
+
this.persistSession(session).catch(console.error);
|
|
405
|
+
this.emit('session:exit', session.id, exitCode);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Persist a session's metadata to disk.
|
|
410
|
+
*/
|
|
411
|
+
async persistSession(session) {
|
|
412
|
+
const metadataPath = this.paths.metadataFile(session.id);
|
|
413
|
+
const data = JSON.stringify(session, null, 2);
|
|
414
|
+
await fs.writeFile(metadataPath, data, 'utf-8');
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Load persisted sessions from disk.
|
|
418
|
+
*/
|
|
419
|
+
async loadPersistedSessions() {
|
|
420
|
+
const baseDir = this.paths.baseDir;
|
|
421
|
+
if (!existsSync(baseDir)) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
const entries = await fs.readdir(baseDir, { withFileTypes: true });
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
if (!entry.isDirectory())
|
|
427
|
+
continue;
|
|
428
|
+
const sessionId = entry.name;
|
|
429
|
+
const metadataPath = this.paths.metadataFile(sessionId);
|
|
430
|
+
if (!existsSync(metadataPath))
|
|
431
|
+
continue;
|
|
432
|
+
try {
|
|
433
|
+
const data = await fs.readFile(metadataPath, 'utf-8');
|
|
434
|
+
const session = JSON.parse(data);
|
|
435
|
+
// Mark process as not running (it died when server stopped)
|
|
436
|
+
session.process.pid = null;
|
|
437
|
+
this.sessions.set(sessionId, session);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
console.error(`Failed to load session ${sessionId}:`, error);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Generate a display name for a new session.
|
|
446
|
+
* Returns the next available number (1, 2, 3, ...).
|
|
447
|
+
*/
|
|
448
|
+
generateDisplayName() {
|
|
449
|
+
const existingNames = new Set(Array.from(this.sessions.values()).map((s) => s.displayName));
|
|
450
|
+
let num = 1;
|
|
451
|
+
while (existingNames.has(String(num))) {
|
|
452
|
+
num++;
|
|
453
|
+
}
|
|
454
|
+
return String(num);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Clean up all resources.
|
|
458
|
+
*/
|
|
459
|
+
async destroy() {
|
|
460
|
+
// Kill all PTY processes
|
|
461
|
+
for (const pty of this.ptyProcesses.values()) {
|
|
462
|
+
pty.kill();
|
|
463
|
+
}
|
|
464
|
+
this.ptyProcesses.clear();
|
|
465
|
+
// Close all output recorders (flushes pending data)
|
|
466
|
+
for (const recorder of this.outputRecorders.values()) {
|
|
467
|
+
await recorder.close();
|
|
468
|
+
}
|
|
469
|
+
this.outputRecorders.clear();
|
|
470
|
+
// Persist all sessions
|
|
471
|
+
for (const session of this.sessions.values()) {
|
|
472
|
+
await this.persistSession(session);
|
|
473
|
+
}
|
|
474
|
+
this.removeAllListeners();
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Singleton instance for convenience.
|
|
479
|
+
* For most use cases, you can use this directly.
|
|
480
|
+
*/
|
|
481
|
+
let defaultManager = null;
|
|
482
|
+
export function getSessionManager(config) {
|
|
483
|
+
if (!defaultManager) {
|
|
484
|
+
defaultManager = new SessionManager(config);
|
|
485
|
+
}
|
|
486
|
+
return defaultManager;
|
|
487
|
+
}
|
|
488
|
+
export { getDefaultShell, getDefaultEnv };
|
|
489
|
+
//# sourceMappingURL=session-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-manager.js","sourceRoot":"","sources":["session-manager.ts"],"names":[],"mappings":"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"}
|