@episoda/cli 0.2.174 → 0.2.175

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.
@@ -2996,7 +2996,7 @@ var require_package = __commonJS({
2996
2996
  "package.json"(exports2, module2) {
2997
2997
  module2.exports = {
2998
2998
  name: "@episoda/cli",
2999
- version: "0.2.174",
2999
+ version: "0.2.175",
3000
3000
  description: "CLI tool for Episoda local development workflow orchestration",
3001
3001
  main: "dist/index.js",
3002
3002
  types: "dist/index.d.ts",
@@ -7630,26 +7630,12 @@ var AgentControlPlane = class {
7630
7630
  const existingSession = this.sessions.get(sessionId);
7631
7631
  if (existingSession) {
7632
7632
  if (existingSession.provider === provider && existingSession.moduleId === moduleId) {
7633
- console.log(`[AgentManager] EP1232: Session ${sessionId} already exists, treating start as message (idempotent)`);
7633
+ console.log(`[AgentManager] EP1417: Session ${sessionId} already exists, acknowledging duplicate start`);
7634
7634
  if (requestedWorkspaceId && !normalizeWorkspaceId(existingSession.workspaceId)) {
7635
7635
  existingSession.workspaceId = requestedWorkspaceId;
7636
7636
  console.log(`[AgentManager] EP1357: Updated session ${sessionId} workspaceId from start options`);
7637
7637
  }
7638
- return this.sendMessage({
7639
- sessionId,
7640
- message,
7641
- isFirstMessage: false,
7642
- // Not first since session exists
7643
- canWrite,
7644
- readOnlyReason,
7645
- onChunk,
7646
- onToolUse,
7647
- // EP1236: Pass tool use callback
7648
- onToolResult,
7649
- // EP1311: Pass tool result callback
7650
- onComplete,
7651
- onError
7652
- });
7638
+ return { success: true };
7653
7639
  }
7654
7640
  console.log(`[AgentManager] EP1232: Session ${sessionId} exists with incompatible config - provider: ${existingSession.provider} vs ${provider}, module: ${existingSession.moduleId} vs ${moduleId}`);
7655
7641
  return { success: false, error: "Session already exists with incompatible configuration" };
@@ -13609,6 +13595,8 @@ var HealthOrchestrator = class _HealthOrchestrator {
13609
13595
  this.tunnelPollInterval = null;
13610
13596
  // EP833: Track consecutive health check failures per tunnel
13611
13597
  this.tunnelHealthFailures = /* @__PURE__ */ new Map();
13598
+ // Restart after 2 consecutive failures
13599
+ this.tunnelRestartCooldownUntil = /* @__PURE__ */ new Map();
13612
13600
  // 3 second timeout for health checks
13613
13601
  // EP929: Health check polling interval (restored from EP843 removal)
13614
13602
  // Health checks are orthogonal to push-based state sync - they detect dead tunnels
@@ -13623,7 +13611,11 @@ var HealthOrchestrator = class _HealthOrchestrator {
13623
13611
  this.HEALTH_CHECK_FAILURE_THRESHOLD = 2;
13624
13612
  }
13625
13613
  static {
13626
- // Restart after 2 consecutive failures
13614
+ // moduleUid -> unix ms
13615
+ this.HEALTH_RESTART_COOLDOWN_MS = 3 * 60 * 1e3;
13616
+ }
13617
+ static {
13618
+ // 3 minutes
13627
13619
  this.HEALTH_CHECK_TIMEOUT_MS = 3e3;
13628
13620
  }
13629
13621
  static {
@@ -14043,20 +14035,55 @@ var HealthOrchestrator = class _HealthOrchestrator {
14043
14035
  const isHealthy = await this.checkTunnelHealth(tunnel);
14044
14036
  if (isHealthy) {
14045
14037
  this.tunnelHealthFailures.delete(tunnel.moduleUid);
14038
+ this.tunnelRestartCooldownUntil.delete(tunnel.moduleUid);
14046
14039
  } else {
14047
14040
  const failures = (this.tunnelHealthFailures.get(tunnel.moduleUid) || 0) + 1;
14048
14041
  this.tunnelHealthFailures.set(tunnel.moduleUid, failures);
14049
14042
  console.log(`[Daemon] EP833: Health check failed for ${tunnel.moduleUid} (${failures}/${_HealthOrchestrator.HEALTH_CHECK_FAILURE_THRESHOLD})`);
14050
- if (failures >= _HealthOrchestrator.HEALTH_CHECK_FAILURE_THRESHOLD) {
14051
- console.log(`[Daemon] EP833: Tunnel unhealthy for ${tunnel.moduleUid}, restarting...`);
14052
- await this.host.withTunnelLock(tunnel.moduleUid, async () => {
14053
- await this.restartTunnel(tunnel.moduleUid, tunnel.port);
14054
- });
14055
- this.tunnelHealthFailures.delete(tunnel.moduleUid);
14043
+ if (failures < _HealthOrchestrator.HEALTH_CHECK_FAILURE_THRESHOLD) {
14044
+ continue;
14056
14045
  }
14046
+ const now = Date.now();
14047
+ const cooldownUntil = this.tunnelRestartCooldownUntil.get(tunnel.moduleUid) || 0;
14048
+ if (cooldownUntil > now) {
14049
+ continue;
14050
+ }
14051
+ const healthProjectId = config.project_id || config.projectId;
14052
+ if (healthProjectId) {
14053
+ const activeModuleUids = await this.fetchActiveModuleUids(healthProjectId);
14054
+ if (activeModuleUids && !activeModuleUids.includes(tunnel.moduleUid)) {
14055
+ console.log(`[Daemon] EP1417: Skipping health auto-restart for ${tunnel.moduleUid}; module is no longer active`);
14056
+ await this.retireInactiveTunnel(tunnel.moduleUid);
14057
+ this.tunnelHealthFailures.delete(tunnel.moduleUid);
14058
+ this.tunnelRestartCooldownUntil.delete(tunnel.moduleUid);
14059
+ continue;
14060
+ }
14061
+ }
14062
+ console.log(`[Daemon] EP833: Tunnel unhealthy for ${tunnel.moduleUid}, restarting...`);
14063
+ await this.host.withTunnelLock(tunnel.moduleUid, async () => {
14064
+ await this.restartTunnel(tunnel.moduleUid, tunnel.port);
14065
+ });
14066
+ this.tunnelHealthFailures.delete(tunnel.moduleUid);
14067
+ this.tunnelRestartCooldownUntil.set(
14068
+ tunnel.moduleUid,
14069
+ now + _HealthOrchestrator.HEALTH_RESTART_COOLDOWN_MS
14070
+ );
14057
14071
  }
14058
14072
  }
14059
14073
  }
14074
+ async retireInactiveTunnel(moduleUid) {
14075
+ try {
14076
+ const tunnelManager = getTunnelManager();
14077
+ await tunnelManager.stopTunnel(moduleUid);
14078
+ } catch (error) {
14079
+ console.warn(`[Daemon] EP1417: Failed to stop inactive tunnel for ${moduleUid}:`, error);
14080
+ }
14081
+ try {
14082
+ await stopDevServer(moduleUid);
14083
+ } catch (error) {
14084
+ console.warn(`[Daemon] EP1417: Failed to stop inactive dev server for ${moduleUid}:`, error);
14085
+ }
14086
+ }
14060
14087
  /**
14061
14088
  * EP833: Check if a tunnel is healthy
14062
14089
  * EP1042: Now also verifies dev server ownership (correct worktree)