@episoda/cli 0.2.221 → 0.2.223
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 +153 -10
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +24 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -36,7 +36,8 @@ var require_command_protocol = __commonJS({
|
|
|
36
36
|
"../core/dist/command-protocol.js"(exports2) {
|
|
37
37
|
"use strict";
|
|
38
38
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
39
|
-
exports2.WORKTREE_STATUS_VALUES = void 0;
|
|
39
|
+
exports2.WORKTREE_STATUS_VALUES = exports2.DAEMON_PROTOCOL_VERSION = void 0;
|
|
40
|
+
exports2.DAEMON_PROTOCOL_VERSION = 2;
|
|
40
41
|
exports2.WORKTREE_STATUS_VALUES = [
|
|
41
42
|
"pending",
|
|
42
43
|
"provisioning",
|
|
@@ -2180,6 +2181,7 @@ var require_websocket_client = __commonJS({
|
|
|
2180
2181
|
exports2.EpisodaClient = void 0;
|
|
2181
2182
|
var ws_1 = __importDefault(require("ws"));
|
|
2182
2183
|
var https_1 = __importDefault(require("https"));
|
|
2184
|
+
var command_protocol_1 = require_command_protocol();
|
|
2183
2185
|
var version_1 = require_version();
|
|
2184
2186
|
var ipv4Agent = new https_1.default.Agent({ family: 4 });
|
|
2185
2187
|
var MAX_RETRY_DURATION = 6 * 60 * 60 * 1e3;
|
|
@@ -2196,6 +2198,7 @@ var require_websocket_client = __commonJS({
|
|
|
2196
2198
|
this.autoReconnectEnabled = true;
|
|
2197
2199
|
this.url = "";
|
|
2198
2200
|
this.token = "";
|
|
2201
|
+
this.protocolVersion = command_protocol_1.DAEMON_PROTOCOL_VERSION;
|
|
2199
2202
|
this.isConnected = false;
|
|
2200
2203
|
this.isDisconnecting = false;
|
|
2201
2204
|
this.isGracefulShutdown = false;
|
|
@@ -2221,11 +2224,15 @@ var require_websocket_client = __commonJS({
|
|
|
2221
2224
|
this.osArch = deviceInfo?.osArch;
|
|
2222
2225
|
this.daemonPid = deviceInfo?.daemonPid;
|
|
2223
2226
|
this.cliVersion = deviceInfo?.cliVersion;
|
|
2227
|
+
this.protocolVersion = deviceInfo?.protocolVersion ?? command_protocol_1.DAEMON_PROTOCOL_VERSION;
|
|
2224
2228
|
this.cliPackageName = deviceInfo?.cliPackageName;
|
|
2225
2229
|
this.capabilities = deviceInfo?.capabilities;
|
|
2230
|
+
this.ptyProviderVersions = deviceInfo?.ptyProviderVersions;
|
|
2226
2231
|
this.environment = deviceInfo?.environment;
|
|
2227
2232
|
this.containerId = deviceInfo?.containerId;
|
|
2233
|
+
this.sessionGeneration = void 0;
|
|
2228
2234
|
this.isDisconnecting = false;
|
|
2235
|
+
this.autoReconnectEnabled = true;
|
|
2229
2236
|
this.isGracefulShutdown = false;
|
|
2230
2237
|
this.isIntentionalDisconnect = false;
|
|
2231
2238
|
this.lastConnectAttemptTime = Date.now();
|
|
@@ -2262,8 +2269,10 @@ var require_websocket_client = __commonJS({
|
|
|
2262
2269
|
type: "auth",
|
|
2263
2270
|
token,
|
|
2264
2271
|
version: this.cliVersion || version_1.VERSION,
|
|
2272
|
+
protocolVersion: this.protocolVersion,
|
|
2265
2273
|
cliPackageName: this.cliPackageName,
|
|
2266
2274
|
capabilities: this.capabilities,
|
|
2275
|
+
ptyProviderVersions: this.ptyProviderVersions,
|
|
2267
2276
|
environment: this.environment,
|
|
2268
2277
|
machineId,
|
|
2269
2278
|
containerId: this.containerId,
|
|
@@ -2295,6 +2304,7 @@ var require_websocket_client = __commonJS({
|
|
|
2295
2304
|
this.ws.on("close", (code, reason) => {
|
|
2296
2305
|
console.log(`[EpisodaClient] WebSocket closed: ${code} ${reason.toString()}`);
|
|
2297
2306
|
this.isConnected = false;
|
|
2307
|
+
this.sessionGeneration = void 0;
|
|
2298
2308
|
const willReconnect = !this.isDisconnecting && this.autoReconnectEnabled;
|
|
2299
2309
|
this.emit({
|
|
2300
2310
|
type: "disconnected",
|
|
@@ -2356,6 +2366,7 @@ var require_websocket_client = __commonJS({
|
|
|
2356
2366
|
this.ws = void 0;
|
|
2357
2367
|
}
|
|
2358
2368
|
this.isConnected = false;
|
|
2369
|
+
this.sessionGeneration = void 0;
|
|
2359
2370
|
}
|
|
2360
2371
|
/**
|
|
2361
2372
|
* Register an event handler
|
|
@@ -2433,7 +2444,8 @@ var require_websocket_client = __commonJS({
|
|
|
2433
2444
|
*/
|
|
2434
2445
|
getStatus() {
|
|
2435
2446
|
return {
|
|
2436
|
-
connected: this.isConnected
|
|
2447
|
+
connected: this.isConnected,
|
|
2448
|
+
sessionGeneration: this.sessionGeneration
|
|
2437
2449
|
};
|
|
2438
2450
|
}
|
|
2439
2451
|
/**
|
|
@@ -2475,6 +2487,9 @@ var require_websocket_client = __commonJS({
|
|
|
2475
2487
|
* Handle incoming message from server
|
|
2476
2488
|
*/
|
|
2477
2489
|
handleMessage(message) {
|
|
2490
|
+
if (message.type === "auth_success") {
|
|
2491
|
+
this.sessionGeneration = message.sessionGeneration;
|
|
2492
|
+
}
|
|
2478
2493
|
if (message.type === "shutdown") {
|
|
2479
2494
|
console.log("[EpisodaClient] Received graceful shutdown message from server");
|
|
2480
2495
|
this.isGracefulShutdown = true;
|
|
@@ -2492,6 +2507,11 @@ var require_websocket_client = __commonJS({
|
|
|
2492
2507
|
this.consecutiveAuthFailures++;
|
|
2493
2508
|
console.warn(`[EpisodaClient] Auth failure (${this.consecutiveAuthFailures}): ${errorMessage.code}`);
|
|
2494
2509
|
}
|
|
2510
|
+
if (errorMessage.code === "DAEMON_UPDATE_REQUIRED" || errorMessage.code === "PROTOCOL_MISMATCH") {
|
|
2511
|
+
this.autoReconnectEnabled = false;
|
|
2512
|
+
const errorText = "message" in errorMessage && typeof errorMessage.message === "string" ? errorMessage.message : "Daemon update required";
|
|
2513
|
+
console.error(`[EpisodaClient] ${errorMessage.code}: ${errorText}`);
|
|
2514
|
+
}
|
|
2495
2515
|
}
|
|
2496
2516
|
const handlers = this.eventHandlers.get(message.type) || [];
|
|
2497
2517
|
handlers.forEach((handler) => {
|
|
@@ -2621,8 +2641,10 @@ var require_websocket_client = __commonJS({
|
|
|
2621
2641
|
osArch: this.osArch,
|
|
2622
2642
|
daemonPid: this.daemonPid,
|
|
2623
2643
|
cliVersion: this.cliVersion,
|
|
2644
|
+
protocolVersion: this.protocolVersion,
|
|
2624
2645
|
cliPackageName: this.cliPackageName,
|
|
2625
2646
|
capabilities: this.capabilities,
|
|
2647
|
+
ptyProviderVersions: this.ptyProviderVersions,
|
|
2626
2648
|
environment: this.environment,
|
|
2627
2649
|
containerId: this.containerId
|
|
2628
2650
|
}).then(() => {
|
|
@@ -3052,7 +3074,7 @@ var require_package = __commonJS({
|
|
|
3052
3074
|
"package.json"(exports2, module2) {
|
|
3053
3075
|
module2.exports = {
|
|
3054
3076
|
name: "@episoda/cli",
|
|
3055
|
-
version: "0.2.
|
|
3077
|
+
version: "0.2.223",
|
|
3056
3078
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
3057
3079
|
main: "dist/index.js",
|
|
3058
3080
|
types: "dist/index.d.ts",
|
|
@@ -3314,6 +3336,10 @@ function removeProject(projectPath) {
|
|
|
3314
3336
|
}
|
|
3315
3337
|
return false;
|
|
3316
3338
|
}
|
|
3339
|
+
function getProject(projectPath) {
|
|
3340
|
+
const data = readProjects();
|
|
3341
|
+
return data.projects.find((p) => p.path === projectPath) || null;
|
|
3342
|
+
}
|
|
3317
3343
|
function getAllProjects() {
|
|
3318
3344
|
const data = readProjects();
|
|
3319
3345
|
return data.projects;
|
|
@@ -13262,7 +13288,8 @@ async function handlePtySpawn(payload, client) {
|
|
|
13262
13288
|
moduleUid,
|
|
13263
13289
|
agent_run_id,
|
|
13264
13290
|
code: -1,
|
|
13265
|
-
durationMs: 0
|
|
13291
|
+
durationMs: 0,
|
|
13292
|
+
sessionGeneration: client.getStatus().sessionGeneration
|
|
13266
13293
|
});
|
|
13267
13294
|
return {
|
|
13268
13295
|
success: false,
|
|
@@ -13328,7 +13355,8 @@ async function handlePtySpawn(payload, client) {
|
|
|
13328
13355
|
moduleUid,
|
|
13329
13356
|
agent_run_id,
|
|
13330
13357
|
code: exitCode,
|
|
13331
|
-
durationMs
|
|
13358
|
+
durationMs,
|
|
13359
|
+
sessionGeneration: client.getStatus().sessionGeneration
|
|
13332
13360
|
}).catch((err) => {
|
|
13333
13361
|
console.error(`[PTY] Failed to send pty_exit for ${agent_run_id}:`, err.message);
|
|
13334
13362
|
});
|
|
@@ -15496,7 +15524,8 @@ var ProjectMessageRouter = class {
|
|
|
15496
15524
|
startupConfirmedAt: result.startupConfirmedAt,
|
|
15497
15525
|
modelUsed: result.modelUsed,
|
|
15498
15526
|
launchConfig: result.launchConfig,
|
|
15499
|
-
authPath: result.authPath
|
|
15527
|
+
authPath: result.authPath,
|
|
15528
|
+
sessionGeneration: client.getStatus().sessionGeneration
|
|
15500
15529
|
});
|
|
15501
15530
|
}
|
|
15502
15531
|
});
|
|
@@ -15524,7 +15553,8 @@ var ProjectMessageRouter = class {
|
|
|
15524
15553
|
requestId: message.id,
|
|
15525
15554
|
agent_run_id: payload.agent_run_id,
|
|
15526
15555
|
success: result.success,
|
|
15527
|
-
error: result.error
|
|
15556
|
+
error: result.error,
|
|
15557
|
+
sessionGeneration: client.getStatus().sessionGeneration
|
|
15528
15558
|
});
|
|
15529
15559
|
if (!result.success) {
|
|
15530
15560
|
console.warn(`[Daemon] EP1476: pty_stdin failed for run ${payload.agent_run_id}: ${result.error}`);
|
|
@@ -15815,6 +15845,69 @@ function collectUnexpectedWorktrees(params) {
|
|
|
15815
15845
|
|
|
15816
15846
|
// src/daemon/daemon-process.ts
|
|
15817
15847
|
var packageJson = require_package();
|
|
15848
|
+
var DAEMON_PROTOCOL_VERSION = 2;
|
|
15849
|
+
function resolveBundledBinaryCandidates(binaryName) {
|
|
15850
|
+
return [
|
|
15851
|
+
path37.join(__dirname, "..", "..", "node_modules", ".bin", binaryName),
|
|
15852
|
+
path37.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", binaryName),
|
|
15853
|
+
path37.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", binaryName)
|
|
15854
|
+
];
|
|
15855
|
+
}
|
|
15856
|
+
function tryReadBinaryVersionAsync(binaryPath, versionPattern) {
|
|
15857
|
+
return new Promise((resolve10) => {
|
|
15858
|
+
(0, import_child_process18.execFile)(
|
|
15859
|
+
binaryPath,
|
|
15860
|
+
["--version"],
|
|
15861
|
+
{
|
|
15862
|
+
encoding: "utf-8",
|
|
15863
|
+
timeout: 5e3,
|
|
15864
|
+
windowsHide: true
|
|
15865
|
+
},
|
|
15866
|
+
(error, stdout) => {
|
|
15867
|
+
if (error) {
|
|
15868
|
+
resolve10(void 0);
|
|
15869
|
+
return;
|
|
15870
|
+
}
|
|
15871
|
+
const output = String(stdout ?? "").trim();
|
|
15872
|
+
const match = output.match(versionPattern);
|
|
15873
|
+
resolve10(match?.[1]);
|
|
15874
|
+
}
|
|
15875
|
+
);
|
|
15876
|
+
});
|
|
15877
|
+
}
|
|
15878
|
+
async function detectPtyProviderVersions() {
|
|
15879
|
+
const providers = [
|
|
15880
|
+
{ key: "claude", binaryName: "claude", versionPattern: /(\d+\.\d+(?:\.\d+)?)/ },
|
|
15881
|
+
{ key: "codex", binaryName: "codex", versionPattern: /(\d+\.\d+(?:\.\d+)?)/i }
|
|
15882
|
+
];
|
|
15883
|
+
const versions = {};
|
|
15884
|
+
for (const provider of providers) {
|
|
15885
|
+
const bundledCandidates = resolveBundledBinaryCandidates(provider.binaryName);
|
|
15886
|
+
const candidates = bundledCandidates.filter((candidate, index, arr) => fs35.existsSync(candidate) && arr.indexOf(candidate) === index);
|
|
15887
|
+
for (const candidate of candidates) {
|
|
15888
|
+
const version = await tryReadBinaryVersionAsync(candidate, provider.versionPattern);
|
|
15889
|
+
if (version) {
|
|
15890
|
+
versions[provider.key] = version;
|
|
15891
|
+
break;
|
|
15892
|
+
}
|
|
15893
|
+
}
|
|
15894
|
+
if (versions[provider.key]) {
|
|
15895
|
+
continue;
|
|
15896
|
+
}
|
|
15897
|
+
const globalVersion = await tryReadBinaryVersionAsync(provider.binaryName, provider.versionPattern);
|
|
15898
|
+
if (globalVersion) {
|
|
15899
|
+
versions[provider.key] = globalVersion;
|
|
15900
|
+
}
|
|
15901
|
+
}
|
|
15902
|
+
return versions;
|
|
15903
|
+
}
|
|
15904
|
+
var detectedPtyProviderVersionsPromise = null;
|
|
15905
|
+
function getDetectedPtyProviderVersions() {
|
|
15906
|
+
if (!detectedPtyProviderVersionsPromise) {
|
|
15907
|
+
detectedPtyProviderVersionsPromise = detectPtyProviderVersions().catch(() => ({}));
|
|
15908
|
+
}
|
|
15909
|
+
return detectedPtyProviderVersionsPromise;
|
|
15910
|
+
}
|
|
15818
15911
|
var Daemon = class _Daemon {
|
|
15819
15912
|
constructor() {
|
|
15820
15913
|
this.machineId = "";
|
|
@@ -15857,6 +15950,9 @@ var Daemon = class _Daemon {
|
|
|
15857
15950
|
static {
|
|
15858
15951
|
this.AGENT_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
15859
15952
|
}
|
|
15953
|
+
static {
|
|
15954
|
+
this.DISCONNECTED_PROJECT_RECONNECT_DELAY_MS = 2e3;
|
|
15955
|
+
}
|
|
15860
15956
|
static {
|
|
15861
15957
|
this.HEALTH_PORT = 9999;
|
|
15862
15958
|
}
|
|
@@ -15911,6 +16007,32 @@ var Daemon = class _Daemon {
|
|
|
15911
16007
|
console.warn(`[Daemon] EP1429: Failed to stop session ${sessionId} after continue=false`, error);
|
|
15912
16008
|
}
|
|
15913
16009
|
}
|
|
16010
|
+
scheduleTrackedProjectReconnect(projectId, projectPath, reason) {
|
|
16011
|
+
const trackedProject = getProject(projectPath);
|
|
16012
|
+
const connection = this.connectionManager.getConnection(projectPath);
|
|
16013
|
+
if (!trackedProject || !connection) {
|
|
16014
|
+
return false;
|
|
16015
|
+
}
|
|
16016
|
+
if (connection.reconnectTimer) {
|
|
16017
|
+
clearTimeout(connection.reconnectTimer);
|
|
16018
|
+
}
|
|
16019
|
+
connection.reconnectTimer = setTimeout(() => {
|
|
16020
|
+
const latest = this.connectionManager.getConnection(projectPath);
|
|
16021
|
+
if (latest) {
|
|
16022
|
+
latest.reconnectTimer = void 0;
|
|
16023
|
+
}
|
|
16024
|
+
void this.connectProject(projectId, projectPath).catch((error) => {
|
|
16025
|
+
console.warn(
|
|
16026
|
+
`[Daemon] Automatic reconnect failed for ${projectPath}:`,
|
|
16027
|
+
error instanceof Error ? error.message : error
|
|
16028
|
+
);
|
|
16029
|
+
});
|
|
16030
|
+
}, _Daemon.DISCONNECTED_PROJECT_RECONNECT_DELAY_MS);
|
|
16031
|
+
console.log(
|
|
16032
|
+
`[Daemon] Scheduling reconnect for ${projectPath} in ${_Daemon.DISCONNECTED_PROJECT_RECONNECT_DELAY_MS}ms (${reason})`
|
|
16033
|
+
);
|
|
16034
|
+
return true;
|
|
16035
|
+
}
|
|
15914
16036
|
startAgentHeartbeatLoop(sessionId, client) {
|
|
15915
16037
|
if (this.agentHeartbeatLoops.has(sessionId)) return;
|
|
15916
16038
|
let stopped = false;
|
|
@@ -16598,6 +16720,14 @@ var Daemon = class _Daemon {
|
|
|
16598
16720
|
client.setAutoReconnect(true);
|
|
16599
16721
|
console.log(`[Daemon] Authenticated for project ${projectId}`);
|
|
16600
16722
|
touchProject(projectPath);
|
|
16723
|
+
const activeConnection = this.connectionManager.getConnection(projectPath);
|
|
16724
|
+
if (activeConnection?.reconnectTimer) {
|
|
16725
|
+
clearTimeout(activeConnection.reconnectTimer);
|
|
16726
|
+
activeConnection.reconnectTimer = void 0;
|
|
16727
|
+
}
|
|
16728
|
+
if (activeConnection) {
|
|
16729
|
+
activeConnection.manualDisconnectRequested = false;
|
|
16730
|
+
}
|
|
16601
16731
|
this.connectionManager.addLiveConnection(projectPath);
|
|
16602
16732
|
this.lastLiveConnectionAt = Date.now();
|
|
16603
16733
|
this.lastDisconnectAt = null;
|
|
@@ -16754,6 +16884,8 @@ var Daemon = class _Daemon {
|
|
|
16754
16884
|
});
|
|
16755
16885
|
client.on("disconnected", (event) => {
|
|
16756
16886
|
const disconnectEvent = event;
|
|
16887
|
+
const activeConnection = this.connectionManager.getConnection(projectPath);
|
|
16888
|
+
const manualDisconnectRequested = activeConnection?.manualDisconnectRequested === true;
|
|
16757
16889
|
console.log(`[Daemon] Connection closed for ${projectId}: code=${disconnectEvent.code}, willReconnect=${disconnectEvent.willReconnect}`);
|
|
16758
16890
|
this.logReliabilityMetric("ws_transport_disconnect", {
|
|
16759
16891
|
projectId,
|
|
@@ -16767,7 +16899,12 @@ var Daemon = class _Daemon {
|
|
|
16767
16899
|
this.lastDisconnectAt = this.lastDisconnectAt || Date.now();
|
|
16768
16900
|
}
|
|
16769
16901
|
this.updateManager.applyPendingUpdateIfIdle();
|
|
16770
|
-
|
|
16902
|
+
const daemonReconnectScheduled = !disconnectEvent.willReconnect && !manualDisconnectRequested ? this.scheduleTrackedProjectReconnect(
|
|
16903
|
+
projectId,
|
|
16904
|
+
projectPath,
|
|
16905
|
+
disconnectEvent.reason || "transport_disconnected"
|
|
16906
|
+
) : false;
|
|
16907
|
+
if (!disconnectEvent.willReconnect && (manualDisconnectRequested || !daemonReconnectScheduled)) {
|
|
16771
16908
|
this.connectionManager.deleteConnection(projectPath);
|
|
16772
16909
|
console.log(`[Daemon] Removed connection for ${projectPath} from map`);
|
|
16773
16910
|
}
|
|
@@ -16787,17 +16924,21 @@ var Daemon = class _Daemon {
|
|
|
16787
16924
|
const environment = modeConfig.mode;
|
|
16788
16925
|
const containerId = process.env.EPISODA_CONTAINER_ID;
|
|
16789
16926
|
const capabilities = ["worktree_create_v1", "pty_v1"];
|
|
16790
|
-
|
|
16927
|
+
const ptyProviderVersions = await getDetectedPtyProviderVersions();
|
|
16928
|
+
const deviceInfo = {
|
|
16791
16929
|
hostname: os16.hostname(),
|
|
16792
16930
|
osPlatform: os16.platform(),
|
|
16793
16931
|
osArch: os16.arch(),
|
|
16794
16932
|
daemonPid,
|
|
16795
16933
|
cliVersion: this.cliRuntimeVersion,
|
|
16934
|
+
protocolVersion: DAEMON_PROTOCOL_VERSION,
|
|
16796
16935
|
cliPackageName: packageJson.name,
|
|
16797
16936
|
capabilities,
|
|
16937
|
+
ptyProviderVersions,
|
|
16798
16938
|
environment,
|
|
16799
16939
|
containerId
|
|
16800
|
-
}
|
|
16940
|
+
};
|
|
16941
|
+
await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, deviceInfo);
|
|
16801
16942
|
console.log(`[Daemon] Successfully connected to project ${projectId}`);
|
|
16802
16943
|
this.connectionManager.setState(projectPath, "authenticating");
|
|
16803
16944
|
await this.connectionManager.waitForAuthentication(client, 3e4);
|
|
@@ -16843,8 +16984,10 @@ var Daemon = class _Daemon {
|
|
|
16843
16984
|
if (!connection) {
|
|
16844
16985
|
return;
|
|
16845
16986
|
}
|
|
16987
|
+
connection.manualDisconnectRequested = true;
|
|
16846
16988
|
if (connection.reconnectTimer) {
|
|
16847
16989
|
clearTimeout(connection.reconnectTimer);
|
|
16990
|
+
connection.reconnectTimer = void 0;
|
|
16848
16991
|
}
|
|
16849
16992
|
await connection.client.disconnect();
|
|
16850
16993
|
this.connectionManager.deleteConnection(projectPath);
|