sessioncast-cli 2.0.2 → 2.0.3
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/dist/agent/runner.js +23 -0
- package/dist/agent/session-handler.d.ts +1 -2
- package/dist/agent/session-handler.js +34 -79
- package/dist/agent/tmux-executor.d.ts +3 -33
- package/dist/agent/tmux-executor.js +3 -50
- package/dist/agent/tmux.d.ts +2 -6
- package/dist/agent/tmux.js +2 -9
- package/dist/agent/types.d.ts +0 -10
- package/dist/agent/websocket.d.ts +2 -21
- package/dist/agent/websocket.js +10 -46
- package/dist/commands/agent.js +3 -0
- package/dist/index.js +14 -0
- package/dist/sentry.d.ts +4 -0
- package/dist/sentry.js +87 -0
- package/package.json +2 -1
- package/dist/autopilot/index.d.ts +0 -94
- package/dist/autopilot/index.js +0 -322
- package/dist/autopilot/mission-analyzer.d.ts +0 -27
- package/dist/autopilot/mission-analyzer.js +0 -232
- package/dist/autopilot/project-detector.d.ts +0 -12
- package/dist/autopilot/project-detector.js +0 -326
- package/dist/autopilot/source-scanner.d.ts +0 -26
- package/dist/autopilot/source-scanner.js +0 -285
- package/dist/autopilot/speckit-generator.d.ts +0 -60
- package/dist/autopilot/speckit-generator.js +0 -511
- package/dist/autopilot/types.d.ts +0 -110
- package/dist/autopilot/types.js +0 -6
- package/dist/autopilot/workflow-generator.d.ts +0 -33
- package/dist/autopilot/workflow-generator.js +0 -278
- package/dist/commands/autopilot.d.ts +0 -30
- package/dist/commands/autopilot.js +0 -262
- package/dist/commands/project.d.ts +0 -33
- package/dist/commands/project.js +0 -350
- package/dist/project/executor.d.ts +0 -73
- package/dist/project/executor.js +0 -437
- package/dist/project/index.d.ts +0 -4
- package/dist/project/index.js +0 -20
- package/dist/project/manager.d.ts +0 -66
- package/dist/project/manager.js +0 -290
- package/dist/project/relay-client.d.ts +0 -37
- package/dist/project/relay-client.js +0 -204
- package/dist/project/types.d.ts +0 -48
- package/dist/project/types.js +0 -3
- package/dist/utils/fileUtils.d.ts +0 -28
- package/dist/utils/fileUtils.js +0 -159
package/dist/agent/runner.js
CHANGED
|
@@ -42,6 +42,7 @@ const session_handler_1 = require("./session-handler");
|
|
|
42
42
|
const api_client_1 = require("./api-client");
|
|
43
43
|
const tmux = __importStar(require("./tmux"));
|
|
44
44
|
const config_1 = require("../config");
|
|
45
|
+
const sentry_1 = require("../sentry");
|
|
45
46
|
const SCAN_INTERVAL_MS = 5000;
|
|
46
47
|
class AgentRunner {
|
|
47
48
|
constructor(config) {
|
|
@@ -105,6 +106,27 @@ class AgentRunner {
|
|
|
105
106
|
console.log(`Machine ID: ${this.config.machineId}`);
|
|
106
107
|
console.log(`Relay: ${this.config.relay}`);
|
|
107
108
|
console.log(`Token: ${this.config.token ? 'present' : 'none'}`);
|
|
109
|
+
// Check tmux availability before starting
|
|
110
|
+
if (!tmux.isAvailable()) {
|
|
111
|
+
const platform = os.platform();
|
|
112
|
+
let installHint;
|
|
113
|
+
if (platform === 'darwin') {
|
|
114
|
+
installHint = ' Install: brew install tmux';
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
installHint = ' Install: sudo apt install tmux (Debian/Ubuntu)\n' +
|
|
118
|
+
' sudo yum install tmux (RHEL/CentOS)';
|
|
119
|
+
}
|
|
120
|
+
throw new Error('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' +
|
|
121
|
+
' tmux not found - required for SessionCast Agent\n' +
|
|
122
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n' +
|
|
123
|
+
`${installHint}\n\n` +
|
|
124
|
+
' After installing, start a tmux session:\n' +
|
|
125
|
+
' tmux new -s main\n\n' +
|
|
126
|
+
' Then run the agent:\n' +
|
|
127
|
+
' sessioncast agent\n\n' +
|
|
128
|
+
'━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
129
|
+
}
|
|
108
130
|
// Start API client if configured
|
|
109
131
|
if (this.config.api?.enabled && this.config.api.agentId) {
|
|
110
132
|
this.apiClient = new api_client_1.ApiWebSocketClient(this.config);
|
|
@@ -139,6 +161,7 @@ class AgentRunner {
|
|
|
139
161
|
}
|
|
140
162
|
}
|
|
141
163
|
catch (error) {
|
|
164
|
+
(0, sentry_1.captureException)(error);
|
|
142
165
|
console.error('Error during session scan:', error);
|
|
143
166
|
}
|
|
144
167
|
}
|
|
@@ -11,8 +11,7 @@ export declare class TmuxSessionHandler {
|
|
|
11
11
|
private wsClient;
|
|
12
12
|
private onCreateSession?;
|
|
13
13
|
private running;
|
|
14
|
-
private
|
|
15
|
-
private lastPaneLayoutJson;
|
|
14
|
+
private lastScreen;
|
|
16
15
|
private lastForceSendTime;
|
|
17
16
|
private lastChangeTime;
|
|
18
17
|
private captureTimer;
|
|
@@ -38,9 +38,10 @@ const websocket_1 = require("./websocket");
|
|
|
38
38
|
const tmux = __importStar(require("./tmux"));
|
|
39
39
|
const fs = __importStar(require("fs"));
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
|
+
const sentry_1 = require("../sentry");
|
|
41
42
|
// Capture intervals
|
|
42
|
-
const CAPTURE_INTERVAL_ACTIVE_MS =
|
|
43
|
-
const CAPTURE_INTERVAL_IDLE_MS =
|
|
43
|
+
const CAPTURE_INTERVAL_ACTIVE_MS = 50;
|
|
44
|
+
const CAPTURE_INTERVAL_IDLE_MS = 200;
|
|
44
45
|
const ACTIVE_THRESHOLD_MS = 2000;
|
|
45
46
|
const FORCE_SEND_INTERVAL_MS = 10000;
|
|
46
47
|
const USE_COMPRESSION = true;
|
|
@@ -49,8 +50,7 @@ class TmuxSessionHandler {
|
|
|
49
50
|
constructor(options) {
|
|
50
51
|
this.wsClient = null;
|
|
51
52
|
this.running = false;
|
|
52
|
-
this.
|
|
53
|
-
this.lastPaneLayoutJson = '';
|
|
53
|
+
this.lastScreen = '';
|
|
54
54
|
this.lastForceSendTime = 0;
|
|
55
55
|
this.lastChangeTime = 0;
|
|
56
56
|
this.captureTimer = null;
|
|
@@ -84,16 +84,14 @@ class TmuxSessionHandler {
|
|
|
84
84
|
});
|
|
85
85
|
this.wsClient.on('connected', () => {
|
|
86
86
|
console.log(`[${this.tmuxSession}] Connected to relay`);
|
|
87
|
-
// Reset pane layout cache so it gets re-sent on reconnection
|
|
88
|
-
this.lastPaneLayoutJson = '';
|
|
89
87
|
this.startScreenCapture();
|
|
90
88
|
});
|
|
91
89
|
this.wsClient.on('disconnected', ({ code, reason }) => {
|
|
92
90
|
console.log(`[${this.tmuxSession}] Disconnected: code=${code}, reason=${reason}`);
|
|
93
91
|
this.stopScreenCapture();
|
|
94
92
|
});
|
|
95
|
-
this.wsClient.on('keys', (keys
|
|
96
|
-
this.handleKeys(keys
|
|
93
|
+
this.wsClient.on('keys', (keys) => {
|
|
94
|
+
this.handleKeys(keys);
|
|
97
95
|
});
|
|
98
96
|
this.wsClient.on('resize', ({ cols, rows }) => {
|
|
99
97
|
console.log(`[${this.tmuxSession}] Resize: ${cols}x${rows}`);
|
|
@@ -117,6 +115,7 @@ class TmuxSessionHandler {
|
|
|
117
115
|
this.handleUploadChunk(chunk);
|
|
118
116
|
});
|
|
119
117
|
this.wsClient.on('error', (error) => {
|
|
118
|
+
(0, sentry_1.captureException)(error);
|
|
120
119
|
console.error(`[${this.tmuxSession}] WebSocket error:`, error.message);
|
|
121
120
|
});
|
|
122
121
|
this.wsClient.connect();
|
|
@@ -282,9 +281,8 @@ class TmuxSessionHandler {
|
|
|
282
281
|
};
|
|
283
282
|
return types[ext] || 'application/octet-stream';
|
|
284
283
|
}
|
|
285
|
-
handleKeys(keys
|
|
286
|
-
|
|
287
|
-
tmux.sendKeys(target, keys, false);
|
|
284
|
+
handleKeys(keys) {
|
|
285
|
+
tmux.sendKeys(this.tmuxSession, keys, false);
|
|
288
286
|
}
|
|
289
287
|
startScreenCapture() {
|
|
290
288
|
if (this.captureTimer)
|
|
@@ -296,86 +294,43 @@ class TmuxSessionHandler {
|
|
|
296
294
|
return;
|
|
297
295
|
}
|
|
298
296
|
try {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
for (const key of this.lastScreens.keys()) {
|
|
310
|
-
if (!currentPaneIds.has(key)) {
|
|
311
|
-
this.lastScreens.delete(key);
|
|
297
|
+
const screen = tmux.capturePane(this.tmuxSession);
|
|
298
|
+
if (screen !== null) {
|
|
299
|
+
const now = Date.now();
|
|
300
|
+
const changed = screen !== this.lastScreen;
|
|
301
|
+
const forceTime = (now - this.lastForceSendTime) >= FORCE_SEND_INTERVAL_MS;
|
|
302
|
+
if (changed || forceTime) {
|
|
303
|
+
this.lastScreen = screen;
|
|
304
|
+
this.lastForceSendTime = now;
|
|
305
|
+
if (changed) {
|
|
306
|
+
this.lastChangeTime = now;
|
|
312
307
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for (const pane of panes) {
|
|
320
|
-
const screen = tmux.capturePane(this.tmuxSession, pane.id);
|
|
321
|
-
if (screen !== null) {
|
|
322
|
-
const lastScreen = this.lastScreens.get(pane.id) || '';
|
|
323
|
-
const changed = screen !== lastScreen;
|
|
324
|
-
if (changed || forceTime) {
|
|
325
|
-
this.lastScreens.set(pane.id, screen);
|
|
326
|
-
if (changed)
|
|
327
|
-
anyChanged = true;
|
|
328
|
-
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
329
|
-
const data = Buffer.from(fullOutput, 'utf-8');
|
|
330
|
-
const paneMeta = { pane: pane.id, index: pane.index };
|
|
331
|
-
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
332
|
-
this.wsClient.sendScreenCompressed(data, paneMeta);
|
|
333
|
-
}
|
|
334
|
-
else {
|
|
335
|
-
this.wsClient.sendScreen(data, paneMeta);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
308
|
+
// Send clear screen + content
|
|
309
|
+
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
310
|
+
const data = Buffer.from(fullOutput, 'utf-8');
|
|
311
|
+
// Compress if enabled and data is large enough
|
|
312
|
+
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
313
|
+
this.wsClient.sendScreenCompressed(data);
|
|
338
314
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
else {
|
|
342
|
-
// Single pane: backward compatible (no meta)
|
|
343
|
-
const screen = tmux.capturePane(this.tmuxSession);
|
|
344
|
-
if (screen !== null) {
|
|
345
|
-
const lastScreen = this.lastScreens.get('_single') || '';
|
|
346
|
-
const changed = screen !== lastScreen;
|
|
347
|
-
if (changed || forceTime) {
|
|
348
|
-
this.lastScreens.set('_single', screen);
|
|
349
|
-
if (changed)
|
|
350
|
-
anyChanged = true;
|
|
351
|
-
const fullOutput = '\x1b[2J\x1b[H' + screen;
|
|
352
|
-
const data = Buffer.from(fullOutput, 'utf-8');
|
|
353
|
-
if (USE_COMPRESSION && data.length > MIN_COMPRESS_SIZE) {
|
|
354
|
-
this.wsClient.sendScreenCompressed(data);
|
|
355
|
-
}
|
|
356
|
-
else {
|
|
357
|
-
this.wsClient.sendScreen(data);
|
|
358
|
-
}
|
|
315
|
+
else {
|
|
316
|
+
this.wsClient.sendScreen(data);
|
|
359
317
|
}
|
|
360
318
|
}
|
|
319
|
+
// Adaptive sleep: faster when active, slower when idle
|
|
320
|
+
const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
|
|
321
|
+
const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
|
|
322
|
+
this.captureTimer = setTimeout(capture, sleepMs);
|
|
361
323
|
}
|
|
362
|
-
|
|
363
|
-
this.
|
|
364
|
-
if (anyChanged) {
|
|
365
|
-
this.lastChangeTime = now;
|
|
366
|
-
}
|
|
324
|
+
else {
|
|
325
|
+
this.captureTimer = setTimeout(capture, CAPTURE_INTERVAL_IDLE_MS);
|
|
367
326
|
}
|
|
368
|
-
const isActive = (now - this.lastChangeTime) < ACTIVE_THRESHOLD_MS;
|
|
369
|
-
const sleepMs = isActive ? CAPTURE_INTERVAL_ACTIVE_MS : CAPTURE_INTERVAL_IDLE_MS;
|
|
370
|
-
this.captureTimer = setTimeout(capture, sleepMs);
|
|
371
327
|
}
|
|
372
328
|
catch (error) {
|
|
373
329
|
console.error(`[${this.tmuxSession}] Screen capture error:`, error);
|
|
374
330
|
this.captureTimer = setTimeout(capture, 500);
|
|
375
331
|
}
|
|
376
332
|
};
|
|
377
|
-
|
|
378
|
-
setTimeout(capture, 300);
|
|
333
|
+
capture();
|
|
379
334
|
console.log(`[${this.tmuxSession}] Screen capture started`);
|
|
380
335
|
}
|
|
381
336
|
stopScreenCapture() {
|
|
@@ -4,17 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export interface TmuxExecutor {
|
|
6
6
|
listSessions(): string[];
|
|
7
|
-
|
|
8
|
-
id: string;
|
|
9
|
-
index: number;
|
|
10
|
-
width: number;
|
|
11
|
-
height: number;
|
|
12
|
-
top: number;
|
|
13
|
-
left: number;
|
|
14
|
-
active: boolean;
|
|
15
|
-
title: string;
|
|
16
|
-
}[];
|
|
17
|
-
capturePane(session: string, paneId?: string): string | null;
|
|
7
|
+
capturePane(session: string): string | null;
|
|
18
8
|
sendKeys(session: string, keys: string): boolean;
|
|
19
9
|
sendSpecialKey(session: string, key: string): boolean;
|
|
20
10
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -30,17 +20,7 @@ export interface TmuxExecutor {
|
|
|
30
20
|
*/
|
|
31
21
|
export declare class UnixTmuxExecutor implements TmuxExecutor {
|
|
32
22
|
listSessions(): string[];
|
|
33
|
-
|
|
34
|
-
id: string;
|
|
35
|
-
index: number;
|
|
36
|
-
width: number;
|
|
37
|
-
height: number;
|
|
38
|
-
top: number;
|
|
39
|
-
left: number;
|
|
40
|
-
active: boolean;
|
|
41
|
-
title: string;
|
|
42
|
-
}[];
|
|
43
|
-
capturePane(session: string, paneId?: string): string | null;
|
|
23
|
+
capturePane(session: string): string | null;
|
|
44
24
|
sendKeys(session: string, keys: string): boolean;
|
|
45
25
|
sendSpecialKey(session: string, key: string): boolean;
|
|
46
26
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -61,17 +41,7 @@ export declare class WindowsTmuxExecutor implements TmuxExecutor {
|
|
|
61
41
|
constructor(itmuxPath: string);
|
|
62
42
|
private executeCommand;
|
|
63
43
|
listSessions(): string[];
|
|
64
|
-
|
|
65
|
-
id: string;
|
|
66
|
-
index: number;
|
|
67
|
-
width: number;
|
|
68
|
-
height: number;
|
|
69
|
-
top: number;
|
|
70
|
-
left: number;
|
|
71
|
-
active: boolean;
|
|
72
|
-
title: string;
|
|
73
|
-
}[];
|
|
74
|
-
capturePane(session: string, paneId?: string): string | null;
|
|
44
|
+
capturePane(session: string): string | null;
|
|
75
45
|
sendKeys(session: string, keys: string): boolean;
|
|
76
46
|
sendSpecialKey(session: string, key: string): boolean;
|
|
77
47
|
resizeWindow(session: string, cols: number, rows: number): boolean;
|
|
@@ -60,31 +60,9 @@ class UnixTmuxExecutor {
|
|
|
60
60
|
return [];
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
-
|
|
63
|
+
capturePane(session) {
|
|
64
64
|
try {
|
|
65
|
-
const output = (0, child_process_1.execSync)(`tmux
|
|
66
|
-
return output.trim().split('\n').filter(l => l.length > 0).map(line => {
|
|
67
|
-
const parts = line.split(':');
|
|
68
|
-
return {
|
|
69
|
-
id: parts[0],
|
|
70
|
-
index: parseInt(parts[1], 10),
|
|
71
|
-
width: parseInt(parts[2], 10),
|
|
72
|
-
height: parseInt(parts[3], 10),
|
|
73
|
-
top: parseInt(parts[4], 10),
|
|
74
|
-
left: parseInt(parts[5], 10),
|
|
75
|
-
active: parts[6] === '1',
|
|
76
|
-
title: parts.slice(7).join(':') // title may contain colons
|
|
77
|
-
};
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return [];
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
capturePane(session, paneId) {
|
|
85
|
-
try {
|
|
86
|
-
const target = paneId ? `${session}.${paneId}` : session;
|
|
87
|
-
const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${target}" -p -e -N`, {
|
|
65
|
+
const output = (0, child_process_1.execSync)(`tmux capture-pane -t "${session}" -p -e -N`, {
|
|
88
66
|
encoding: 'utf-8',
|
|
89
67
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
90
68
|
maxBuffer: 10 * 1024 * 1024
|
|
@@ -245,34 +223,9 @@ class WindowsTmuxExecutor {
|
|
|
245
223
|
return [];
|
|
246
224
|
}
|
|
247
225
|
}
|
|
248
|
-
|
|
226
|
+
capturePane(session) {
|
|
249
227
|
try {
|
|
250
228
|
const escaped = this.escapeSession(session);
|
|
251
|
-
const output = this.executeCommand(`tmux list-panes -t '${escaped}' -F '#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{pane_active}:#{pane_title}'`);
|
|
252
|
-
if (!output)
|
|
253
|
-
return [];
|
|
254
|
-
return output.split('\n').map(s => s.trim()).filter(l => l.length > 0).map(line => {
|
|
255
|
-
const parts = line.split(':');
|
|
256
|
-
return {
|
|
257
|
-
id: parts[0],
|
|
258
|
-
index: parseInt(parts[1], 10),
|
|
259
|
-
width: parseInt(parts[2], 10),
|
|
260
|
-
height: parseInt(parts[3], 10),
|
|
261
|
-
top: parseInt(parts[4], 10),
|
|
262
|
-
left: parseInt(parts[5], 10),
|
|
263
|
-
active: parts[6] === '1',
|
|
264
|
-
title: parts.slice(7).join(':')
|
|
265
|
-
};
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
catch {
|
|
269
|
-
return [];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
capturePane(session, paneId) {
|
|
273
|
-
try {
|
|
274
|
-
const target = paneId ? `${session}.${paneId}` : session;
|
|
275
|
-
const escaped = this.escapeSession(target);
|
|
276
229
|
const output = this.executeCommand(`tmux capture-pane -t '${escaped}' -p -e -N`);
|
|
277
230
|
if (!output)
|
|
278
231
|
return null;
|
package/dist/agent/tmux.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TmuxSession
|
|
1
|
+
import { TmuxSession } from './types';
|
|
2
2
|
/**
|
|
3
3
|
* Scan for all tmux sessions
|
|
4
4
|
*/
|
|
@@ -7,14 +7,10 @@ export declare function scanSessions(): string[];
|
|
|
7
7
|
* Get detailed session info
|
|
8
8
|
*/
|
|
9
9
|
export declare function listSessions(): TmuxSession[];
|
|
10
|
-
/**
|
|
11
|
-
* List all panes in a tmux session
|
|
12
|
-
*/
|
|
13
|
-
export declare function listPanes(sessionName: string): PaneInfo[];
|
|
14
10
|
/**
|
|
15
11
|
* Capture tmux pane content with escape sequences (colors)
|
|
16
12
|
*/
|
|
17
|
-
export declare function capturePane(sessionName: string
|
|
13
|
+
export declare function capturePane(sessionName: string): string | null;
|
|
18
14
|
/**
|
|
19
15
|
* Send keys to tmux session
|
|
20
16
|
*/
|
package/dist/agent/tmux.js
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.scanSessions = scanSessions;
|
|
4
4
|
exports.listSessions = listSessions;
|
|
5
|
-
exports.listPanes = listPanes;
|
|
6
5
|
exports.capturePane = capturePane;
|
|
7
6
|
exports.sendKeys = sendKeys;
|
|
8
7
|
exports.resizeWindow = resizeWindow;
|
|
@@ -50,17 +49,11 @@ function listSessions() {
|
|
|
50
49
|
return [];
|
|
51
50
|
}
|
|
52
51
|
}
|
|
53
|
-
/**
|
|
54
|
-
* List all panes in a tmux session
|
|
55
|
-
*/
|
|
56
|
-
function listPanes(sessionName) {
|
|
57
|
-
return getExecutor().listPanes(sessionName);
|
|
58
|
-
}
|
|
59
52
|
/**
|
|
60
53
|
* Capture tmux pane content with escape sequences (colors)
|
|
61
54
|
*/
|
|
62
|
-
function capturePane(sessionName
|
|
63
|
-
return getExecutor().capturePane(sessionName
|
|
55
|
+
function capturePane(sessionName) {
|
|
56
|
+
return getExecutor().capturePane(sessionName);
|
|
64
57
|
}
|
|
65
58
|
/**
|
|
66
59
|
* Send keys to tmux session
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -37,16 +37,6 @@ export interface TmuxSession {
|
|
|
37
37
|
created?: string;
|
|
38
38
|
attached: boolean;
|
|
39
39
|
}
|
|
40
|
-
export interface PaneInfo {
|
|
41
|
-
id: string;
|
|
42
|
-
index: number;
|
|
43
|
-
width: number;
|
|
44
|
-
height: number;
|
|
45
|
-
top: number;
|
|
46
|
-
left: number;
|
|
47
|
-
active: boolean;
|
|
48
|
-
title: string;
|
|
49
|
-
}
|
|
50
40
|
export interface ExecResult {
|
|
51
41
|
exitCode: number;
|
|
52
42
|
stdout: string;
|
|
@@ -21,35 +21,16 @@ export declare class RelayWebSocketClient extends EventEmitter {
|
|
|
21
21
|
private circuitBreakerOpen;
|
|
22
22
|
private circuitBreakerResetTime;
|
|
23
23
|
private reconnectTimer;
|
|
24
|
-
private pingTimer;
|
|
25
24
|
private destroyed;
|
|
26
25
|
constructor(options: WebSocketClientOptions);
|
|
27
26
|
connect(): void;
|
|
28
27
|
private registerAsHost;
|
|
29
28
|
private handleMessage;
|
|
30
29
|
private handleError;
|
|
31
|
-
private startPing;
|
|
32
|
-
private stopPing;
|
|
33
30
|
private scheduleReconnect;
|
|
34
31
|
send(message: Message): boolean;
|
|
35
|
-
sendScreen(data: Buffer
|
|
36
|
-
|
|
37
|
-
index: number;
|
|
38
|
-
}): boolean;
|
|
39
|
-
sendScreenCompressed(data: Buffer, paneMeta?: {
|
|
40
|
-
pane: string;
|
|
41
|
-
index: number;
|
|
42
|
-
}): boolean;
|
|
43
|
-
sendPaneLayout(panes: {
|
|
44
|
-
id: string;
|
|
45
|
-
index: number;
|
|
46
|
-
width: number;
|
|
47
|
-
height: number;
|
|
48
|
-
top: number;
|
|
49
|
-
left: number;
|
|
50
|
-
active: boolean;
|
|
51
|
-
title: string;
|
|
52
|
-
}[]): boolean;
|
|
32
|
+
sendScreen(data: Buffer): boolean;
|
|
33
|
+
sendScreenCompressed(data: Buffer): boolean;
|
|
53
34
|
/**
|
|
54
35
|
* Send file content to be displayed in the web FileViewer
|
|
55
36
|
* @param filename - The name of the file
|
package/dist/agent/websocket.js
CHANGED
|
@@ -40,11 +40,10 @@ exports.RelayWebSocketClient = void 0;
|
|
|
40
40
|
const ws_1 = __importDefault(require("ws"));
|
|
41
41
|
const events_1 = require("events");
|
|
42
42
|
const zlib = __importStar(require("zlib"));
|
|
43
|
-
const MAX_RECONNECT_ATTEMPTS =
|
|
43
|
+
const MAX_RECONNECT_ATTEMPTS = 5;
|
|
44
44
|
const BASE_RECONNECT_DELAY_MS = 2000;
|
|
45
45
|
const MAX_RECONNECT_DELAY_MS = 60000;
|
|
46
46
|
const CIRCUIT_BREAKER_DURATION_MS = 120000;
|
|
47
|
-
const PING_INTERVAL_MS = 30000;
|
|
48
47
|
class RelayWebSocketClient extends events_1.EventEmitter {
|
|
49
48
|
constructor(options) {
|
|
50
49
|
super();
|
|
@@ -54,7 +53,6 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
54
53
|
this.circuitBreakerOpen = false;
|
|
55
54
|
this.circuitBreakerResetTime = 0;
|
|
56
55
|
this.reconnectTimer = null;
|
|
57
|
-
this.pingTimer = null;
|
|
58
56
|
this.destroyed = false;
|
|
59
57
|
this.url = options.url;
|
|
60
58
|
this.sessionId = options.sessionId;
|
|
@@ -72,9 +70,8 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
72
70
|
this.isConnected = true;
|
|
73
71
|
this.reconnectAttempts = 0;
|
|
74
72
|
this.circuitBreakerOpen = false;
|
|
75
|
-
this.registerAsHost();
|
|
76
|
-
this.startPing();
|
|
77
73
|
this.emit('connected');
|
|
74
|
+
this.registerAsHost();
|
|
78
75
|
});
|
|
79
76
|
this.ws.on('message', (data) => {
|
|
80
77
|
try {
|
|
@@ -87,7 +84,6 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
87
84
|
});
|
|
88
85
|
this.ws.on('close', (code, reason) => {
|
|
89
86
|
this.isConnected = false;
|
|
90
|
-
this.stopPing();
|
|
91
87
|
this.emit('disconnected', { code, reason: reason.toString() });
|
|
92
88
|
if (this.autoReconnect && !this.destroyed) {
|
|
93
89
|
this.scheduleReconnect();
|
|
@@ -123,7 +119,7 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
123
119
|
switch (message.type) {
|
|
124
120
|
case 'keys':
|
|
125
121
|
if (message.session === this.sessionId && message.payload) {
|
|
126
|
-
this.emit('keys', message.payload
|
|
122
|
+
this.emit('keys', message.payload);
|
|
127
123
|
}
|
|
128
124
|
break;
|
|
129
125
|
case 'resize':
|
|
@@ -192,20 +188,6 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
192
188
|
console.error(`Error: code=${meta.code}, message=${meta.messageEn}`);
|
|
193
189
|
}
|
|
194
190
|
}
|
|
195
|
-
startPing() {
|
|
196
|
-
this.stopPing();
|
|
197
|
-
this.pingTimer = setInterval(() => {
|
|
198
|
-
if (this.ws && this.ws.readyState === ws_1.default.OPEN) {
|
|
199
|
-
this.ws.ping();
|
|
200
|
-
}
|
|
201
|
-
}, PING_INTERVAL_MS);
|
|
202
|
-
}
|
|
203
|
-
stopPing() {
|
|
204
|
-
if (this.pingTimer) {
|
|
205
|
-
clearInterval(this.pingTimer);
|
|
206
|
-
this.pingTimer = null;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
191
|
scheduleReconnect() {
|
|
210
192
|
if (this.destroyed)
|
|
211
193
|
return;
|
|
@@ -257,49 +239,32 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
257
239
|
return false;
|
|
258
240
|
}
|
|
259
241
|
}
|
|
260
|
-
sendScreen(data
|
|
242
|
+
sendScreen(data) {
|
|
261
243
|
if (!this.isConnected)
|
|
262
244
|
return false;
|
|
263
245
|
const base64Data = data.toString('base64');
|
|
264
|
-
|
|
246
|
+
return this.send({
|
|
265
247
|
type: 'screen',
|
|
266
248
|
session: this.sessionId,
|
|
267
249
|
payload: base64Data
|
|
268
|
-
};
|
|
269
|
-
if (paneMeta) {
|
|
270
|
-
msg.meta = { pane: paneMeta.pane, index: String(paneMeta.index) };
|
|
271
|
-
}
|
|
272
|
-
return this.send(msg);
|
|
250
|
+
});
|
|
273
251
|
}
|
|
274
|
-
sendScreenCompressed(data
|
|
252
|
+
sendScreenCompressed(data) {
|
|
275
253
|
if (!this.isConnected)
|
|
276
254
|
return false;
|
|
277
255
|
try {
|
|
278
256
|
const compressed = zlib.gzipSync(data);
|
|
279
257
|
const base64Data = compressed.toString('base64');
|
|
280
|
-
|
|
258
|
+
return this.send({
|
|
281
259
|
type: 'screenGz',
|
|
282
260
|
session: this.sessionId,
|
|
283
261
|
payload: base64Data
|
|
284
|
-
};
|
|
285
|
-
if (paneMeta) {
|
|
286
|
-
msg.meta = { pane: paneMeta.pane, index: String(paneMeta.index) };
|
|
287
|
-
}
|
|
288
|
-
return this.send(msg);
|
|
262
|
+
});
|
|
289
263
|
}
|
|
290
264
|
catch {
|
|
291
|
-
return this.sendScreen(data
|
|
265
|
+
return this.sendScreen(data);
|
|
292
266
|
}
|
|
293
267
|
}
|
|
294
|
-
sendPaneLayout(panes) {
|
|
295
|
-
if (!this.isConnected)
|
|
296
|
-
return false;
|
|
297
|
-
return this.send({
|
|
298
|
-
type: 'paneLayout',
|
|
299
|
-
session: this.sessionId,
|
|
300
|
-
payload: JSON.stringify(panes)
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
268
|
/**
|
|
304
269
|
* Send file content to be displayed in the web FileViewer
|
|
305
270
|
* @param filename - The name of the file
|
|
@@ -358,7 +323,6 @@ class RelayWebSocketClient extends events_1.EventEmitter {
|
|
|
358
323
|
destroy() {
|
|
359
324
|
this.destroyed = true;
|
|
360
325
|
this.autoReconnect = false;
|
|
361
|
-
this.stopPing();
|
|
362
326
|
if (this.reconnectTimer) {
|
|
363
327
|
clearTimeout(this.reconnectTimer);
|
|
364
328
|
this.reconnectTimer = null;
|
package/dist/commands/agent.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.startAgent = startAgent;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const runner_1 = require("../agent/runner");
|
|
9
|
+
const sentry_1 = require("../sentry");
|
|
9
10
|
async function startAgent(options) {
|
|
10
11
|
try {
|
|
11
12
|
const config = runner_1.AgentRunner.loadConfig(options.config);
|
|
@@ -13,6 +14,8 @@ async function startAgent(options) {
|
|
|
13
14
|
await runner.start();
|
|
14
15
|
}
|
|
15
16
|
catch (error) {
|
|
17
|
+
(0, sentry_1.captureException)(error);
|
|
18
|
+
await (0, sentry_1.flush)();
|
|
16
19
|
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
17
20
|
process.exit(1);
|
|
18
21
|
}
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,20 @@ const sessions_1 = require("./commands/sessions");
|
|
|
47
47
|
const sendkeys_1 = require("./commands/sendkeys");
|
|
48
48
|
const agent_1 = require("./commands/agent");
|
|
49
49
|
const config_1 = require("./config");
|
|
50
|
+
const sentry_1 = require("./sentry");
|
|
51
|
+
// Initialize Sentry as early as possible
|
|
52
|
+
(0, sentry_1.initSentry)();
|
|
53
|
+
// Catch unhandled errors globally
|
|
54
|
+
process.on('uncaughtException', async (error) => {
|
|
55
|
+
(0, sentry_1.captureException)(error);
|
|
56
|
+
await (0, sentry_1.flush)();
|
|
57
|
+
console.error(chalk_1.default.red(`Fatal error: ${error.message}`));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
});
|
|
60
|
+
process.on('unhandledRejection', async (reason) => {
|
|
61
|
+
(0, sentry_1.captureException)(reason);
|
|
62
|
+
await (0, sentry_1.flush)();
|
|
63
|
+
});
|
|
50
64
|
// Check if tmux/itmux is available
|
|
51
65
|
function checkTmux() {
|
|
52
66
|
const isWindows = os.platform() === 'win32';
|
package/dist/sentry.d.ts
ADDED