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.
Files changed (45) hide show
  1. package/dist/agent/runner.js +23 -0
  2. package/dist/agent/session-handler.d.ts +1 -2
  3. package/dist/agent/session-handler.js +34 -79
  4. package/dist/agent/tmux-executor.d.ts +3 -33
  5. package/dist/agent/tmux-executor.js +3 -50
  6. package/dist/agent/tmux.d.ts +2 -6
  7. package/dist/agent/tmux.js +2 -9
  8. package/dist/agent/types.d.ts +0 -10
  9. package/dist/agent/websocket.d.ts +2 -21
  10. package/dist/agent/websocket.js +10 -46
  11. package/dist/commands/agent.js +3 -0
  12. package/dist/index.js +14 -0
  13. package/dist/sentry.d.ts +4 -0
  14. package/dist/sentry.js +87 -0
  15. package/package.json +2 -1
  16. package/dist/autopilot/index.d.ts +0 -94
  17. package/dist/autopilot/index.js +0 -322
  18. package/dist/autopilot/mission-analyzer.d.ts +0 -27
  19. package/dist/autopilot/mission-analyzer.js +0 -232
  20. package/dist/autopilot/project-detector.d.ts +0 -12
  21. package/dist/autopilot/project-detector.js +0 -326
  22. package/dist/autopilot/source-scanner.d.ts +0 -26
  23. package/dist/autopilot/source-scanner.js +0 -285
  24. package/dist/autopilot/speckit-generator.d.ts +0 -60
  25. package/dist/autopilot/speckit-generator.js +0 -511
  26. package/dist/autopilot/types.d.ts +0 -110
  27. package/dist/autopilot/types.js +0 -6
  28. package/dist/autopilot/workflow-generator.d.ts +0 -33
  29. package/dist/autopilot/workflow-generator.js +0 -278
  30. package/dist/commands/autopilot.d.ts +0 -30
  31. package/dist/commands/autopilot.js +0 -262
  32. package/dist/commands/project.d.ts +0 -33
  33. package/dist/commands/project.js +0 -350
  34. package/dist/project/executor.d.ts +0 -73
  35. package/dist/project/executor.js +0 -437
  36. package/dist/project/index.d.ts +0 -4
  37. package/dist/project/index.js +0 -20
  38. package/dist/project/manager.d.ts +0 -66
  39. package/dist/project/manager.js +0 -290
  40. package/dist/project/relay-client.d.ts +0 -37
  41. package/dist/project/relay-client.js +0 -204
  42. package/dist/project/types.d.ts +0 -48
  43. package/dist/project/types.js +0 -3
  44. package/dist/utils/fileUtils.d.ts +0 -28
  45. package/dist/utils/fileUtils.js +0 -159
@@ -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 lastScreens;
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 = 16;
43
- const CAPTURE_INTERVAL_IDLE_MS = 100;
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.lastScreens = new Map();
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, meta) => {
96
- this.handleKeys(keys, meta);
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, meta) {
286
- const target = meta?.pane ? `${this.tmuxSession}.${meta.pane}` : this.tmuxSession;
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 panes = tmux.listPanes(this.tmuxSession);
300
- const now = Date.now();
301
- const isMultiPane = panes.length > 1;
302
- // Check if pane layout changed
303
- const paneLayoutJson = JSON.stringify(panes);
304
- if (paneLayoutJson !== this.lastPaneLayoutJson) {
305
- this.lastPaneLayoutJson = paneLayoutJson;
306
- this.wsClient.sendPaneLayout(panes);
307
- // Clean up screens for removed panes
308
- const currentPaneIds = new Set(panes.map(p => p.id));
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
- let anyChanged = false;
316
- const forceTime = (now - this.lastForceSendTime) >= FORCE_SEND_INTERVAL_MS;
317
- if (isMultiPane) {
318
- // Multi-pane: capture each pane individually
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
- if (anyChanged || forceTime) {
363
- this.lastForceSendTime = now;
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
- // Small delay to ensure register message is processed by server first
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
- listPanes(session: string): {
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
- listPanes(session: string): {
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
- listPanes(session: string): {
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
- listPanes(session) {
63
+ capturePane(session) {
64
64
  try {
65
- const output = (0, child_process_1.execSync)(`tmux list-panes -t "${session}" -F "#{pane_id}:#{pane_index}:#{pane_width}:#{pane_height}:#{pane_top}:#{pane_left}:#{pane_active}:#{pane_title}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
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
- listPanes(session) {
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;
@@ -1,4 +1,4 @@
1
- import { TmuxSession, PaneInfo } from './types';
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, paneId?: string): string | null;
13
+ export declare function capturePane(sessionName: string): string | null;
18
14
  /**
19
15
  * Send keys to tmux session
20
16
  */
@@ -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, paneId) {
63
- return getExecutor().capturePane(sessionName, paneId);
55
+ function capturePane(sessionName) {
56
+ return getExecutor().capturePane(sessionName);
64
57
  }
65
58
  /**
66
59
  * Send keys to tmux session
@@ -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, paneMeta?: {
36
- pane: string;
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
@@ -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 = 50;
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, message.meta);
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, paneMeta) {
242
+ sendScreen(data) {
261
243
  if (!this.isConnected)
262
244
  return false;
263
245
  const base64Data = data.toString('base64');
264
- const msg = {
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, paneMeta) {
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
- const msg = {
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, paneMeta);
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;
@@ -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';
@@ -0,0 +1,4 @@
1
+ export declare function initSentry(): void;
2
+ export declare function captureException(error: unknown): void;
3
+ export declare function setUser(email: string): void;
4
+ export declare function flush(): Promise<void>;