@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.
@@ -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.221",
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
- if (!disconnectEvent.willReconnect) {
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
- await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, {
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);