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.
- package/dist/commands/auth.d.ts +22 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +384 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/dev.d.ts +20 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +305 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +75 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/stop.d.ts +17 -0
- package/dist/commands/stop.d.ts.map +1 -0
- package/dist/commands/stop.js +81 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/core/auth.d.ts +26 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +113 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/command-protocol.d.ts +262 -0
- package/dist/core/command-protocol.d.ts.map +1 -0
- package/dist/core/command-protocol.js +13 -0
- package/dist/core/command-protocol.js.map +1 -0
- package/dist/core/connection-manager.d.ts +58 -0
- package/dist/core/connection-manager.d.ts.map +1 -0
- package/dist/core/connection-manager.js +215 -0
- package/dist/core/connection-manager.js.map +1 -0
- package/dist/core/errors.d.ts +18 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +55 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git-executor.d.ts +157 -0
- package/dist/core/git-executor.d.ts.map +1 -0
- package/dist/core/git-executor.js +1605 -0
- package/dist/core/git-executor.js.map +1 -0
- package/dist/core/git-parser.d.ts +40 -0
- package/dist/core/git-parser.d.ts.map +1 -0
- package/dist/core/git-parser.js +194 -0
- package/dist/core/git-parser.js.map +1 -0
- package/dist/core/git-validator.d.ts +42 -0
- package/dist/core/git-validator.d.ts.map +1 -0
- package/dist/core/git-validator.js +102 -0
- package/dist/core/git-validator.js.map +1 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +41 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/version.d.ts +9 -0
- package/dist/core/version.d.ts.map +1 -0
- package/dist/core/version.js +19 -0
- package/dist/core/version.js.map +1 -0
- package/dist/core/websocket-client.d.ts +122 -0
- package/dist/core/websocket-client.d.ts.map +1 -0
- package/dist/core/websocket-client.js +438 -0
- package/dist/core/websocket-client.js.map +1 -0
- package/dist/daemon/daemon-manager.d.ts +71 -0
- package/dist/daemon/daemon-manager.d.ts.map +1 -0
- package/dist/daemon/daemon-manager.js +289 -0
- package/dist/daemon/daemon-manager.js.map +1 -0
- package/dist/daemon/daemon-process.d.ts +13 -0
- package/dist/daemon/daemon-process.d.ts.map +1 -0
- package/dist/daemon/daemon-process.js +608 -0
- package/dist/daemon/daemon-process.js.map +1 -0
- package/dist/daemon/machine-id.d.ts +36 -0
- package/dist/daemon/machine-id.d.ts.map +1 -0
- package/dist/daemon/machine-id.js +195 -0
- package/dist/daemon/machine-id.js.map +1 -0
- package/dist/daemon/project-tracker.d.ts +92 -0
- package/dist/daemon/project-tracker.d.ts.map +1 -0
- package/dist/daemon/project-tracker.js +259 -0
- package/dist/daemon/project-tracker.js.map +1 -0
- package/dist/dev-wrapper.d.ts +88 -0
- package/dist/dev-wrapper.d.ts.map +1 -0
- package/dist/dev-wrapper.js +288 -0
- package/dist/dev-wrapper.js.map +1 -0
- package/dist/framework-detector.d.ts +29 -0
- package/dist/framework-detector.d.ts.map +1 -0
- package/dist/framework-detector.js +276 -0
- package/dist/framework-detector.js.map +1 -0
- package/dist/git-helpers/git-credential-helper.d.ts +29 -0
- package/dist/git-helpers/git-credential-helper.d.ts.map +1 -0
- package/dist/git-helpers/git-credential-helper.js +349 -0
- package/dist/git-helpers/git-credential-helper.js.map +1 -0
- package/dist/hooks/post-checkout +296 -0
- package/dist/hooks/pre-commit +139 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +102 -0
- package/dist/index.js.map +1 -0
- package/dist/ipc/ipc-client.d.ts +95 -0
- package/dist/ipc/ipc-client.d.ts.map +1 -0
- package/dist/ipc/ipc-client.js +204 -0
- package/dist/ipc/ipc-client.js.map +1 -0
- package/dist/ipc/ipc-server.d.ts +55 -0
- package/dist/ipc/ipc-server.d.ts.map +1 -0
- package/dist/ipc/ipc-server.js +177 -0
- package/dist/ipc/ipc-server.js.map +1 -0
- package/dist/output.d.ts +48 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +129 -0
- package/dist/output.js.map +1 -0
- package/dist/utils/port-check.d.ts +15 -0
- package/dist/utils/port-check.d.ts.map +1 -0
- package/dist/utils/port-check.js +79 -0
- package/dist/utils/port-check.js.map +1 -0
- package/dist/utils/update-checker.d.ts +23 -0
- package/dist/utils/update-checker.d.ts.map +1 -0
- package/dist/utils/update-checker.js +95 -0
- package/dist/utils/update-checker.js.map +1 -0
- 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"}
|