episoda 0.2.13 → 0.2.15
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/daemon/daemon-process.js +106 -11
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +84 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1718,6 +1718,18 @@ var require_websocket_client = __commonJS({
|
|
|
1718
1718
|
this.isIntentionalDisconnect = false;
|
|
1719
1719
|
this.lastConnectAttemptTime = Date.now();
|
|
1720
1720
|
this.lastErrorCode = void 0;
|
|
1721
|
+
if (this.ws) {
|
|
1722
|
+
try {
|
|
1723
|
+
this.ws.removeAllListeners();
|
|
1724
|
+
this.ws.terminate();
|
|
1725
|
+
} catch {
|
|
1726
|
+
}
|
|
1727
|
+
this.ws = void 0;
|
|
1728
|
+
}
|
|
1729
|
+
if (this.reconnectTimeout) {
|
|
1730
|
+
clearTimeout(this.reconnectTimeout);
|
|
1731
|
+
this.reconnectTimeout = void 0;
|
|
1732
|
+
}
|
|
1721
1733
|
return new Promise((resolve2, reject) => {
|
|
1722
1734
|
const connectionTimeout = setTimeout(() => {
|
|
1723
1735
|
if (this.ws) {
|
|
@@ -1827,6 +1839,32 @@ var require_websocket_client = __commonJS({
|
|
|
1827
1839
|
}
|
|
1828
1840
|
this.eventHandlers.get(event).push(handler);
|
|
1829
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* EP812: Register a one-time event handler (removes itself after first call)
|
|
1844
|
+
* @param event - Event type
|
|
1845
|
+
* @param handler - Handler function
|
|
1846
|
+
*/
|
|
1847
|
+
once(event, handler) {
|
|
1848
|
+
const onceHandler = (message) => {
|
|
1849
|
+
this.off(event, onceHandler);
|
|
1850
|
+
handler(message);
|
|
1851
|
+
};
|
|
1852
|
+
this.on(event, onceHandler);
|
|
1853
|
+
}
|
|
1854
|
+
/**
|
|
1855
|
+
* EP812: Remove an event handler
|
|
1856
|
+
* @param event - Event type
|
|
1857
|
+
* @param handler - Handler function to remove
|
|
1858
|
+
*/
|
|
1859
|
+
off(event, handler) {
|
|
1860
|
+
const handlers = this.eventHandlers.get(event);
|
|
1861
|
+
if (handlers) {
|
|
1862
|
+
const index = handlers.indexOf(handler);
|
|
1863
|
+
if (index !== -1) {
|
|
1864
|
+
handlers.splice(index, 1);
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1830
1868
|
/**
|
|
1831
1869
|
* Send a message to the server
|
|
1832
1870
|
* @param message - Client message to send
|
|
@@ -1920,6 +1958,10 @@ var require_websocket_client = __commonJS({
|
|
|
1920
1958
|
console.log("[EpisodaClient] Intentional disconnect - not reconnecting");
|
|
1921
1959
|
return;
|
|
1922
1960
|
}
|
|
1961
|
+
if (this.reconnectTimeout) {
|
|
1962
|
+
console.log("[EpisodaClient] Reconnection already scheduled, skipping duplicate");
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1923
1965
|
if (this.heartbeatTimer) {
|
|
1924
1966
|
clearInterval(this.heartbeatTimer);
|
|
1925
1967
|
this.heartbeatTimer = void 0;
|
|
@@ -2238,7 +2280,7 @@ var require_package = __commonJS({
|
|
|
2238
2280
|
"package.json"(exports2, module2) {
|
|
2239
2281
|
module2.exports = {
|
|
2240
2282
|
name: "episoda",
|
|
2241
|
-
version: "0.2.
|
|
2283
|
+
version: "0.2.14",
|
|
2242
2284
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2243
2285
|
main: "dist/index.js",
|
|
2244
2286
|
types: "dist/index.d.ts",
|
|
@@ -3130,6 +3172,10 @@ var Daemon = class {
|
|
|
3130
3172
|
// Updated by 'auth_success' (add) and 'disconnected' (remove) events
|
|
3131
3173
|
this.liveConnections = /* @__PURE__ */ new Set();
|
|
3132
3174
|
// projectPath
|
|
3175
|
+
// EP813: Track connections that are still authenticating (in progress)
|
|
3176
|
+
// Prevents race condition between restoreConnections() and add-project IPC
|
|
3177
|
+
this.pendingConnections = /* @__PURE__ */ new Set();
|
|
3178
|
+
// projectPath
|
|
3133
3179
|
this.shuttingDown = false;
|
|
3134
3180
|
this.ipcServer = new IPCServer();
|
|
3135
3181
|
}
|
|
@@ -3198,18 +3244,31 @@ var Daemon = class {
|
|
|
3198
3244
|
this.ipcServer.on("add-project", async (params) => {
|
|
3199
3245
|
const { projectId, projectPath } = params;
|
|
3200
3246
|
addProject(projectId, projectPath);
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3247
|
+
const MAX_RETRIES = 3;
|
|
3248
|
+
const INITIAL_DELAY = 1e3;
|
|
3249
|
+
let lastError = "";
|
|
3250
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
3251
|
+
try {
|
|
3252
|
+
await this.connectProject(projectId, projectPath);
|
|
3253
|
+
const isHealthy = this.isConnectionHealthy(projectPath);
|
|
3254
|
+
if (!isHealthy) {
|
|
3255
|
+
console.warn(`[Daemon] Connection completed but not healthy for ${projectPath}`);
|
|
3256
|
+
lastError = "Connection established but not healthy";
|
|
3257
|
+
return { success: false, connected: false, error: lastError };
|
|
3258
|
+
}
|
|
3259
|
+
return { success: true, connected: true };
|
|
3260
|
+
} catch (error) {
|
|
3261
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
3262
|
+
console.error(`[Daemon] Connection attempt ${attempt}/${MAX_RETRIES} failed:`, lastError);
|
|
3263
|
+
if (attempt < MAX_RETRIES) {
|
|
3264
|
+
const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
|
|
3265
|
+
console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
|
|
3266
|
+
await new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
3267
|
+
await this.disconnectProject(projectPath);
|
|
3268
|
+
}
|
|
3207
3269
|
}
|
|
3208
|
-
return { success: true, connected: true };
|
|
3209
|
-
} catch (error) {
|
|
3210
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3211
|
-
return { success: false, connected: false, error: errorMessage };
|
|
3212
3270
|
}
|
|
3271
|
+
return { success: false, connected: false, error: `Failed after ${MAX_RETRIES} attempts: ${lastError}` };
|
|
3213
3272
|
});
|
|
3214
3273
|
this.ipcServer.on("remove-project", async (params) => {
|
|
3215
3274
|
const { projectPath } = params;
|
|
@@ -3284,6 +3343,19 @@ var Daemon = class {
|
|
|
3284
3343
|
console.log(`[Daemon] Already connected to ${projectPath}`);
|
|
3285
3344
|
return;
|
|
3286
3345
|
}
|
|
3346
|
+
if (this.pendingConnections.has(projectPath)) {
|
|
3347
|
+
console.log(`[Daemon] Connection in progress for ${projectPath}, waiting...`);
|
|
3348
|
+
const maxWait = 35e3;
|
|
3349
|
+
const startTime = Date.now();
|
|
3350
|
+
while (this.pendingConnections.has(projectPath) && Date.now() - startTime < maxWait) {
|
|
3351
|
+
await new Promise((resolve2) => setTimeout(resolve2, 500));
|
|
3352
|
+
}
|
|
3353
|
+
if (this.liveConnections.has(projectPath)) {
|
|
3354
|
+
console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
|
|
3355
|
+
return;
|
|
3356
|
+
}
|
|
3357
|
+
console.warn(`[Daemon] Pending connection timed out for ${projectPath}`);
|
|
3358
|
+
}
|
|
3287
3359
|
console.warn(`[Daemon] Stale connection detected for ${projectPath}, forcing reconnection`);
|
|
3288
3360
|
await this.disconnectProject(projectPath);
|
|
3289
3361
|
}
|
|
@@ -3310,6 +3382,7 @@ var Daemon = class {
|
|
|
3310
3382
|
gitExecutor
|
|
3311
3383
|
};
|
|
3312
3384
|
this.connections.set(projectPath, connection);
|
|
3385
|
+
this.pendingConnections.add(projectPath);
|
|
3313
3386
|
client.on("command", async (message) => {
|
|
3314
3387
|
if (message.type === "command" && message.command) {
|
|
3315
3388
|
console.log(`[Daemon] Received command for ${projectId}:`, message.command);
|
|
@@ -3405,6 +3478,7 @@ var Daemon = class {
|
|
|
3405
3478
|
console.log(`[Daemon] Authenticated for project ${projectId}`);
|
|
3406
3479
|
touchProject(projectPath);
|
|
3407
3480
|
this.liveConnections.add(projectPath);
|
|
3481
|
+
this.pendingConnections.delete(projectPath);
|
|
3408
3482
|
const authMessage = message;
|
|
3409
3483
|
if (authMessage.userId && authMessage.workspaceId) {
|
|
3410
3484
|
await this.configureGitUser(projectPath, authMessage.userId, authMessage.workspaceId, this.machineId, projectId, authMessage.deviceId);
|
|
@@ -3447,6 +3521,23 @@ var Daemon = class {
|
|
|
3447
3521
|
} catch (pidError) {
|
|
3448
3522
|
console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError);
|
|
3449
3523
|
}
|
|
3524
|
+
const authSuccessPromise = new Promise((resolve2, reject) => {
|
|
3525
|
+
const AUTH_TIMEOUT = 3e4;
|
|
3526
|
+
const timeout = setTimeout(() => {
|
|
3527
|
+
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
3528
|
+
}, AUTH_TIMEOUT);
|
|
3529
|
+
const authHandler = () => {
|
|
3530
|
+
clearTimeout(timeout);
|
|
3531
|
+
resolve2();
|
|
3532
|
+
};
|
|
3533
|
+
client.once("auth_success", authHandler);
|
|
3534
|
+
const errorHandler = (message) => {
|
|
3535
|
+
clearTimeout(timeout);
|
|
3536
|
+
const errorMsg = message;
|
|
3537
|
+
reject(new Error(errorMsg.message || "Authentication failed"));
|
|
3538
|
+
};
|
|
3539
|
+
client.once("auth_error", errorHandler);
|
|
3540
|
+
});
|
|
3450
3541
|
await client.connect(wsUrl, config.access_token, this.machineId, {
|
|
3451
3542
|
hostname: os.hostname(),
|
|
3452
3543
|
osPlatform: os.platform(),
|
|
@@ -3454,9 +3545,12 @@ var Daemon = class {
|
|
|
3454
3545
|
daemonPid
|
|
3455
3546
|
});
|
|
3456
3547
|
console.log(`[Daemon] Successfully connected to project ${projectId}`);
|
|
3548
|
+
await authSuccessPromise;
|
|
3549
|
+
console.log(`[Daemon] Authentication complete for project ${projectId}`);
|
|
3457
3550
|
} catch (error) {
|
|
3458
3551
|
console.error(`[Daemon] Failed to connect to ${projectId}:`, error);
|
|
3459
3552
|
this.connections.delete(projectPath);
|
|
3553
|
+
this.pendingConnections.delete(projectPath);
|
|
3460
3554
|
throw error;
|
|
3461
3555
|
}
|
|
3462
3556
|
}
|
|
@@ -3474,6 +3568,7 @@ var Daemon = class {
|
|
|
3474
3568
|
await connection.client.disconnect();
|
|
3475
3569
|
this.connections.delete(projectPath);
|
|
3476
3570
|
this.liveConnections.delete(projectPath);
|
|
3571
|
+
this.pendingConnections.delete(projectPath);
|
|
3477
3572
|
console.log(`[Daemon] Disconnected from ${projectPath}`);
|
|
3478
3573
|
}
|
|
3479
3574
|
/**
|