episoda 0.2.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.
Files changed (111) hide show
  1. package/dist/commands/auth.d.ts +22 -0
  2. package/dist/commands/auth.d.ts.map +1 -0
  3. package/dist/commands/auth.js +384 -0
  4. package/dist/commands/auth.js.map +1 -0
  5. package/dist/commands/dev.d.ts +20 -0
  6. package/dist/commands/dev.d.ts.map +1 -0
  7. package/dist/commands/dev.js +305 -0
  8. package/dist/commands/dev.js.map +1 -0
  9. package/dist/commands/status.d.ts +9 -0
  10. package/dist/commands/status.d.ts.map +1 -0
  11. package/dist/commands/status.js +75 -0
  12. package/dist/commands/status.js.map +1 -0
  13. package/dist/commands/stop.d.ts +17 -0
  14. package/dist/commands/stop.d.ts.map +1 -0
  15. package/dist/commands/stop.js +81 -0
  16. package/dist/commands/stop.js.map +1 -0
  17. package/dist/core/auth.d.ts +26 -0
  18. package/dist/core/auth.d.ts.map +1 -0
  19. package/dist/core/auth.js +113 -0
  20. package/dist/core/auth.js.map +1 -0
  21. package/dist/core/command-protocol.d.ts +262 -0
  22. package/dist/core/command-protocol.d.ts.map +1 -0
  23. package/dist/core/command-protocol.js +13 -0
  24. package/dist/core/command-protocol.js.map +1 -0
  25. package/dist/core/connection-manager.d.ts +58 -0
  26. package/dist/core/connection-manager.d.ts.map +1 -0
  27. package/dist/core/connection-manager.js +215 -0
  28. package/dist/core/connection-manager.js.map +1 -0
  29. package/dist/core/errors.d.ts +18 -0
  30. package/dist/core/errors.d.ts.map +1 -0
  31. package/dist/core/errors.js +55 -0
  32. package/dist/core/errors.js.map +1 -0
  33. package/dist/core/git-executor.d.ts +157 -0
  34. package/dist/core/git-executor.d.ts.map +1 -0
  35. package/dist/core/git-executor.js +1605 -0
  36. package/dist/core/git-executor.js.map +1 -0
  37. package/dist/core/git-parser.d.ts +40 -0
  38. package/dist/core/git-parser.d.ts.map +1 -0
  39. package/dist/core/git-parser.js +194 -0
  40. package/dist/core/git-parser.js.map +1 -0
  41. package/dist/core/git-validator.d.ts +42 -0
  42. package/dist/core/git-validator.d.ts.map +1 -0
  43. package/dist/core/git-validator.js +102 -0
  44. package/dist/core/git-validator.js.map +1 -0
  45. package/dist/core/index.d.ts +17 -0
  46. package/dist/core/index.d.ts.map +1 -0
  47. package/dist/core/index.js +41 -0
  48. package/dist/core/index.js.map +1 -0
  49. package/dist/core/version.d.ts +9 -0
  50. package/dist/core/version.d.ts.map +1 -0
  51. package/dist/core/version.js +19 -0
  52. package/dist/core/version.js.map +1 -0
  53. package/dist/core/websocket-client.d.ts +122 -0
  54. package/dist/core/websocket-client.d.ts.map +1 -0
  55. package/dist/core/websocket-client.js +438 -0
  56. package/dist/core/websocket-client.js.map +1 -0
  57. package/dist/daemon/daemon-manager.d.ts +71 -0
  58. package/dist/daemon/daemon-manager.d.ts.map +1 -0
  59. package/dist/daemon/daemon-manager.js +289 -0
  60. package/dist/daemon/daemon-manager.js.map +1 -0
  61. package/dist/daemon/daemon-process.d.ts +13 -0
  62. package/dist/daemon/daemon-process.d.ts.map +1 -0
  63. package/dist/daemon/daemon-process.js +608 -0
  64. package/dist/daemon/daemon-process.js.map +1 -0
  65. package/dist/daemon/machine-id.d.ts +36 -0
  66. package/dist/daemon/machine-id.d.ts.map +1 -0
  67. package/dist/daemon/machine-id.js +195 -0
  68. package/dist/daemon/machine-id.js.map +1 -0
  69. package/dist/daemon/project-tracker.d.ts +92 -0
  70. package/dist/daemon/project-tracker.d.ts.map +1 -0
  71. package/dist/daemon/project-tracker.js +259 -0
  72. package/dist/daemon/project-tracker.js.map +1 -0
  73. package/dist/dev-wrapper.d.ts +88 -0
  74. package/dist/dev-wrapper.d.ts.map +1 -0
  75. package/dist/dev-wrapper.js +288 -0
  76. package/dist/dev-wrapper.js.map +1 -0
  77. package/dist/framework-detector.d.ts +29 -0
  78. package/dist/framework-detector.d.ts.map +1 -0
  79. package/dist/framework-detector.js +276 -0
  80. package/dist/framework-detector.js.map +1 -0
  81. package/dist/git-helpers/git-credential-helper.d.ts +29 -0
  82. package/dist/git-helpers/git-credential-helper.d.ts.map +1 -0
  83. package/dist/git-helpers/git-credential-helper.js +349 -0
  84. package/dist/git-helpers/git-credential-helper.js.map +1 -0
  85. package/dist/hooks/post-checkout +296 -0
  86. package/dist/hooks/pre-commit +139 -0
  87. package/dist/index.d.ts +8 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +102 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/ipc/ipc-client.d.ts +95 -0
  92. package/dist/ipc/ipc-client.d.ts.map +1 -0
  93. package/dist/ipc/ipc-client.js +204 -0
  94. package/dist/ipc/ipc-client.js.map +1 -0
  95. package/dist/ipc/ipc-server.d.ts +55 -0
  96. package/dist/ipc/ipc-server.d.ts.map +1 -0
  97. package/dist/ipc/ipc-server.js +177 -0
  98. package/dist/ipc/ipc-server.js.map +1 -0
  99. package/dist/output.d.ts +48 -0
  100. package/dist/output.d.ts.map +1 -0
  101. package/dist/output.js +129 -0
  102. package/dist/output.js.map +1 -0
  103. package/dist/utils/port-check.d.ts +15 -0
  104. package/dist/utils/port-check.d.ts.map +1 -0
  105. package/dist/utils/port-check.js +79 -0
  106. package/dist/utils/port-check.js.map +1 -0
  107. package/dist/utils/update-checker.d.ts +23 -0
  108. package/dist/utils/update-checker.d.ts.map +1 -0
  109. package/dist/utils/update-checker.js +95 -0
  110. package/dist/utils/update-checker.js.map +1 -0
  111. package/package.json +51 -0
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Episoda WebSocket Client
3
+ *
4
+ * EP589-9: Implemented with comprehensive error handling and reconnection logic
5
+ *
6
+ * Provides reliable WebSocket connection to episoda.dev CLI gateway with:
7
+ * - Automatic reconnection with exponential backoff
8
+ * - Heartbeat/ping-pong to keep connection alive
9
+ * - Event-driven architecture for handling server commands
10
+ * - Graceful error handling and recovery
11
+ */
12
+ import { ServerMessage, ClientMessage, ConnectionStatus } from './command-protocol';
13
+ export type DisconnectEvent = {
14
+ type: 'disconnected';
15
+ code: number;
16
+ reason: string;
17
+ willReconnect: boolean;
18
+ };
19
+ export type ClientEvent = ServerMessage | DisconnectEvent;
20
+ export type EventHandler = (event: ClientEvent) => void | Promise<void>;
21
+ /**
22
+ * WebSocket client for connecting to episoda.dev/cli gateway
23
+ *
24
+ * DESIGN PRINCIPLES:
25
+ * - Reverse WebSocket: Client connects TO server (bypasses NAT/firewall)
26
+ * - Automatic reconnection with exponential backoff
27
+ * - Server-initiated heartbeat via WebSocket ping/pong (30s interval)
28
+ * - Event-driven architecture for handling server commands
29
+ */
30
+ export declare class EpisodaClient {
31
+ private ws?;
32
+ private eventHandlers;
33
+ private reconnectAttempts;
34
+ private reconnectTimeout?;
35
+ private url;
36
+ private token;
37
+ private machineId?;
38
+ private hostname?;
39
+ private osPlatform?;
40
+ private osArch?;
41
+ private daemonPid?;
42
+ private isConnected;
43
+ private isDisconnecting;
44
+ private isGracefulShutdown;
45
+ private heartbeatTimer?;
46
+ private heartbeatTimeoutTimer?;
47
+ private lastCommandTime;
48
+ private firstDisconnectTime?;
49
+ private isIntentionalDisconnect;
50
+ private rateLimitBackoffUntil?;
51
+ private lastConnectAttemptTime;
52
+ private lastErrorCode?;
53
+ /**
54
+ * Connect to episoda.dev WebSocket gateway
55
+ * @param url - WebSocket URL (wss://episoda.dev/cli)
56
+ * @param token - OAuth access token
57
+ * @param machineId - Optional machine identifier for multi-machine support
58
+ * @param deviceInfo - Optional device information (hostname, OS, daemonPid)
59
+ */
60
+ connect(url: string, token: string, machineId?: string, deviceInfo?: {
61
+ hostname?: string;
62
+ osPlatform?: string;
63
+ osArch?: string;
64
+ daemonPid?: number;
65
+ }): Promise<void>;
66
+ /**
67
+ * Disconnect from the server
68
+ * @param intentional - If true, prevents automatic reconnection (user-initiated disconnect)
69
+ */
70
+ disconnect(intentional?: boolean): Promise<void>;
71
+ /**
72
+ * Register an event handler
73
+ * @param event - Event type ('command', 'ping', 'error', 'auth_success')
74
+ * @param handler - Handler function
75
+ */
76
+ on(event: string, handler: EventHandler): void;
77
+ /**
78
+ * Send a message to the server
79
+ * @param message - Client message to send
80
+ */
81
+ send(message: ClientMessage): Promise<void>;
82
+ /**
83
+ * Get current connection status
84
+ */
85
+ getStatus(): ConnectionStatus;
86
+ /**
87
+ * EP605: Update last command time to reset idle detection
88
+ * Call this when a command is received/executed
89
+ */
90
+ updateActivity(): void;
91
+ /**
92
+ * EP701: Emit a client-side event to registered handlers
93
+ * Used for events like 'disconnected' that originate from the client, not server
94
+ */
95
+ private emit;
96
+ /**
97
+ * Handle incoming message from server
98
+ */
99
+ private handleMessage;
100
+ /**
101
+ * Schedule reconnection with exponential backoff and randomization
102
+ *
103
+ * EP605: Conservative reconnection with multiple safeguards:
104
+ * - 6-hour maximum retry duration to prevent indefinite retrying
105
+ * - Activity-based backoff (10 min delay if idle for 1+ hour)
106
+ * - No reconnection after intentional disconnect
107
+ * - Randomization to prevent thundering herd
108
+ *
109
+ * EP648: Additional protections against reconnection loops:
110
+ * - Rate limit awareness: respect server's RATE_LIMITED response
111
+ * - Rapid close detection: if connection closes within 2s, apply longer backoff
112
+ */
113
+ private scheduleReconnect;
114
+ /**
115
+ * EP605: Start client-side heartbeat to detect dead connections
116
+ *
117
+ * Sends ping every 45 seconds and expects pong within 15 seconds.
118
+ * If no pong received, terminates connection to trigger reconnection.
119
+ */
120
+ private startHeartbeat;
121
+ }
122
+ //# sourceMappingURL=websocket-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-client.d.ts","sourceRoot":"","sources":["../../src/core/websocket-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAInF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,cAAc,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,eAAe,CAAA;AAEzD,MAAM,MAAM,YAAY,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAqBvE;;;;;;;;GAQG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,EAAE,CAAC,CAAI;IACf,OAAO,CAAC,aAAa,CAAyC;IAC9D,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,gBAAgB,CAAC,CAAgB;IACzC,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,QAAQ,CAAC,CAAQ;IACzB,OAAO,CAAC,UAAU,CAAC,CAAQ;IAC3B,OAAO,CAAC,MAAM,CAAC,CAAQ;IACvB,OAAO,CAAC,SAAS,CAAC,CAAQ;IAC1B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,kBAAkB,CAAQ;IAElC,OAAO,CAAC,cAAc,CAAC,CAAgB;IACvC,OAAO,CAAC,qBAAqB,CAAC,CAAgB;IAE9C,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,mBAAmB,CAAC,CAAQ;IACpC,OAAO,CAAC,uBAAuB,CAAQ;IAEvC,OAAO,CAAC,qBAAqB,CAAC,CAAQ;IACtC,OAAO,CAAC,sBAAsB,CAAI;IAClC,OAAO,CAAC,aAAa,CAAC,CAAQ;IAE9B;;;;;;OAMG;IACG,OAAO,CACX,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC3F,OAAO,CAAC,IAAI,CAAC;IAoHhB;;;OAGG;IACG,UAAU,CAAC,WAAW,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BnD;;;;OAIG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI;IAO9C;;;OAGG;IACG,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBjD;;OAEG;IACH,SAAS,IAAI,gBAAgB;IAM7B;;;OAGG;IACH,cAAc,IAAI,IAAI;IAItB;;;OAGG;IACH,OAAO,CAAC,IAAI;IAWZ;;OAEG;IACH,OAAO,CAAC,aAAa;IAiCrB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,iBAAiB;IAkHzB;;;;;OAKG;IACH,OAAO,CAAC,cAAc;CAgCvB"}
@@ -0,0 +1,438 @@
1
+ "use strict";
2
+ /**
3
+ * Episoda WebSocket Client
4
+ *
5
+ * EP589-9: Implemented with comprehensive error handling and reconnection logic
6
+ *
7
+ * Provides reliable WebSocket connection to episoda.dev CLI gateway with:
8
+ * - Automatic reconnection with exponential backoff
9
+ * - Heartbeat/ping-pong to keep connection alive
10
+ * - Event-driven architecture for handling server commands
11
+ * - Graceful error handling and recovery
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.EpisodaClient = void 0;
18
+ const ws_1 = __importDefault(require("ws"));
19
+ const version_1 = require("./version");
20
+ // EP605: Reconnection configuration for long-term stability
21
+ const INITIAL_RECONNECT_DELAY = 1000; // 1 second
22
+ const MAX_RECONNECT_DELAY = 60000; // 60 seconds standard max
23
+ const IDLE_RECONNECT_DELAY = 600000; // 10 minutes when idle (no commands for 1+ hour)
24
+ const MAX_RETRY_DURATION = 6 * 60 * 60 * 1000; // 6 hours maximum retry duration
25
+ const IDLE_THRESHOLD = 60 * 60 * 1000; // 1 hour - after this, use slower retry
26
+ // EP648: Rate limit and rapid reconnect prevention
27
+ const RATE_LIMIT_BACKOFF = 60000; // 60 seconds when rate limited
28
+ const RAPID_CLOSE_THRESHOLD = 2000; // If connection closes within 2s, it's likely an error
29
+ const RAPID_CLOSE_BACKOFF = 30000; // 30 seconds backoff for rapid close scenarios
30
+ // EP605: Client-side heartbeat to detect dead connections
31
+ const CLIENT_HEARTBEAT_INTERVAL = 45000; // 45 seconds (offset from server's 30s to avoid collision)
32
+ const CLIENT_HEARTBEAT_TIMEOUT = 15000; // 15 seconds to wait for pong
33
+ // EP606: Connection timeout for initial WebSocket connection
34
+ const CONNECTION_TIMEOUT = 15000; // 15 seconds to establish connection
35
+ /**
36
+ * WebSocket client for connecting to episoda.dev/cli gateway
37
+ *
38
+ * DESIGN PRINCIPLES:
39
+ * - Reverse WebSocket: Client connects TO server (bypasses NAT/firewall)
40
+ * - Automatic reconnection with exponential backoff
41
+ * - Server-initiated heartbeat via WebSocket ping/pong (30s interval)
42
+ * - Event-driven architecture for handling server commands
43
+ */
44
+ class EpisodaClient {
45
+ constructor() {
46
+ this.eventHandlers = new Map();
47
+ this.reconnectAttempts = 0;
48
+ this.url = '';
49
+ this.token = '';
50
+ this.isConnected = false;
51
+ this.isDisconnecting = false;
52
+ this.isGracefulShutdown = false; // Track if shutdown was graceful (server-initiated)
53
+ // EP605: Activity and retry duration tracking
54
+ this.lastCommandTime = Date.now(); // Track last command for idle detection
55
+ this.isIntentionalDisconnect = false; // Prevent reconnection after intentional disconnect
56
+ this.lastConnectAttemptTime = 0; // Track when we last attempted to connect
57
+ }
58
+ /**
59
+ * Connect to episoda.dev WebSocket gateway
60
+ * @param url - WebSocket URL (wss://episoda.dev/cli)
61
+ * @param token - OAuth access token
62
+ * @param machineId - Optional machine identifier for multi-machine support
63
+ * @param deviceInfo - Optional device information (hostname, OS, daemonPid)
64
+ */
65
+ async connect(url, token, machineId, deviceInfo) {
66
+ this.url = url;
67
+ this.token = token;
68
+ this.machineId = machineId;
69
+ this.hostname = deviceInfo?.hostname;
70
+ this.osPlatform = deviceInfo?.osPlatform;
71
+ this.osArch = deviceInfo?.osArch;
72
+ this.daemonPid = deviceInfo?.daemonPid;
73
+ this.isDisconnecting = false;
74
+ this.isGracefulShutdown = false; // Reset graceful shutdown flag on new connection
75
+ this.isIntentionalDisconnect = false; // Allow reconnection on fresh connect
76
+ this.lastConnectAttemptTime = Date.now(); // EP648: Track when this connection attempt started
77
+ this.lastErrorCode = undefined; // EP648: Clear last error code
78
+ return new Promise((resolve, reject) => {
79
+ // EP606: Connection timeout to prevent hanging indefinitely
80
+ const connectionTimeout = setTimeout(() => {
81
+ if (this.ws) {
82
+ this.ws.terminate();
83
+ }
84
+ reject(new Error(`Connection timeout after ${CONNECTION_TIMEOUT / 1000}s - server may be unreachable`));
85
+ }, CONNECTION_TIMEOUT);
86
+ try {
87
+ this.ws = new ws_1.default(url);
88
+ // Connection opened
89
+ this.ws.on('open', () => {
90
+ clearTimeout(connectionTimeout); // EP606: Clear timeout on successful connection
91
+ console.log('[EpisodaClient] WebSocket connected');
92
+ this.isConnected = true;
93
+ this.reconnectAttempts = 0;
94
+ this.firstDisconnectTime = undefined; // EP605: Reset retry duration tracking
95
+ this.lastCommandTime = Date.now(); // EP605: Reset activity timer
96
+ // Send auth message (K722: includes machineId, EP596: includes device info, EP601: includes daemonPid)
97
+ this.send({
98
+ type: 'auth',
99
+ token,
100
+ version: version_1.VERSION,
101
+ machineId,
102
+ hostname: this.hostname,
103
+ osPlatform: this.osPlatform,
104
+ osArch: this.osArch,
105
+ daemonPid: this.daemonPid
106
+ });
107
+ // EP605: Start client-side heartbeat
108
+ this.startHeartbeat();
109
+ resolve();
110
+ });
111
+ // EP605: Handle pong response from server (for client-initiated pings)
112
+ this.ws.on('pong', () => {
113
+ // Clear the timeout - connection is alive
114
+ if (this.heartbeatTimeoutTimer) {
115
+ clearTimeout(this.heartbeatTimeoutTimer);
116
+ this.heartbeatTimeoutTimer = undefined;
117
+ }
118
+ });
119
+ // Message received
120
+ this.ws.on('message', (data) => {
121
+ try {
122
+ const message = JSON.parse(data.toString());
123
+ this.handleMessage(message);
124
+ }
125
+ catch (error) {
126
+ console.error('[EpisodaClient] Failed to parse message:', error);
127
+ }
128
+ });
129
+ // EP603: Log ping events for debugging connection health
130
+ this.ws.on('ping', () => {
131
+ console.log('[EpisodaClient] Received ping from server');
132
+ });
133
+ // Connection closed
134
+ this.ws.on('close', (code, reason) => {
135
+ console.log(`[EpisodaClient] WebSocket closed: ${code} ${reason.toString()}`);
136
+ this.isConnected = false;
137
+ const willReconnect = !this.isDisconnecting;
138
+ // EP701: Emit 'disconnected' event so daemon can clean up connection
139
+ this.emit({
140
+ type: 'disconnected',
141
+ code,
142
+ reason: reason.toString(),
143
+ willReconnect
144
+ });
145
+ // Attempt reconnection if not intentional disconnect
146
+ if (willReconnect) {
147
+ this.scheduleReconnect();
148
+ }
149
+ });
150
+ // Error occurred
151
+ this.ws.on('error', (error) => {
152
+ console.error('[EpisodaClient] WebSocket error:', error);
153
+ if (!this.isConnected) {
154
+ // Connection failed, reject the promise
155
+ clearTimeout(connectionTimeout); // EP606: Clear timeout on error
156
+ reject(error);
157
+ }
158
+ });
159
+ }
160
+ catch (error) {
161
+ clearTimeout(connectionTimeout); // EP606: Clear timeout on exception
162
+ reject(error);
163
+ }
164
+ });
165
+ }
166
+ /**
167
+ * Disconnect from the server
168
+ * @param intentional - If true, prevents automatic reconnection (user-initiated disconnect)
169
+ */
170
+ async disconnect(intentional = true) {
171
+ this.isDisconnecting = true;
172
+ this.isIntentionalDisconnect = intentional; // EP605: Prevent reconnection if user intentionally disconnected
173
+ // EP605: Clear all timers
174
+ if (this.reconnectTimeout) {
175
+ clearTimeout(this.reconnectTimeout);
176
+ this.reconnectTimeout = undefined;
177
+ }
178
+ if (this.heartbeatTimer) {
179
+ clearInterval(this.heartbeatTimer);
180
+ this.heartbeatTimer = undefined;
181
+ }
182
+ if (this.heartbeatTimeoutTimer) {
183
+ clearTimeout(this.heartbeatTimeoutTimer);
184
+ this.heartbeatTimeoutTimer = undefined;
185
+ }
186
+ if (this.ws) {
187
+ this.ws.close();
188
+ this.ws = undefined;
189
+ }
190
+ this.isConnected = false;
191
+ }
192
+ /**
193
+ * Register an event handler
194
+ * @param event - Event type ('command', 'ping', 'error', 'auth_success')
195
+ * @param handler - Handler function
196
+ */
197
+ on(event, handler) {
198
+ if (!this.eventHandlers.has(event)) {
199
+ this.eventHandlers.set(event, []);
200
+ }
201
+ this.eventHandlers.get(event).push(handler);
202
+ }
203
+ /**
204
+ * Send a message to the server
205
+ * @param message - Client message to send
206
+ */
207
+ async send(message) {
208
+ if (!this.ws || !this.isConnected) {
209
+ throw new Error('WebSocket not connected');
210
+ }
211
+ return new Promise((resolve, reject) => {
212
+ this.ws.send(JSON.stringify(message), (error) => {
213
+ if (error) {
214
+ console.error('[EpisodaClient] Failed to send message:', error);
215
+ reject(error);
216
+ }
217
+ else {
218
+ resolve();
219
+ }
220
+ });
221
+ });
222
+ }
223
+ /**
224
+ * Get current connection status
225
+ */
226
+ getStatus() {
227
+ return {
228
+ connected: this.isConnected
229
+ };
230
+ }
231
+ /**
232
+ * EP605: Update last command time to reset idle detection
233
+ * Call this when a command is received/executed
234
+ */
235
+ updateActivity() {
236
+ this.lastCommandTime = Date.now();
237
+ }
238
+ /**
239
+ * EP701: Emit a client-side event to registered handlers
240
+ * Used for events like 'disconnected' that originate from the client, not server
241
+ */
242
+ emit(event) {
243
+ const handlers = this.eventHandlers.get(event.type) || [];
244
+ handlers.forEach(handler => {
245
+ try {
246
+ handler(event);
247
+ }
248
+ catch (error) {
249
+ console.error(`[EpisodaClient] Handler error for ${event.type}:`, error);
250
+ }
251
+ });
252
+ }
253
+ /**
254
+ * Handle incoming message from server
255
+ */
256
+ handleMessage(message) {
257
+ // Special handling for graceful shutdown messages from server
258
+ if (message.type === 'shutdown') {
259
+ console.log('[EpisodaClient] Received graceful shutdown message from server');
260
+ this.isGracefulShutdown = true;
261
+ }
262
+ // EP648: Handle error messages, especially rate limiting and rapid reconnect
263
+ if (message.type === 'error') {
264
+ const errorMessage = message;
265
+ this.lastErrorCode = errorMessage.code;
266
+ if (errorMessage.code === 'RATE_LIMITED' || errorMessage.code === 'TOO_SOON') {
267
+ // Use server-provided retryAfter or default to 60 seconds for rate limit, 5 seconds for too soon
268
+ const defaultRetry = errorMessage.code === 'RATE_LIMITED' ? 60 : 5;
269
+ const retryAfterMs = (errorMessage.retryAfter || defaultRetry) * 1000;
270
+ this.rateLimitBackoffUntil = Date.now() + retryAfterMs;
271
+ console.log(`[EpisodaClient] ${errorMessage.code}: will retry after ${retryAfterMs / 1000}s`);
272
+ }
273
+ }
274
+ const handlers = this.eventHandlers.get(message.type) || [];
275
+ // Execute all handlers for this message type
276
+ handlers.forEach(handler => {
277
+ try {
278
+ handler(message);
279
+ }
280
+ catch (error) {
281
+ console.error(`[EpisodaClient] Handler error for ${message.type}:`, error);
282
+ }
283
+ });
284
+ }
285
+ /**
286
+ * Schedule reconnection with exponential backoff and randomization
287
+ *
288
+ * EP605: Conservative reconnection with multiple safeguards:
289
+ * - 6-hour maximum retry duration to prevent indefinite retrying
290
+ * - Activity-based backoff (10 min delay if idle for 1+ hour)
291
+ * - No reconnection after intentional disconnect
292
+ * - Randomization to prevent thundering herd
293
+ *
294
+ * EP648: Additional protections against reconnection loops:
295
+ * - Rate limit awareness: respect server's RATE_LIMITED response
296
+ * - Rapid close detection: if connection closes within 2s, apply longer backoff
297
+ */
298
+ scheduleReconnect() {
299
+ // EP605: Don't reconnect if user intentionally disconnected
300
+ if (this.isIntentionalDisconnect) {
301
+ console.log('[EpisodaClient] Intentional disconnect - not reconnecting');
302
+ return;
303
+ }
304
+ // EP605: Clear heartbeat timers before reconnection attempt
305
+ if (this.heartbeatTimer) {
306
+ clearInterval(this.heartbeatTimer);
307
+ this.heartbeatTimer = undefined;
308
+ }
309
+ if (this.heartbeatTimeoutTimer) {
310
+ clearTimeout(this.heartbeatTimeoutTimer);
311
+ this.heartbeatTimeoutTimer = undefined;
312
+ }
313
+ // EP605: Track when reconnection attempts started
314
+ if (!this.firstDisconnectTime) {
315
+ this.firstDisconnectTime = Date.now();
316
+ }
317
+ // EP605: Check 6-hour maximum retry duration
318
+ const retryDuration = Date.now() - this.firstDisconnectTime;
319
+ if (retryDuration >= MAX_RETRY_DURATION) {
320
+ console.error(`[EpisodaClient] Maximum retry duration (6 hours) exceeded, giving up. Please restart the CLI.`);
321
+ return;
322
+ }
323
+ // EP605: Log periodically to show we're still trying
324
+ if (this.reconnectAttempts > 0 && this.reconnectAttempts % 10 === 0) {
325
+ const hoursRemaining = ((MAX_RETRY_DURATION - retryDuration) / (60 * 60 * 1000)).toFixed(1);
326
+ console.log(`[EpisodaClient] Still attempting to reconnect (attempt ${this.reconnectAttempts}, ${hoursRemaining}h remaining)...`);
327
+ }
328
+ // EP648: Check if we're still in rate limit backoff period
329
+ if (this.rateLimitBackoffUntil && Date.now() < this.rateLimitBackoffUntil) {
330
+ const waitTime = this.rateLimitBackoffUntil - Date.now();
331
+ console.log(`[EpisodaClient] Rate limited, waiting ${Math.round(waitTime / 1000)}s before retry`);
332
+ this.reconnectAttempts++;
333
+ this.reconnectTimeout = setTimeout(() => {
334
+ this.rateLimitBackoffUntil = undefined; // Clear rate limit after waiting
335
+ this.scheduleReconnect();
336
+ }, waitTime);
337
+ return;
338
+ }
339
+ // EP648: Detect rapid close (connection closed within 2 seconds of opening)
340
+ // This indicates an error condition like rate limiting, auth failure, etc.
341
+ const timeSinceConnect = Date.now() - this.lastConnectAttemptTime;
342
+ const wasRapidClose = timeSinceConnect < RAPID_CLOSE_THRESHOLD && this.lastConnectAttemptTime > 0;
343
+ // EP605: Check if connection is idle (no commands for 1+ hour)
344
+ const timeSinceLastCommand = Date.now() - this.lastCommandTime;
345
+ const isIdle = timeSinceLastCommand >= IDLE_THRESHOLD;
346
+ // Calculate base delay based on shutdown type and conditions
347
+ let baseDelay;
348
+ if (this.isGracefulShutdown && this.reconnectAttempts < 3) {
349
+ // Graceful shutdown: try reconnecting quickly (500ms, 1s, 2s)
350
+ baseDelay = 500 * Math.pow(2, this.reconnectAttempts);
351
+ }
352
+ else if (wasRapidClose) {
353
+ // EP648: Connection failed almost immediately - likely rate limited or auth error
354
+ // Apply longer backoff to prevent spam
355
+ baseDelay = Math.max(RAPID_CLOSE_BACKOFF, INITIAL_RECONNECT_DELAY * Math.pow(2, Math.min(this.reconnectAttempts, 6)));
356
+ console.log(`[EpisodaClient] Rapid close detected (${timeSinceConnect}ms), applying ${baseDelay / 1000}s backoff`);
357
+ }
358
+ else if (isIdle) {
359
+ // EP605: Use slower retry for idle connections (10 minutes)
360
+ baseDelay = IDLE_RECONNECT_DELAY;
361
+ }
362
+ else {
363
+ // EP605: Use capped exponential backoff
364
+ // After 6 attempts, we hit max delay and stay there
365
+ const cappedAttempts = Math.min(this.reconnectAttempts, 6);
366
+ baseDelay = INITIAL_RECONNECT_DELAY * Math.pow(2, cappedAttempts);
367
+ }
368
+ // Add randomization (±25%) to prevent thundering herd
369
+ // When server restarts, all clients shouldn't hit it at the exact same time
370
+ const jitter = baseDelay * 0.25 * (Math.random() * 2 - 1); // Random between -25% and +25%
371
+ const maxDelay = isIdle ? IDLE_RECONNECT_DELAY : MAX_RECONNECT_DELAY;
372
+ const delay = Math.min(baseDelay + jitter, maxDelay);
373
+ this.reconnectAttempts++;
374
+ const shutdownType = this.isGracefulShutdown ? 'graceful' : 'ungraceful';
375
+ const idleStatus = isIdle ? ', idle' : '';
376
+ const rapidStatus = wasRapidClose ? ', rapid-close' : '';
377
+ // EP605: Only log first few attempts and then periodically
378
+ if (this.reconnectAttempts <= 5 || this.reconnectAttempts % 10 === 0) {
379
+ console.log(`[EpisodaClient] Reconnecting in ${Math.round(delay / 1000)}s (attempt ${this.reconnectAttempts}, ${shutdownType}${idleStatus}${rapidStatus})`);
380
+ }
381
+ this.reconnectTimeout = setTimeout(() => {
382
+ console.log('[EpisodaClient] Attempting reconnection...');
383
+ this.connect(this.url, this.token, this.machineId, {
384
+ hostname: this.hostname,
385
+ osPlatform: this.osPlatform,
386
+ osArch: this.osArch,
387
+ daemonPid: this.daemonPid
388
+ }).then(() => {
389
+ // EP605: Reset counters after successful reconnection
390
+ console.log('[EpisodaClient] Reconnection successful, resetting retry counter');
391
+ this.reconnectAttempts = 0;
392
+ this.isGracefulShutdown = false;
393
+ this.firstDisconnectTime = undefined;
394
+ this.rateLimitBackoffUntil = undefined; // EP648: Clear rate limit on success
395
+ }).catch(error => {
396
+ console.error('[EpisodaClient] Reconnection failed:', error);
397
+ // scheduleReconnect will be called again from the 'close' event
398
+ });
399
+ }, delay);
400
+ }
401
+ /**
402
+ * EP605: Start client-side heartbeat to detect dead connections
403
+ *
404
+ * Sends ping every 45 seconds and expects pong within 15 seconds.
405
+ * If no pong received, terminates connection to trigger reconnection.
406
+ */
407
+ startHeartbeat() {
408
+ // Clear any existing heartbeat
409
+ if (this.heartbeatTimer) {
410
+ clearInterval(this.heartbeatTimer);
411
+ }
412
+ this.heartbeatTimer = setInterval(() => {
413
+ if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
414
+ return;
415
+ }
416
+ // Send ping
417
+ try {
418
+ this.ws.ping();
419
+ // Clear any existing timeout before setting new one (prevents timer leak)
420
+ if (this.heartbeatTimeoutTimer) {
421
+ clearTimeout(this.heartbeatTimeoutTimer);
422
+ }
423
+ // Set timeout for pong response
424
+ this.heartbeatTimeoutTimer = setTimeout(() => {
425
+ console.log('[EpisodaClient] Heartbeat timeout - no pong received, terminating connection');
426
+ if (this.ws) {
427
+ this.ws.terminate(); // Force close to trigger reconnection
428
+ }
429
+ }, CLIENT_HEARTBEAT_TIMEOUT);
430
+ }
431
+ catch (error) {
432
+ console.error('[EpisodaClient] Error sending heartbeat ping:', error);
433
+ }
434
+ }, CLIENT_HEARTBEAT_INTERVAL);
435
+ }
436
+ }
437
+ exports.EpisodaClient = EpisodaClient;
438
+ //# sourceMappingURL=websocket-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket-client.js","sourceRoot":"","sources":["../../src/core/websocket-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Daemon lifecycle management
3
+ *
4
+ * Manages the Episoda daemon process lifecycle:
5
+ * - Spawning daemon in detached mode
6
+ * - Checking daemon status
7
+ * - Stopping daemon gracefully
8
+ * - PID file management
9
+ *
10
+ * Ensures only one daemon runs per user.
11
+ */
12
+ /**
13
+ * EP734: Kill all stale Episoda processes
14
+ *
15
+ * Finds and kills any existing episoda-related processes to ensure
16
+ * a clean slate before starting a new daemon. This prevents:
17
+ * - Multiple daemon processes running simultaneously
18
+ * - Stale `episoda dev` foreground processes from previous sessions
19
+ * - Orphaned node processes running daemon-process.js
20
+ *
21
+ * @returns Number of processes killed
22
+ */
23
+ export declare function killAllEpisodaProcesses(): number;
24
+ /**
25
+ * Get path to daemon PID file
26
+ */
27
+ export declare function getPidFilePath(): string;
28
+ /**
29
+ * Check if daemon is running
30
+ *
31
+ * Reads PID from file and checks if process exists.
32
+ *
33
+ * @returns PID if running, null if not
34
+ */
35
+ export declare function isDaemonRunning(): number | null;
36
+ /**
37
+ * Start the daemon process
38
+ *
39
+ * Spawns daemon in detached mode. Daemon survives terminal close.
40
+ *
41
+ * @throws Error if daemon already running or spawn fails
42
+ */
43
+ export declare function startDaemon(): Promise<number>;
44
+ /**
45
+ * Stop the daemon process
46
+ *
47
+ * Sends SIGTERM for graceful shutdown. If daemon doesn't stop
48
+ * within timeout, sends SIGKILL.
49
+ *
50
+ * @param timeout Milliseconds to wait before SIGKILL (default: 5000)
51
+ * @returns true if stopped, false if wasn't running
52
+ */
53
+ export declare function stopDaemon(timeout?: number): Promise<boolean>;
54
+ /**
55
+ * Restart the daemon
56
+ *
57
+ * Stops existing daemon and starts new one.
58
+ *
59
+ * @returns PID of new daemon
60
+ */
61
+ export declare function restartDaemon(): Promise<number>;
62
+ /**
63
+ * Get daemon status information
64
+ *
65
+ * @returns Status object with running state and PID
66
+ */
67
+ export declare function getDaemonStatus(): {
68
+ running: boolean;
69
+ pid: number | null;
70
+ };
71
+ //# sourceMappingURL=daemon-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daemon-manager.d.ts","sourceRoot":"","sources":["../../src/daemon/daemon-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CA6DhD;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CA+B/C;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC,CAmDnD;AAED;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAAC,OAAO,GAAE,MAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CA8CzE;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,CAGrD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAM1E"}