@episoda/cli 0.2.171 → 0.2.173

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.
@@ -1640,15 +1640,15 @@ var require_git_executor = __commonJS({
1640
1640
  try {
1641
1641
  const { stdout: gitDir } = await execAsync3("git rev-parse --git-dir", { cwd, timeout: 5e3 });
1642
1642
  const gitDirPath = gitDir.trim();
1643
- const fs34 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1643
+ const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1644
1644
  const rebaseMergePath = `${gitDirPath}/rebase-merge`;
1645
1645
  const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
1646
1646
  try {
1647
- await fs34.access(rebaseMergePath);
1647
+ await fs35.access(rebaseMergePath);
1648
1648
  inRebase = true;
1649
1649
  } catch {
1650
1650
  try {
1651
- await fs34.access(rebaseApplyPath);
1651
+ await fs35.access(rebaseApplyPath);
1652
1652
  inRebase = true;
1653
1653
  } catch {
1654
1654
  inRebase = false;
@@ -1704,9 +1704,9 @@ var require_git_executor = __commonJS({
1704
1704
  };
1705
1705
  }
1706
1706
  }
1707
- const fs34 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1707
+ const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1708
1708
  try {
1709
- await fs34.access(command.path);
1709
+ await fs35.access(command.path);
1710
1710
  return {
1711
1711
  success: false,
1712
1712
  error: "WORKTREE_EXISTS",
@@ -1765,9 +1765,9 @@ var require_git_executor = __commonJS({
1765
1765
  */
1766
1766
  async executeWorktreeRemove(command, cwd, options) {
1767
1767
  try {
1768
- const fs34 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1768
+ const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1769
1769
  try {
1770
- await fs34.access(command.path);
1770
+ await fs35.access(command.path);
1771
1771
  } catch {
1772
1772
  return {
1773
1773
  success: false,
@@ -1802,7 +1802,7 @@ var require_git_executor = __commonJS({
1802
1802
  const result = await this.runGitCommand(args, cwd, options);
1803
1803
  if (result.success) {
1804
1804
  try {
1805
- await fs34.rm(command.path, { recursive: true, force: true });
1805
+ await fs35.rm(command.path, { recursive: true, force: true });
1806
1806
  } catch {
1807
1807
  }
1808
1808
  return {
@@ -1936,10 +1936,10 @@ var require_git_executor = __commonJS({
1936
1936
  */
1937
1937
  async executeCloneBare(command, options) {
1938
1938
  try {
1939
- const fs34 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1940
- const path34 = await Promise.resolve().then(() => __importStar(require("path")));
1939
+ const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1940
+ const path35 = await Promise.resolve().then(() => __importStar(require("path")));
1941
1941
  try {
1942
- await fs34.access(command.path);
1942
+ await fs35.access(command.path);
1943
1943
  return {
1944
1944
  success: false,
1945
1945
  error: "BRANCH_ALREADY_EXISTS",
@@ -1948,9 +1948,9 @@ var require_git_executor = __commonJS({
1948
1948
  };
1949
1949
  } catch {
1950
1950
  }
1951
- const parentDir = path34.dirname(command.path);
1951
+ const parentDir = path35.dirname(command.path);
1952
1952
  try {
1953
- await fs34.mkdir(parentDir, { recursive: true });
1953
+ await fs35.mkdir(parentDir, { recursive: true });
1954
1954
  } catch {
1955
1955
  }
1956
1956
  const { stdout, stderr } = await execAsync3(
@@ -1998,22 +1998,22 @@ var require_git_executor = __commonJS({
1998
1998
  */
1999
1999
  async executeProjectInfo(cwd, options) {
2000
2000
  try {
2001
- const fs34 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
2002
- const path34 = await Promise.resolve().then(() => __importStar(require("path")));
2001
+ const fs35 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
2002
+ const path35 = await Promise.resolve().then(() => __importStar(require("path")));
2003
2003
  let currentPath = cwd;
2004
2004
  let projectPath = cwd;
2005
2005
  let bareRepoPath;
2006
2006
  for (let i = 0; i < 10; i++) {
2007
- const bareDir = path34.join(currentPath, ".bare");
2008
- const episodaDir = path34.join(currentPath, ".episoda");
2007
+ const bareDir = path35.join(currentPath, ".bare");
2008
+ const episodaDir = path35.join(currentPath, ".episoda");
2009
2009
  try {
2010
- await fs34.access(bareDir);
2011
- await fs34.access(episodaDir);
2010
+ await fs35.access(bareDir);
2011
+ await fs35.access(episodaDir);
2012
2012
  projectPath = currentPath;
2013
2013
  bareRepoPath = bareDir;
2014
2014
  break;
2015
2015
  } catch {
2016
- const parentPath = path34.dirname(currentPath);
2016
+ const parentPath = path35.dirname(currentPath);
2017
2017
  if (parentPath === currentPath) {
2018
2018
  break;
2019
2019
  }
@@ -2235,7 +2235,7 @@ var require_websocket_client = __commonJS({
2235
2235
  clearTimeout(this.reconnectTimeout);
2236
2236
  this.reconnectTimeout = void 0;
2237
2237
  }
2238
- return new Promise((resolve4, reject) => {
2238
+ return new Promise((resolve6, reject) => {
2239
2239
  const connectionTimeout = setTimeout(() => {
2240
2240
  if (this.ws) {
2241
2241
  this.ws.terminate();
@@ -2266,7 +2266,7 @@ var require_websocket_client = __commonJS({
2266
2266
  daemonPid: this.daemonPid
2267
2267
  });
2268
2268
  this.startHeartbeat();
2269
- resolve4();
2269
+ resolve6();
2270
2270
  });
2271
2271
  this.ws.on("pong", () => {
2272
2272
  if (this.heartbeatTimeoutTimer) {
@@ -2397,13 +2397,13 @@ var require_websocket_client = __commonJS({
2397
2397
  console.warn("[EpisodaClient] Cannot send - WebSocket not connected");
2398
2398
  return false;
2399
2399
  }
2400
- return new Promise((resolve4) => {
2400
+ return new Promise((resolve6) => {
2401
2401
  this.ws.send(JSON.stringify(message), (error) => {
2402
2402
  if (error) {
2403
2403
  console.error("[EpisodaClient] Failed to send message:", error);
2404
- resolve4(false);
2404
+ resolve6(false);
2405
2405
  } else {
2406
- resolve4(true);
2406
+ resolve6(true);
2407
2407
  }
2408
2408
  });
2409
2409
  });
@@ -2674,33 +2674,33 @@ var require_auth = __commonJS({
2674
2674
  exports2.loadConfig = loadConfig16;
2675
2675
  exports2.saveConfig = saveConfig4;
2676
2676
  exports2.validateToken = validateToken;
2677
- var fs34 = __importStar(require("fs"));
2678
- var path34 = __importStar(require("path"));
2679
- var os15 = __importStar(require("os"));
2677
+ var fs35 = __importStar(require("fs"));
2678
+ var path35 = __importStar(require("path"));
2679
+ var os16 = __importStar(require("os"));
2680
2680
  var child_process_1 = require("child_process");
2681
2681
  var DEFAULT_CONFIG_FILE = "config.json";
2682
2682
  var hasWarnedMissingProjectId = false;
2683
2683
  var hasWarnedMissingRequiredFields = false;
2684
2684
  function getConfigDir10() {
2685
- return process.env.EPISODA_CONFIG_DIR || path34.join(os15.homedir(), ".episoda");
2685
+ return process.env.EPISODA_CONFIG_DIR || path35.join(os16.homedir(), ".episoda");
2686
2686
  }
2687
2687
  function getConfigPath(configPath) {
2688
2688
  if (configPath) {
2689
2689
  return configPath;
2690
2690
  }
2691
- return path34.join(getConfigDir10(), DEFAULT_CONFIG_FILE);
2691
+ return path35.join(getConfigDir10(), DEFAULT_CONFIG_FILE);
2692
2692
  }
2693
2693
  function ensureConfigDir(configPath) {
2694
- const dir = path34.dirname(configPath);
2695
- const isNew = !fs34.existsSync(dir);
2694
+ const dir = path35.dirname(configPath);
2695
+ const isNew = !fs35.existsSync(dir);
2696
2696
  if (isNew) {
2697
- fs34.mkdirSync(dir, { recursive: true, mode: 448 });
2697
+ fs35.mkdirSync(dir, { recursive: true, mode: 448 });
2698
2698
  }
2699
2699
  if (process.platform === "darwin") {
2700
- const nosyncPath = path34.join(dir, ".nosync");
2701
- if (isNew || !fs34.existsSync(nosyncPath)) {
2700
+ const nosyncPath = path35.join(dir, ".nosync");
2701
+ if (isNew || !fs35.existsSync(nosyncPath)) {
2702
2702
  try {
2703
- fs34.writeFileSync(nosyncPath, "", { mode: 384 });
2703
+ fs35.writeFileSync(nosyncPath, "", { mode: 384 });
2704
2704
  (0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
2705
2705
  stdio: "ignore",
2706
2706
  timeout: 5e3
@@ -2714,11 +2714,11 @@ var require_auth = __commonJS({
2714
2714
  const fullPath = getConfigPath(configPath);
2715
2715
  const isCloudMode = process.env.EPISODA_MODE === "cloud";
2716
2716
  const readConfigFile = (pathToFile) => {
2717
- if (!fs34.existsSync(pathToFile)) {
2717
+ if (!fs35.existsSync(pathToFile)) {
2718
2718
  return null;
2719
2719
  }
2720
2720
  try {
2721
- const content = fs34.readFileSync(pathToFile, "utf8");
2721
+ const content = fs35.readFileSync(pathToFile, "utf8");
2722
2722
  return JSON.parse(content);
2723
2723
  } catch (error) {
2724
2724
  console.error("Error loading config:", error);
@@ -2757,11 +2757,11 @@ var require_auth = __commonJS({
2757
2757
  }
2758
2758
  const homeDir = process.env.HOME || require("os").homedir();
2759
2759
  const workspaceConfigPath = require("path").join(homeDir, "episoda", process.env.EPISODA_WORKSPACE, ".episoda", "config.json");
2760
- if (!fs34.existsSync(workspaceConfigPath)) {
2760
+ if (!fs35.existsSync(workspaceConfigPath)) {
2761
2761
  return null;
2762
2762
  }
2763
2763
  try {
2764
- const content = fs34.readFileSync(workspaceConfigPath, "utf8");
2764
+ const content = fs35.readFileSync(workspaceConfigPath, "utf8");
2765
2765
  const workspaceConfig2 = JSON.parse(content);
2766
2766
  const expiresAtEnv = envValue(process.env.EPISODA_ACCESS_TOKEN_EXPIRES_AT);
2767
2767
  return {
@@ -2789,11 +2789,11 @@ var require_auth = __commonJS({
2789
2789
  }
2790
2790
  const homeDir = process.env.HOME || require("os").homedir();
2791
2791
  const projectConfigPath = require("path").join(homeDir, "episoda", workspaceSlug, projectSlug, ".episoda", "config.json");
2792
- if (!fs34.existsSync(projectConfigPath)) {
2792
+ if (!fs35.existsSync(projectConfigPath)) {
2793
2793
  return null;
2794
2794
  }
2795
2795
  try {
2796
- const content = fs34.readFileSync(projectConfigPath, "utf8");
2796
+ const content = fs35.readFileSync(projectConfigPath, "utf8");
2797
2797
  const projectConfig2 = JSON.parse(content);
2798
2798
  return {
2799
2799
  project_id: projectConfig2.projectId || projectConfig2.project_id,
@@ -2861,7 +2861,7 @@ var require_auth = __commonJS({
2861
2861
  ensureConfigDir(fullPath);
2862
2862
  try {
2863
2863
  const content = JSON.stringify(config, null, 2);
2864
- fs34.writeFileSync(fullPath, content, { mode: 384 });
2864
+ fs35.writeFileSync(fullPath, content, { mode: 384 });
2865
2865
  } catch (error) {
2866
2866
  throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
2867
2867
  }
@@ -2931,6 +2931,23 @@ Branch: ${details.branchName}`;
2931
2931
  }
2932
2932
  });
2933
2933
 
2934
+ // ../core/dist/internal-env-keys.js
2935
+ var require_internal_env_keys = __commonJS({
2936
+ "../core/dist/internal-env-keys.js"(exports2) {
2937
+ "use strict";
2938
+ Object.defineProperty(exports2, "__esModule", { value: true });
2939
+ exports2.INTERNAL_ONLY_ENV_KEYS = void 0;
2940
+ exports2.isInternalOnlyEnvKey = isInternalOnlyEnvKey;
2941
+ exports2.INTERNAL_ONLY_ENV_KEYS = [
2942
+ "AI_CREDENTIALS_ENCRYPTION_KEY"
2943
+ ];
2944
+ var INTERNAL_ONLY_ENV_KEY_SET = new Set(exports2.INTERNAL_ONLY_ENV_KEYS);
2945
+ function isInternalOnlyEnvKey(key) {
2946
+ return INTERNAL_ONLY_ENV_KEY_SET.has(key.toUpperCase());
2947
+ }
2948
+ }
2949
+ });
2950
+
2934
2951
  // ../core/dist/index.js
2935
2952
  var require_dist = __commonJS({
2936
2953
  "../core/dist/index.js"(exports2) {
@@ -2966,6 +2983,7 @@ var require_dist = __commonJS({
2966
2983
  __exportStar(require_errors(), exports2);
2967
2984
  __exportStar(require_git_validator(), exports2);
2968
2985
  __exportStar(require_git_parser(), exports2);
2986
+ __exportStar(require_internal_env_keys(), exports2);
2969
2987
  var version_1 = require_version();
2970
2988
  Object.defineProperty(exports2, "VERSION", { enumerable: true, get: function() {
2971
2989
  return version_1.VERSION;
@@ -2978,7 +2996,7 @@ var require_package = __commonJS({
2978
2996
  "package.json"(exports2, module2) {
2979
2997
  module2.exports = {
2980
2998
  name: "@episoda/cli",
2981
- version: "0.2.171",
2999
+ version: "0.2.173",
2982
3000
  description: "CLI tool for Episoda local development workflow orchestration",
2983
3001
  main: "dist/index.js",
2984
3002
  types: "dist/index.d.ts",
@@ -3283,10 +3301,10 @@ var IPCServer = class {
3283
3301
  this.server = net.createServer((socket) => {
3284
3302
  this.handleConnection(socket);
3285
3303
  });
3286
- return new Promise((resolve4, reject) => {
3304
+ return new Promise((resolve6, reject) => {
3287
3305
  this.server.listen(socketPath, () => {
3288
3306
  fs3.chmodSync(socketPath, 384);
3289
- resolve4();
3307
+ resolve6();
3290
3308
  });
3291
3309
  this.server.on("error", reject);
3292
3310
  });
@@ -3297,12 +3315,12 @@ var IPCServer = class {
3297
3315
  async stop() {
3298
3316
  if (!this.server) return;
3299
3317
  const socketPath = getSocketPath();
3300
- return new Promise((resolve4) => {
3318
+ return new Promise((resolve6) => {
3301
3319
  this.server.close(() => {
3302
3320
  if (fs3.existsSync(socketPath)) {
3303
3321
  fs3.unlinkSync(socketPath);
3304
3322
  }
3305
- resolve4();
3323
+ resolve6();
3306
3324
  });
3307
3325
  });
3308
3326
  }
@@ -3528,7 +3546,7 @@ function getDownloadUrl() {
3528
3546
  return platformUrls[arch4] || null;
3529
3547
  }
3530
3548
  async function downloadFile(url, destPath) {
3531
- return new Promise((resolve4, reject) => {
3549
+ return new Promise((resolve6, reject) => {
3532
3550
  const followRedirect = (currentUrl, redirectCount = 0) => {
3533
3551
  if (redirectCount > 5) {
3534
3552
  reject(new Error("Too many redirects"));
@@ -3558,7 +3576,7 @@ async function downloadFile(url, destPath) {
3558
3576
  response.pipe(file);
3559
3577
  file.on("finish", () => {
3560
3578
  file.close();
3561
- resolve4();
3579
+ resolve6();
3562
3580
  });
3563
3581
  file.on("error", (err) => {
3564
3582
  fs4.unlinkSync(destPath);
@@ -3972,10 +3990,10 @@ var TunnelManager = class extends import_events.EventEmitter {
3972
3990
  const isTracked = Array.from(this.tunnelStates.values()).some((s) => s.info.pid === pid);
3973
3991
  console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`);
3974
3992
  this.killByPid(pid, "SIGTERM");
3975
- await new Promise((resolve4) => setTimeout(resolve4, 500));
3993
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
3976
3994
  if (this.isProcessRunning(pid)) {
3977
3995
  this.killByPid(pid, "SIGKILL");
3978
- await new Promise((resolve4) => setTimeout(resolve4, 200));
3996
+ await new Promise((resolve6) => setTimeout(resolve6, 200));
3979
3997
  }
3980
3998
  killed.push(pid);
3981
3999
  }
@@ -4009,7 +4027,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4009
4027
  if (!this.tunnelStates.has(moduleUid)) {
4010
4028
  console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`);
4011
4029
  this.killByPid(pid, "SIGTERM");
4012
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
4030
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
4013
4031
  if (this.isProcessRunning(pid)) {
4014
4032
  this.killByPid(pid, "SIGKILL");
4015
4033
  }
@@ -4024,7 +4042,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4024
4042
  if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {
4025
4043
  console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`);
4026
4044
  this.killByPid(pid, "SIGTERM");
4027
- await new Promise((resolve4) => setTimeout(resolve4, 500));
4045
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
4028
4046
  if (this.isProcessRunning(pid)) {
4029
4047
  this.killByPid(pid, "SIGKILL");
4030
4048
  }
@@ -4114,7 +4132,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4114
4132
  return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
4115
4133
  }
4116
4134
  }
4117
- return new Promise((resolve4) => {
4135
+ return new Promise((resolve6) => {
4118
4136
  const tunnelInfo = {
4119
4137
  moduleUid,
4120
4138
  url: previewUrl || "",
@@ -4180,7 +4198,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4180
4198
  moduleUid,
4181
4199
  url: tunnelInfo.url
4182
4200
  });
4183
- resolve4({ success: true, url: tunnelInfo.url });
4201
+ resolve6({ success: true, url: tunnelInfo.url });
4184
4202
  }
4185
4203
  };
4186
4204
  process2.stderr?.on("data", (data) => {
@@ -4207,7 +4225,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4207
4225
  onStatusChange?.("error", errorMsg);
4208
4226
  this.emitEvent({ type: "error", moduleUid, error: errorMsg });
4209
4227
  }
4210
- resolve4({ success: false, error: errorMsg });
4228
+ resolve6({ success: false, error: errorMsg });
4211
4229
  } else if (wasConnected) {
4212
4230
  if (currentState && !currentState.intentionallyStopped) {
4213
4231
  console.log(`[Tunnel] EP948: Named tunnel ${moduleUid} crashed unexpectedly, attempting reconnect...`);
@@ -4238,7 +4256,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4238
4256
  this.emitEvent({ type: "error", moduleUid, error: error.message });
4239
4257
  }
4240
4258
  if (!connected) {
4241
- resolve4({ success: false, error: error.message });
4259
+ resolve6({ success: false, error: error.message });
4242
4260
  }
4243
4261
  });
4244
4262
  setTimeout(() => {
@@ -4261,7 +4279,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4261
4279
  onStatusChange?.("error", errorMsg);
4262
4280
  this.emitEvent({ type: "error", moduleUid, error: errorMsg });
4263
4281
  }
4264
- resolve4({ success: false, error: errorMsg });
4282
+ resolve6({ success: false, error: errorMsg });
4265
4283
  }
4266
4284
  }, TUNNEL_TIMEOUTS.NAMED_TUNNEL_CONNECT);
4267
4285
  });
@@ -4322,7 +4340,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4322
4340
  if (orphanPid && this.isProcessRunning(orphanPid)) {
4323
4341
  console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`);
4324
4342
  this.killByPid(orphanPid, "SIGTERM");
4325
- await new Promise((resolve4) => setTimeout(resolve4, 500));
4343
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
4326
4344
  if (this.isProcessRunning(orphanPid)) {
4327
4345
  this.killByPid(orphanPid, "SIGKILL");
4328
4346
  }
@@ -4331,7 +4349,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4331
4349
  const killedOnPort = await this.killCloudflaredOnPort(port);
4332
4350
  if (killedOnPort.length > 0) {
4333
4351
  console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`);
4334
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
4352
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
4335
4353
  }
4336
4354
  const cleanup = await this.cleanupOrphanedProcesses();
4337
4355
  if (cleanup.cleaned > 0) {
@@ -4371,7 +4389,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4371
4389
  if (orphanPid && this.isProcessRunning(orphanPid)) {
4372
4390
  console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`);
4373
4391
  this.killByPid(orphanPid, "SIGTERM");
4374
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
4392
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
4375
4393
  if (this.isProcessRunning(orphanPid)) {
4376
4394
  this.killByPid(orphanPid, "SIGKILL");
4377
4395
  }
@@ -4387,16 +4405,16 @@ var TunnelManager = class extends import_events.EventEmitter {
4387
4405
  const tunnel = state.info;
4388
4406
  if (tunnel.process && !tunnel.process.killed) {
4389
4407
  tunnel.process.kill("SIGTERM");
4390
- await new Promise((resolve4) => {
4408
+ await new Promise((resolve6) => {
4391
4409
  const timeout = setTimeout(() => {
4392
4410
  if (tunnel.process && !tunnel.process.killed) {
4393
4411
  tunnel.process.kill("SIGKILL");
4394
4412
  }
4395
- resolve4();
4413
+ resolve6();
4396
4414
  }, 3e3);
4397
4415
  tunnel.process.once("exit", () => {
4398
4416
  clearTimeout(timeout);
4399
- resolve4();
4417
+ resolve6();
4400
4418
  });
4401
4419
  });
4402
4420
  }
@@ -4683,15 +4701,15 @@ async function writeToStdinWithBackpressure(process2, data, drainTimeoutMs, labe
4683
4701
  if (!stdin || stdin.destroyed) {
4684
4702
  throw new Error(`[${label}] stdin not available. session=${sessionId}`);
4685
4703
  }
4686
- await new Promise((resolve4, reject) => {
4704
+ await new Promise((resolve6, reject) => {
4687
4705
  const ok = stdin.write(data);
4688
4706
  if (ok) {
4689
- resolve4();
4707
+ resolve6();
4690
4708
  return;
4691
4709
  }
4692
4710
  const onDrain = () => {
4693
4711
  cleanup();
4694
- resolve4();
4712
+ resolve6();
4695
4713
  };
4696
4714
  const onError = (err) => {
4697
4715
  cleanup();
@@ -4712,18 +4730,18 @@ async function writeToStdinWithBackpressure(process2, data, drainTimeoutMs, labe
4712
4730
  });
4713
4731
  }
4714
4732
  function waitForProcessExit(process2, alive, timeoutMs) {
4715
- return new Promise((resolve4) => {
4733
+ return new Promise((resolve6) => {
4716
4734
  if (!process2 || !alive) {
4717
- resolve4(true);
4735
+ resolve6(true);
4718
4736
  return;
4719
4737
  }
4720
4738
  const onExit = () => {
4721
4739
  clearTimeout(timer);
4722
- resolve4(true);
4740
+ resolve6(true);
4723
4741
  };
4724
4742
  const timer = setTimeout(() => {
4725
4743
  process2.removeListener("exit", onExit);
4726
- resolve4(false);
4744
+ resolve6(false);
4727
4745
  }, timeoutMs);
4728
4746
  process2.once("exit", onExit);
4729
4747
  });
@@ -5719,10 +5737,10 @@ var CodexPersistentRuntime = class {
5719
5737
  }
5720
5738
  waitForThreadId(timeoutMs) {
5721
5739
  if (this._agentSessionId) return Promise.resolve(this._agentSessionId);
5722
- return new Promise((resolve4, reject) => {
5740
+ return new Promise((resolve6, reject) => {
5723
5741
  const onId = (id) => {
5724
5742
  clearTimeout(timer);
5725
- resolve4(id);
5743
+ resolve6(id);
5726
5744
  };
5727
5745
  const timer = setTimeout(() => {
5728
5746
  this.threadIdWaiters = this.threadIdWaiters.filter((w) => w !== onId);
@@ -5753,12 +5771,12 @@ var CodexPersistentRuntime = class {
5753
5771
  sendRequest(method, params, timeoutMs = INIT_TIMEOUT_MS) {
5754
5772
  const id = this.nextId++;
5755
5773
  const msg = { jsonrpc: "2.0", id, method, params };
5756
- return new Promise(async (resolve4, reject) => {
5774
+ return new Promise(async (resolve6, reject) => {
5757
5775
  const timeout = setTimeout(() => {
5758
5776
  this.pending.delete(id);
5759
5777
  reject(new Error(`JSON-RPC timeout: ${method}`));
5760
5778
  }, timeoutMs);
5761
- this.pending.set(id, { resolve: resolve4, reject, timeout });
5779
+ this.pending.set(id, { resolve: resolve6, reject, timeout });
5762
5780
  try {
5763
5781
  await this.writeToStdin(JSON.stringify(msg) + "\n");
5764
5782
  } catch (err) {
@@ -5908,11 +5926,11 @@ var UnifiedAgentRuntime = class {
5908
5926
  if (!this.currentTurn && this.impl.turnState === "idle") {
5909
5927
  return this.dispatchTurn(message, callbacks);
5910
5928
  }
5911
- return new Promise((resolve4, reject) => {
5929
+ return new Promise((resolve6, reject) => {
5912
5930
  this.queuedTurns.push({
5913
5931
  message,
5914
5932
  callbacks,
5915
- resolve: resolve4,
5933
+ resolve: resolve6,
5916
5934
  reject
5917
5935
  });
5918
5936
  });
@@ -7425,13 +7443,13 @@ async function getChildProcessRssMb(pid) {
7425
7443
  if (!pid || pid <= 0) return null;
7426
7444
  try {
7427
7445
  if (typeof import_child_process9.execFile !== "function") return null;
7428
- const stdout = await new Promise((resolve4, reject) => {
7446
+ const stdout = await new Promise((resolve6, reject) => {
7429
7447
  (0, import_child_process9.execFile)("ps", ["-o", "rss=", "-p", String(pid)], { timeout: 1e3 }, (err, out) => {
7430
7448
  if (err) {
7431
7449
  reject(err);
7432
7450
  return;
7433
7451
  }
7434
- resolve4(String(out));
7452
+ resolve6(String(out));
7435
7453
  });
7436
7454
  });
7437
7455
  const kb = Number(String(stdout).trim());
@@ -7534,8 +7552,8 @@ var AgentControlPlane = class {
7534
7552
  async withConfigLock(fn) {
7535
7553
  const previousLock = this.configWriteLock;
7536
7554
  let releaseLock;
7537
- this.configWriteLock = new Promise((resolve4) => {
7538
- releaseLock = resolve4;
7555
+ this.configWriteLock = new Promise((resolve6) => {
7556
+ releaseLock = resolve6;
7539
7557
  });
7540
7558
  try {
7541
7559
  await previousLock;
@@ -8778,7 +8796,7 @@ var WorktreeManager = class _WorktreeManager {
8778
8796
  const lockContent = fs14.readFileSync(lockPath, "utf-8").trim();
8779
8797
  const lockPid = parseInt(lockContent, 10);
8780
8798
  if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
8781
- await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
8799
+ await new Promise((resolve6) => setTimeout(resolve6, retryInterval));
8782
8800
  continue;
8783
8801
  }
8784
8802
  } catch {
@@ -8792,7 +8810,7 @@ var WorktreeManager = class _WorktreeManager {
8792
8810
  } catch {
8793
8811
  continue;
8794
8812
  }
8795
- await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
8813
+ await new Promise((resolve6) => setTimeout(resolve6, retryInterval));
8796
8814
  continue;
8797
8815
  }
8798
8816
  throw err;
@@ -9355,62 +9373,190 @@ function reconcileWithRegistry(activeModules) {
9355
9373
  }
9356
9374
  }
9357
9375
 
9358
- // src/framework-detector.ts
9376
+ // src/utils/ws-port-allocator.ts
9359
9377
  var fs19 = __toESM(require("fs"));
9360
9378
  var path19 = __toESM(require("path"));
9379
+ var os10 = __toESM(require("os"));
9380
+ var WS_PORT_RANGE_START = 3200;
9381
+ var WS_PORT_RANGE_END = 3299;
9382
+ var WS_PORT_WARNING_THRESHOLD = 80;
9383
+ var WS_PORTS_FILE = path19.join(os10.homedir(), ".episoda", "ws-ports.json");
9384
+ var wsPortAssignments = /* @__PURE__ */ new Map();
9385
+ var initialized2 = false;
9386
+ function loadFromDisk2() {
9387
+ if (initialized2) return;
9388
+ initialized2 = true;
9389
+ try {
9390
+ if (!fs19.existsSync(WS_PORTS_FILE)) return;
9391
+ const content = fs19.readFileSync(WS_PORTS_FILE, "utf8");
9392
+ const data = JSON.parse(content);
9393
+ for (const [moduleUid, port] of Object.entries(data)) {
9394
+ if (typeof port === "number" && port >= WS_PORT_RANGE_START && port <= WS_PORT_RANGE_END) {
9395
+ wsPortAssignments.set(moduleUid, port);
9396
+ }
9397
+ }
9398
+ if (wsPortAssignments.size > 0) {
9399
+ console.log(`[WsPortAllocator] EP1406: Loaded ${wsPortAssignments.size} WS port assignments from disk`);
9400
+ }
9401
+ } catch (error) {
9402
+ console.warn("[WsPortAllocator] EP1406: Failed to load ws-ports.json:", error);
9403
+ }
9404
+ }
9405
+ function saveToDisk2() {
9406
+ try {
9407
+ const dir = path19.dirname(WS_PORTS_FILE);
9408
+ if (!fs19.existsSync(dir)) {
9409
+ fs19.mkdirSync(dir, { recursive: true });
9410
+ }
9411
+ const data = {};
9412
+ for (const [moduleUid, port] of wsPortAssignments) {
9413
+ data[moduleUid] = port;
9414
+ }
9415
+ fs19.writeFileSync(WS_PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
9416
+ } catch (error) {
9417
+ console.warn("[WsPortAllocator] EP1406: Failed to save ws-ports.json:", error);
9418
+ }
9419
+ }
9420
+ function allocateWsPort(moduleUid) {
9421
+ loadFromDisk2();
9422
+ const existing = wsPortAssignments.get(moduleUid);
9423
+ if (existing) {
9424
+ console.log(`[WsPortAllocator] EP1406: Returning existing WS port ${existing} for ${moduleUid}`);
9425
+ return existing;
9426
+ }
9427
+ const usedPorts = new Set(wsPortAssignments.values());
9428
+ if (usedPorts.size >= WS_PORT_WARNING_THRESHOLD) {
9429
+ console.warn(
9430
+ `[WsPortAllocator] EP1406: Warning ${usedPorts.size}/${WS_PORT_RANGE_END - WS_PORT_RANGE_START + 1} WS ports allocated`
9431
+ );
9432
+ }
9433
+ for (let port = WS_PORT_RANGE_START; port <= WS_PORT_RANGE_END; port++) {
9434
+ if (!usedPorts.has(port)) {
9435
+ wsPortAssignments.set(moduleUid, port);
9436
+ saveToDisk2();
9437
+ console.log(`[WsPortAllocator] EP1406: Allocated WS port ${port} to ${moduleUid}`);
9438
+ return port;
9439
+ }
9440
+ }
9441
+ throw new Error(
9442
+ `No available WS ports in range ${WS_PORT_RANGE_START}-${WS_PORT_RANGE_END}. ${wsPortAssignments.size} modules are using all available WS ports.`
9443
+ );
9444
+ }
9445
+ function assignWsPort(moduleUid, wsPort) {
9446
+ loadFromDisk2();
9447
+ if (!Number.isInteger(wsPort) || wsPort < WS_PORT_RANGE_START || wsPort > WS_PORT_RANGE_END) {
9448
+ throw new Error(
9449
+ `WS port ${wsPort} is outside allowed range ${WS_PORT_RANGE_START}-${WS_PORT_RANGE_END}`
9450
+ );
9451
+ }
9452
+ for (const [ownerModuleUid, ownerPort] of wsPortAssignments.entries()) {
9453
+ if (ownerPort === wsPort && ownerModuleUid !== moduleUid) {
9454
+ throw new Error(`WS port ${wsPort} is already assigned to ${ownerModuleUid}`);
9455
+ }
9456
+ }
9457
+ const existing = wsPortAssignments.get(moduleUid);
9458
+ if (existing === wsPort) {
9459
+ return wsPort;
9460
+ }
9461
+ wsPortAssignments.set(moduleUid, wsPort);
9462
+ saveToDisk2();
9463
+ console.log(`[WsPortAllocator] EP1406: Assigned WS port ${wsPort} to ${moduleUid}`);
9464
+ return wsPort;
9465
+ }
9466
+ function releaseWsPort(moduleUid) {
9467
+ loadFromDisk2();
9468
+ const port = wsPortAssignments.get(moduleUid);
9469
+ if (!port) return;
9470
+ wsPortAssignments.delete(moduleUid);
9471
+ saveToDisk2();
9472
+ console.log(`[WsPortAllocator] EP1406: Released WS port ${port} from ${moduleUid}`);
9473
+ }
9474
+ function reconcileWsPortsWithRegistry(activeModules) {
9475
+ loadFromDisk2();
9476
+ let changed = false;
9477
+ for (const [moduleUid, port] of wsPortAssignments) {
9478
+ if (!activeModules.has(moduleUid)) {
9479
+ console.log(`[WsPortAllocator] EP1406: Removing stale WS allocation ${moduleUid} -> ${port}`);
9480
+ wsPortAssignments.delete(moduleUid);
9481
+ changed = true;
9482
+ }
9483
+ }
9484
+ for (const [moduleUid, port] of activeModules) {
9485
+ if (!wsPortAssignments.has(moduleUid)) {
9486
+ console.log(`[WsPortAllocator] EP1406: Adding WS allocation from registry ${moduleUid} -> ${port}`);
9487
+ wsPortAssignments.set(moduleUid, port);
9488
+ changed = true;
9489
+ }
9490
+ }
9491
+ if (changed) {
9492
+ saveToDisk2();
9493
+ }
9494
+ }
9495
+ function clearAllWsPorts() {
9496
+ const count = wsPortAssignments.size;
9497
+ wsPortAssignments.clear();
9498
+ saveToDisk2();
9499
+ if (count > 0) {
9500
+ console.log(`[WsPortAllocator] EP1406: Cleared ${count} WS port assignments`);
9501
+ }
9502
+ }
9503
+
9504
+ // src/framework-detector.ts
9505
+ var fs20 = __toESM(require("fs"));
9506
+ var path20 = __toESM(require("path"));
9361
9507
  function getInstallCommand(cwd) {
9362
- if (fs19.existsSync(path19.join(cwd, "bun.lockb"))) {
9508
+ if (fs20.existsSync(path20.join(cwd, "bun.lockb"))) {
9363
9509
  return {
9364
9510
  command: ["bun", "install"],
9365
9511
  description: "Installing dependencies with bun",
9366
9512
  detectedFrom: "bun.lockb"
9367
9513
  };
9368
9514
  }
9369
- if (fs19.existsSync(path19.join(cwd, "pnpm-lock.yaml"))) {
9515
+ if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
9370
9516
  return {
9371
9517
  command: ["pnpm", "install"],
9372
9518
  description: "Installing dependencies with pnpm",
9373
9519
  detectedFrom: "pnpm-lock.yaml"
9374
9520
  };
9375
9521
  }
9376
- if (fs19.existsSync(path19.join(cwd, "yarn.lock"))) {
9522
+ if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
9377
9523
  return {
9378
9524
  command: ["yarn", "install"],
9379
9525
  description: "Installing dependencies with yarn",
9380
9526
  detectedFrom: "yarn.lock"
9381
9527
  };
9382
9528
  }
9383
- if (fs19.existsSync(path19.join(cwd, "package-lock.json"))) {
9529
+ if (fs20.existsSync(path20.join(cwd, "package-lock.json"))) {
9384
9530
  return {
9385
9531
  command: ["npm", "ci"],
9386
9532
  description: "Installing dependencies with npm ci",
9387
9533
  detectedFrom: "package-lock.json"
9388
9534
  };
9389
9535
  }
9390
- if (fs19.existsSync(path19.join(cwd, "package.json"))) {
9536
+ if (fs20.existsSync(path20.join(cwd, "package.json"))) {
9391
9537
  return {
9392
9538
  command: ["npm", "install"],
9393
9539
  description: "Installing dependencies with npm",
9394
9540
  detectedFrom: "package.json"
9395
9541
  };
9396
9542
  }
9397
- if (fs19.existsSync(path19.join(cwd, "Pipfile.lock")) || fs19.existsSync(path19.join(cwd, "Pipfile"))) {
9543
+ if (fs20.existsSync(path20.join(cwd, "Pipfile.lock")) || fs20.existsSync(path20.join(cwd, "Pipfile"))) {
9398
9544
  return {
9399
9545
  command: ["pipenv", "install"],
9400
9546
  description: "Installing dependencies with pipenv",
9401
- detectedFrom: fs19.existsSync(path19.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9547
+ detectedFrom: fs20.existsSync(path20.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9402
9548
  };
9403
9549
  }
9404
- if (fs19.existsSync(path19.join(cwd, "poetry.lock"))) {
9550
+ if (fs20.existsSync(path20.join(cwd, "poetry.lock"))) {
9405
9551
  return {
9406
9552
  command: ["poetry", "install"],
9407
9553
  description: "Installing dependencies with poetry",
9408
9554
  detectedFrom: "poetry.lock"
9409
9555
  };
9410
9556
  }
9411
- if (fs19.existsSync(path19.join(cwd, "pyproject.toml"))) {
9412
- const pyprojectPath = path19.join(cwd, "pyproject.toml");
9413
- const content = fs19.readFileSync(pyprojectPath, "utf-8");
9557
+ if (fs20.existsSync(path20.join(cwd, "pyproject.toml"))) {
9558
+ const pyprojectPath = path20.join(cwd, "pyproject.toml");
9559
+ const content = fs20.readFileSync(pyprojectPath, "utf-8");
9414
9560
  if (content.includes("[tool.poetry]")) {
9415
9561
  return {
9416
9562
  command: ["poetry", "install"],
@@ -9419,68 +9565,68 @@ function getInstallCommand(cwd) {
9419
9565
  };
9420
9566
  }
9421
9567
  }
9422
- if (fs19.existsSync(path19.join(cwd, "requirements.txt"))) {
9568
+ if (fs20.existsSync(path20.join(cwd, "requirements.txt"))) {
9423
9569
  return {
9424
9570
  command: ["pip", "install", "-r", "requirements.txt"],
9425
9571
  description: "Installing dependencies with pip",
9426
9572
  detectedFrom: "requirements.txt"
9427
9573
  };
9428
9574
  }
9429
- if (fs19.existsSync(path19.join(cwd, "Gemfile.lock")) || fs19.existsSync(path19.join(cwd, "Gemfile"))) {
9575
+ if (fs20.existsSync(path20.join(cwd, "Gemfile.lock")) || fs20.existsSync(path20.join(cwd, "Gemfile"))) {
9430
9576
  return {
9431
9577
  command: ["bundle", "install"],
9432
9578
  description: "Installing dependencies with bundler",
9433
- detectedFrom: fs19.existsSync(path19.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9579
+ detectedFrom: fs20.existsSync(path20.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9434
9580
  };
9435
9581
  }
9436
- if (fs19.existsSync(path19.join(cwd, "go.sum")) || fs19.existsSync(path19.join(cwd, "go.mod"))) {
9582
+ if (fs20.existsSync(path20.join(cwd, "go.sum")) || fs20.existsSync(path20.join(cwd, "go.mod"))) {
9437
9583
  return {
9438
9584
  command: ["go", "mod", "download"],
9439
9585
  description: "Downloading Go modules",
9440
- detectedFrom: fs19.existsSync(path19.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9586
+ detectedFrom: fs20.existsSync(path20.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9441
9587
  };
9442
9588
  }
9443
- if (fs19.existsSync(path19.join(cwd, "Cargo.lock")) || fs19.existsSync(path19.join(cwd, "Cargo.toml"))) {
9589
+ if (fs20.existsSync(path20.join(cwd, "Cargo.lock")) || fs20.existsSync(path20.join(cwd, "Cargo.toml"))) {
9444
9590
  return {
9445
9591
  command: ["cargo", "build"],
9446
9592
  description: "Building Rust project (downloads dependencies)",
9447
- detectedFrom: fs19.existsSync(path19.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9593
+ detectedFrom: fs20.existsSync(path20.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9448
9594
  };
9449
9595
  }
9450
9596
  return null;
9451
9597
  }
9452
9598
 
9453
9599
  // src/daemon/daemon-process.ts
9454
- var fs33 = __toESM(require("fs"));
9600
+ var fs34 = __toESM(require("fs"));
9455
9601
  var http2 = __toESM(require("http"));
9456
- var os14 = __toESM(require("os"));
9602
+ var os15 = __toESM(require("os"));
9457
9603
 
9458
9604
  // src/daemon/ipc-router.ts
9459
- var os13 = __toESM(require("os"));
9605
+ var os14 = __toESM(require("os"));
9460
9606
  var import_core15 = __toESM(require_dist());
9461
9607
 
9462
9608
  // src/daemon/handlers/file-handlers.ts
9463
- var fs21 = __toESM(require("fs"));
9464
- var path21 = __toESM(require("path"));
9609
+ var fs22 = __toESM(require("fs"));
9610
+ var path22 = __toESM(require("path"));
9465
9611
  var readline = __toESM(require("readline"));
9466
9612
 
9467
9613
  // src/daemon/permissions/path-classifier.ts
9468
- var path20 = __toESM(require("path"));
9469
- var fs20 = __toESM(require("fs"));
9614
+ var path21 = __toESM(require("path"));
9615
+ var fs21 = __toESM(require("fs"));
9470
9616
  function classifyPath(targetPath, options) {
9471
9617
  const { workspaceRoot, projectRoot } = options;
9472
- const normalizedWorkspace = path20.resolve(workspaceRoot);
9473
- const normalizedProject = path20.resolve(projectRoot);
9474
- const resolvedPath = path20.isAbsolute(targetPath) ? path20.resolve(targetPath) : path20.resolve(projectRoot, targetPath);
9475
- const artifactsDir = path20.join(normalizedWorkspace, "artifacts");
9476
- if (resolvedPath.startsWith(artifactsDir + path20.sep) || resolvedPath === artifactsDir) {
9618
+ const normalizedWorkspace = path21.resolve(workspaceRoot);
9619
+ const normalizedProject = path21.resolve(projectRoot);
9620
+ const resolvedPath = path21.isAbsolute(targetPath) ? path21.resolve(targetPath) : path21.resolve(projectRoot, targetPath);
9621
+ const artifactsDir = path21.join(normalizedWorkspace, "artifacts");
9622
+ if (resolvedPath.startsWith(artifactsDir + path21.sep) || resolvedPath === artifactsDir) {
9477
9623
  return {
9478
9624
  classification: "artifacts",
9479
9625
  resolvedPath
9480
9626
  };
9481
9627
  }
9482
- if (!resolvedPath.startsWith(normalizedProject + path20.sep) && resolvedPath !== normalizedProject) {
9483
- if (resolvedPath.startsWith(normalizedWorkspace + path20.sep)) {
9628
+ if (!resolvedPath.startsWith(normalizedProject + path21.sep) && resolvedPath !== normalizedProject) {
9629
+ if (resolvedPath.startsWith(normalizedWorkspace + path21.sep)) {
9484
9630
  return {
9485
9631
  classification: "unknown",
9486
9632
  resolvedPath
@@ -9491,8 +9637,8 @@ function classifyPath(targetPath, options) {
9491
9637
  resolvedPath
9492
9638
  };
9493
9639
  }
9494
- const relativePath = path20.relative(normalizedProject, resolvedPath);
9495
- const pathParts = relativePath.split(path20.sep);
9640
+ const relativePath = path21.relative(normalizedProject, resolvedPath);
9641
+ const pathParts = relativePath.split(path21.sep);
9496
9642
  if (pathParts[0] === ".bare") {
9497
9643
  return {
9498
9644
  classification: "bare_repo",
@@ -9507,9 +9653,9 @@ function classifyPath(targetPath, options) {
9507
9653
  }
9508
9654
  const firstPart = pathParts[0];
9509
9655
  if (firstPart && isModuleUidPattern(firstPart)) {
9510
- const worktreeDir = path20.join(normalizedProject, firstPart);
9511
- const gitFile = path20.join(worktreeDir, ".git");
9512
- const worktreeExists = fs20.existsSync(gitFile) && fs20.statSync(gitFile).isFile();
9656
+ const worktreeDir = path21.join(normalizedProject, firstPart);
9657
+ const gitFile = path21.join(worktreeDir, ".git");
9658
+ const worktreeExists = fs21.existsSync(gitFile) && fs21.statSync(gitFile).isFile();
9513
9659
  return {
9514
9660
  classification: "worktree",
9515
9661
  moduleUid: firstPart,
@@ -9526,19 +9672,19 @@ function isModuleUidPattern(str) {
9526
9672
  return /^EP\d+$/.test(str);
9527
9673
  }
9528
9674
  function deriveWorkspaceRoot(projectPath) {
9529
- return path20.dirname(path20.resolve(projectPath));
9675
+ return path21.dirname(path21.resolve(projectPath));
9530
9676
  }
9531
9677
  function validateWorkspacePath(filePath, projectPath) {
9532
- const normalizedProjectPath = path20.resolve(projectPath);
9678
+ const normalizedProjectPath = path21.resolve(projectPath);
9533
9679
  const workspaceRoot = deriveWorkspaceRoot(projectPath);
9534
- const normalizedWorkspace = path20.resolve(workspaceRoot);
9535
- const artifactsDir = path20.join(normalizedWorkspace, "artifacts");
9536
- const absolutePath = path20.isAbsolute(filePath) ? path20.resolve(filePath) : path20.resolve(projectPath, filePath);
9537
- const normalizedPath = path20.normalize(absolutePath);
9538
- if (normalizedPath.startsWith(normalizedProjectPath + path20.sep) || normalizedPath === normalizedProjectPath) {
9680
+ const normalizedWorkspace = path21.resolve(workspaceRoot);
9681
+ const artifactsDir = path21.join(normalizedWorkspace, "artifacts");
9682
+ const absolutePath = path21.isAbsolute(filePath) ? path21.resolve(filePath) : path21.resolve(projectPath, filePath);
9683
+ const normalizedPath = path21.normalize(absolutePath);
9684
+ if (normalizedPath.startsWith(normalizedProjectPath + path21.sep) || normalizedPath === normalizedProjectPath) {
9539
9685
  return normalizedPath;
9540
9686
  }
9541
- if (normalizedPath.startsWith(artifactsDir + path20.sep) || normalizedPath === artifactsDir) {
9687
+ if (normalizedPath.startsWith(artifactsDir + path21.sep) || normalizedPath === artifactsDir) {
9542
9688
  return normalizedPath;
9543
9689
  }
9544
9690
  return null;
@@ -9636,13 +9782,13 @@ async function handleFileRead(command, projectPath) {
9636
9782
  };
9637
9783
  }
9638
9784
  try {
9639
- if (!fs21.existsSync(validPath)) {
9785
+ if (!fs22.existsSync(validPath)) {
9640
9786
  return {
9641
9787
  success: false,
9642
9788
  error: "File not found"
9643
9789
  };
9644
9790
  }
9645
- const stats = fs21.statSync(validPath);
9791
+ const stats = fs22.statSync(validPath);
9646
9792
  if (stats.isDirectory()) {
9647
9793
  return {
9648
9794
  success: false,
@@ -9655,7 +9801,7 @@ async function handleFileRead(command, projectPath) {
9655
9801
  error: `File too large: ${stats.size} bytes exceeds limit of ${maxSize} bytes`
9656
9802
  };
9657
9803
  }
9658
- const buffer = fs21.readFileSync(validPath);
9804
+ const buffer = fs22.readFileSync(validPath);
9659
9805
  let content;
9660
9806
  if (encoding === "base64") {
9661
9807
  content = buffer.toString("base64");
@@ -9695,9 +9841,9 @@ async function handleFileWrite(command, projectPath) {
9695
9841
  }
9696
9842
  try {
9697
9843
  if (createDirs) {
9698
- const dirPath = path21.dirname(validPath);
9699
- if (!fs21.existsSync(dirPath)) {
9700
- fs21.mkdirSync(dirPath, { recursive: true });
9844
+ const dirPath = path22.dirname(validPath);
9845
+ if (!fs22.existsSync(dirPath)) {
9846
+ fs22.mkdirSync(dirPath, { recursive: true });
9701
9847
  }
9702
9848
  }
9703
9849
  let buffer;
@@ -9707,8 +9853,8 @@ async function handleFileWrite(command, projectPath) {
9707
9853
  buffer = Buffer.from(content, "utf8");
9708
9854
  }
9709
9855
  const tempPath = `${validPath}.tmp.${Date.now()}`;
9710
- fs21.writeFileSync(tempPath, buffer);
9711
- fs21.renameSync(tempPath, validPath);
9856
+ fs22.writeFileSync(tempPath, buffer);
9857
+ fs22.renameSync(tempPath, validPath);
9712
9858
  return {
9713
9859
  success: true,
9714
9860
  bytesWritten: buffer.length
@@ -9733,13 +9879,13 @@ async function handleFileList(command, projectPath) {
9733
9879
  };
9734
9880
  }
9735
9881
  try {
9736
- if (!fs21.existsSync(validPath)) {
9882
+ if (!fs22.existsSync(validPath)) {
9737
9883
  return {
9738
9884
  success: false,
9739
9885
  error: "Directory not found"
9740
9886
  };
9741
9887
  }
9742
- const stats = fs21.statSync(validPath);
9888
+ const stats = fs22.statSync(validPath);
9743
9889
  if (!stats.isDirectory()) {
9744
9890
  return {
9745
9891
  success: false,
@@ -9750,11 +9896,11 @@ async function handleFileList(command, projectPath) {
9750
9896
  if (recursive) {
9751
9897
  await listDirectoryRecursive(validPath, validPath, entries, includeHidden);
9752
9898
  } else {
9753
- const dirEntries = await fs21.promises.readdir(validPath, { withFileTypes: true });
9899
+ const dirEntries = await fs22.promises.readdir(validPath, { withFileTypes: true });
9754
9900
  for (const entry of dirEntries) {
9755
9901
  if (!includeHidden && entry.name.startsWith(".")) continue;
9756
- const entryPath = path21.join(validPath, entry.name);
9757
- const entryStats = await fs21.promises.stat(entryPath);
9902
+ const entryPath = path22.join(validPath, entry.name);
9903
+ const entryStats = await fs22.promises.stat(entryPath);
9758
9904
  entries.push({
9759
9905
  name: entry.name,
9760
9906
  type: entry.isDirectory() ? "directory" : "file",
@@ -9776,13 +9922,13 @@ async function handleFileList(command, projectPath) {
9776
9922
  }
9777
9923
  }
9778
9924
  async function listDirectoryRecursive(basePath, currentPath, entries, includeHidden) {
9779
- const dirEntries = await fs21.promises.readdir(currentPath, { withFileTypes: true });
9925
+ const dirEntries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
9780
9926
  for (const entry of dirEntries) {
9781
9927
  if (!includeHidden && entry.name.startsWith(".")) continue;
9782
- const entryPath = path21.join(currentPath, entry.name);
9783
- const relativePath = path21.relative(basePath, entryPath);
9928
+ const entryPath = path22.join(currentPath, entry.name);
9929
+ const relativePath = path22.relative(basePath, entryPath);
9784
9930
  try {
9785
- const entryStats = await fs21.promises.stat(entryPath);
9931
+ const entryStats = await fs22.promises.stat(entryPath);
9786
9932
  entries.push({
9787
9933
  name: relativePath,
9788
9934
  type: entry.isDirectory() ? "directory" : "file",
@@ -9806,7 +9952,7 @@ async function handleFileSearch(command, projectPath) {
9806
9952
  };
9807
9953
  }
9808
9954
  try {
9809
- if (!fs21.existsSync(validPath)) {
9955
+ if (!fs22.existsSync(validPath)) {
9810
9956
  return {
9811
9957
  success: false,
9812
9958
  error: "Directory not found"
@@ -9896,12 +10042,12 @@ function findClosingBracket(pattern, start) {
9896
10042
  }
9897
10043
  async function searchFilesRecursive(basePath, currentPath, pattern, files, maxResults) {
9898
10044
  if (files.length >= maxResults) return;
9899
- const entries = await fs21.promises.readdir(currentPath, { withFileTypes: true });
10045
+ const entries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
9900
10046
  for (const entry of entries) {
9901
10047
  if (files.length >= maxResults) return;
9902
10048
  if (entry.name.startsWith(".")) continue;
9903
- const entryPath = path21.join(currentPath, entry.name);
9904
- const relativePath = path21.relative(basePath, entryPath);
10049
+ const entryPath = path22.join(currentPath, entry.name);
10050
+ const relativePath = path22.relative(basePath, entryPath);
9905
10051
  try {
9906
10052
  if (entry.isDirectory()) {
9907
10053
  await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults);
@@ -9929,7 +10075,7 @@ async function handleFileGrep(command, projectPath) {
9929
10075
  };
9930
10076
  }
9931
10077
  try {
9932
- if (!fs21.existsSync(validPath)) {
10078
+ if (!fs22.existsSync(validPath)) {
9933
10079
  return {
9934
10080
  success: false,
9935
10081
  error: "Path not found"
@@ -9938,7 +10084,7 @@ async function handleFileGrep(command, projectPath) {
9938
10084
  const matches = [];
9939
10085
  const searchRegex = new RegExp(pattern, caseSensitive ? "" : "i");
9940
10086
  const fileGlobRegex = globToRegex(filePattern);
9941
- const stats = fs21.statSync(validPath);
10087
+ const stats = fs22.statSync(validPath);
9942
10088
  if (stats.isFile()) {
9943
10089
  await grepFile(validPath, validPath, searchRegex, matches, effectiveMaxResults);
9944
10090
  } else {
@@ -9959,8 +10105,8 @@ async function handleFileGrep(command, projectPath) {
9959
10105
  }
9960
10106
  async function grepFile(basePath, filePath, pattern, matches, maxResults) {
9961
10107
  if (matches.length >= maxResults) return;
9962
- const relativePath = path21.relative(basePath, filePath);
9963
- const fileStream = fs21.createReadStream(filePath, { encoding: "utf8" });
10108
+ const relativePath = path22.relative(basePath, filePath);
10109
+ const fileStream = fs22.createReadStream(filePath, { encoding: "utf8" });
9964
10110
  const rl = readline.createInterface({
9965
10111
  input: fileStream,
9966
10112
  crlfDelay: Infinity
@@ -9974,7 +10120,7 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
9974
10120
  }
9975
10121
  if (pattern.test(line)) {
9976
10122
  matches.push({
9977
- file: relativePath || path21.basename(filePath),
10123
+ file: relativePath || path22.basename(filePath),
9978
10124
  line: lineNumber,
9979
10125
  content: line.slice(0, 500)
9980
10126
  // Truncate long lines
@@ -9984,11 +10130,11 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
9984
10130
  }
9985
10131
  async function grepDirectoryRecursive(basePath, currentPath, searchPattern, filePattern, matches, maxResults) {
9986
10132
  if (matches.length >= maxResults) return;
9987
- const entries = await fs21.promises.readdir(currentPath, { withFileTypes: true });
10133
+ const entries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
9988
10134
  for (const entry of entries) {
9989
10135
  if (matches.length >= maxResults) return;
9990
10136
  if (entry.name.startsWith(".")) continue;
9991
- const entryPath = path21.join(currentPath, entry.name);
10137
+ const entryPath = path22.join(currentPath, entry.name);
9992
10138
  try {
9993
10139
  if (entry.isDirectory()) {
9994
10140
  await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults);
@@ -10016,13 +10162,13 @@ async function handleFileEdit(command, projectPath) {
10016
10162
  };
10017
10163
  }
10018
10164
  try {
10019
- if (!fs21.existsSync(validPath)) {
10165
+ if (!fs22.existsSync(validPath)) {
10020
10166
  return {
10021
10167
  success: false,
10022
10168
  error: "File not found"
10023
10169
  };
10024
10170
  }
10025
- const stats = fs21.statSync(validPath);
10171
+ const stats = fs22.statSync(validPath);
10026
10172
  if (stats.isDirectory()) {
10027
10173
  return {
10028
10174
  success: false,
@@ -10036,7 +10182,7 @@ async function handleFileEdit(command, projectPath) {
10036
10182
  error: `File too large for edit operation: ${stats.size} bytes exceeds limit of ${MAX_EDIT_SIZE} bytes (10MB). Use write-file for large files.`
10037
10183
  };
10038
10184
  }
10039
- const content = fs21.readFileSync(validPath, "utf8");
10185
+ const content = fs22.readFileSync(validPath, "utf8");
10040
10186
  const occurrences = content.split(oldString).length - 1;
10041
10187
  if (occurrences === 0) {
10042
10188
  return {
@@ -10053,8 +10199,8 @@ async function handleFileEdit(command, projectPath) {
10053
10199
  const newContent = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
10054
10200
  const replacements = replaceAll ? occurrences : 1;
10055
10201
  const tempPath = `${validPath}.tmp.${Date.now()}`;
10056
- fs21.writeFileSync(tempPath, newContent, "utf8");
10057
- fs21.renameSync(tempPath, validPath);
10202
+ fs22.writeFileSync(tempPath, newContent, "utf8");
10203
+ fs22.renameSync(tempPath, validPath);
10058
10204
  const newSize = Buffer.byteLength(newContent, "utf8");
10059
10205
  return {
10060
10206
  success: true,
@@ -10079,7 +10225,7 @@ async function handleFileDelete(command, projectPath) {
10079
10225
  error: "Invalid path: directory traversal not allowed"
10080
10226
  };
10081
10227
  }
10082
- const normalizedProjectPath = path21.resolve(projectPath);
10228
+ const normalizedProjectPath = path22.resolve(projectPath);
10083
10229
  if (validPath === normalizedProjectPath) {
10084
10230
  return {
10085
10231
  success: false,
@@ -10094,13 +10240,13 @@ async function handleFileDelete(command, projectPath) {
10094
10240
  };
10095
10241
  }
10096
10242
  try {
10097
- if (!fs21.existsSync(validPath)) {
10243
+ if (!fs22.existsSync(validPath)) {
10098
10244
  return {
10099
10245
  success: false,
10100
10246
  error: "Path not found"
10101
10247
  };
10102
10248
  }
10103
- const stats = fs21.statSync(validPath);
10249
+ const stats = fs22.statSync(validPath);
10104
10250
  const isDirectory = stats.isDirectory();
10105
10251
  if (isDirectory && !recursive) {
10106
10252
  return {
@@ -10109,9 +10255,9 @@ async function handleFileDelete(command, projectPath) {
10109
10255
  };
10110
10256
  }
10111
10257
  if (isDirectory) {
10112
- fs21.rmSync(validPath, { recursive: true, force: true });
10258
+ fs22.rmSync(validPath, { recursive: true, force: true });
10113
10259
  } else {
10114
- fs21.unlinkSync(validPath);
10260
+ fs22.unlinkSync(validPath);
10115
10261
  }
10116
10262
  return {
10117
10263
  success: true,
@@ -10144,8 +10290,8 @@ async function handleFileMkdir(command, projectPath) {
10144
10290
  };
10145
10291
  }
10146
10292
  try {
10147
- if (fs21.existsSync(validPath)) {
10148
- const stats = fs21.statSync(validPath);
10293
+ if (fs22.existsSync(validPath)) {
10294
+ const stats = fs22.statSync(validPath);
10149
10295
  if (stats.isDirectory()) {
10150
10296
  return {
10151
10297
  success: true,
@@ -10160,7 +10306,7 @@ async function handleFileMkdir(command, projectPath) {
10160
10306
  }
10161
10307
  }
10162
10308
  const modeNum = parseInt(mode, 8);
10163
- fs21.mkdirSync(validPath, { recursive: true, mode: modeNum });
10309
+ fs22.mkdirSync(validPath, { recursive: true, mode: modeNum });
10164
10310
  return {
10165
10311
  success: true,
10166
10312
  created: true
@@ -10187,7 +10333,7 @@ async function handleExec(command, projectPath) {
10187
10333
  env = {}
10188
10334
  } = command;
10189
10335
  const effectiveTimeout = Math.min(Math.max(timeout, 1e3), MAX_TIMEOUT);
10190
- return new Promise((resolve4) => {
10336
+ return new Promise((resolve6) => {
10191
10337
  let stdout = "";
10192
10338
  let stderr = "";
10193
10339
  let timedOut = false;
@@ -10195,7 +10341,7 @@ async function handleExec(command, projectPath) {
10195
10341
  const done = (result) => {
10196
10342
  if (resolved) return;
10197
10343
  resolved = true;
10198
- resolve4(result);
10344
+ resolve6(result);
10199
10345
  };
10200
10346
  try {
10201
10347
  const proc = (0, import_child_process11.spawn)(cmd, {
@@ -10259,9 +10405,9 @@ async function handleExec(command, projectPath) {
10259
10405
  }
10260
10406
 
10261
10407
  // src/daemon/handlers/worktree-handlers.ts
10262
- var path27 = __toESM(require("path"));
10263
- var fs27 = __toESM(require("fs"));
10264
- var os12 = __toESM(require("os"));
10408
+ var path28 = __toESM(require("path"));
10409
+ var fs28 = __toESM(require("fs"));
10410
+ var os13 = __toESM(require("os"));
10265
10411
  var import_child_process14 = require("child_process");
10266
10412
  var import_util2 = require("util");
10267
10413
 
@@ -10291,52 +10437,52 @@ var import_fs = require("fs");
10291
10437
  // src/preview/dev-server-runner.ts
10292
10438
  var import_child_process13 = require("child_process");
10293
10439
  var http = __toESM(require("http"));
10294
- var fs24 = __toESM(require("fs"));
10295
- var path24 = __toESM(require("path"));
10440
+ var fs25 = __toESM(require("fs"));
10441
+ var path25 = __toESM(require("path"));
10296
10442
  var import_events2 = require("events");
10297
10443
  var import_core12 = __toESM(require_dist());
10298
10444
 
10299
10445
  // src/utils/port-check.ts
10300
10446
  var net2 = __toESM(require("net"));
10301
10447
  async function isPortInUse(port) {
10302
- return new Promise((resolve4) => {
10448
+ return new Promise((resolve6) => {
10303
10449
  const server = net2.createServer();
10304
10450
  server.once("error", (err) => {
10305
10451
  if (err.code === "EADDRINUSE") {
10306
- resolve4(true);
10452
+ resolve6(true);
10307
10453
  } else {
10308
- resolve4(false);
10454
+ resolve6(false);
10309
10455
  }
10310
10456
  });
10311
10457
  server.once("listening", () => {
10312
10458
  server.close();
10313
- resolve4(false);
10459
+ resolve6(false);
10314
10460
  });
10315
10461
  server.listen(port);
10316
10462
  });
10317
10463
  }
10318
10464
 
10319
10465
  // src/utils/env-cache.ts
10320
- var fs22 = __toESM(require("fs"));
10321
- var path22 = __toESM(require("path"));
10322
- var os10 = __toESM(require("os"));
10466
+ var fs23 = __toESM(require("fs"));
10467
+ var path23 = __toESM(require("path"));
10468
+ var os11 = __toESM(require("os"));
10323
10469
  var DEFAULT_CACHE_TTL = 60;
10324
- var CACHE_DIR = path22.join(os10.homedir(), ".episoda", "cache");
10470
+ var CACHE_DIR = path23.join(os11.homedir(), ".episoda", "cache");
10325
10471
  function getCacheFilePath(projectId) {
10326
- return path22.join(CACHE_DIR, `env-vars-${projectId}.json`);
10472
+ return path23.join(CACHE_DIR, `env-vars-${projectId}.json`);
10327
10473
  }
10328
10474
  function ensureCacheDir() {
10329
- if (!fs22.existsSync(CACHE_DIR)) {
10330
- fs22.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
10475
+ if (!fs23.existsSync(CACHE_DIR)) {
10476
+ fs23.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
10331
10477
  }
10332
10478
  }
10333
10479
  function readCache(projectId) {
10334
10480
  try {
10335
10481
  const cacheFile = getCacheFilePath(projectId);
10336
- if (!fs22.existsSync(cacheFile)) {
10482
+ if (!fs23.existsSync(cacheFile)) {
10337
10483
  return null;
10338
10484
  }
10339
- const content = fs22.readFileSync(cacheFile, "utf-8");
10485
+ const content = fs23.readFileSync(cacheFile, "utf-8");
10340
10486
  const data = JSON.parse(content);
10341
10487
  if (!data.vars || typeof data.vars !== "object" || !data.fetchedAt) {
10342
10488
  return null;
@@ -10355,7 +10501,7 @@ function writeCache(projectId, vars) {
10355
10501
  fetchedAt: Date.now(),
10356
10502
  projectId
10357
10503
  };
10358
- fs22.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
10504
+ fs23.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
10359
10505
  } catch (error) {
10360
10506
  console.warn("[env-cache] Failed to write cache:", error instanceof Error ? error.message : error);
10361
10507
  }
@@ -10424,11 +10570,11 @@ No cached values available as fallback.`
10424
10570
  }
10425
10571
 
10426
10572
  // src/preview/dev-server-registry.ts
10427
- var fs23 = __toESM(require("fs"));
10428
- var path23 = __toESM(require("path"));
10429
- var os11 = __toESM(require("os"));
10573
+ var fs24 = __toESM(require("fs"));
10574
+ var path24 = __toESM(require("path"));
10575
+ var os12 = __toESM(require("os"));
10430
10576
  var import_child_process12 = require("child_process");
10431
- var DEV_SERVER_REGISTRY_DIR = path23.join(os11.homedir(), ".episoda", "dev-servers");
10577
+ var DEV_SERVER_REGISTRY_DIR = path24.join(os12.homedir(), ".episoda", "dev-servers");
10432
10578
  var DevServerRegistry = class {
10433
10579
  constructor() {
10434
10580
  this.ensureRegistryDir();
@@ -10438,9 +10584,9 @@ var DevServerRegistry = class {
10438
10584
  */
10439
10585
  ensureRegistryDir() {
10440
10586
  try {
10441
- if (!fs23.existsSync(DEV_SERVER_REGISTRY_DIR)) {
10587
+ if (!fs24.existsSync(DEV_SERVER_REGISTRY_DIR)) {
10442
10588
  console.log(`[DevServerRegistry] EP1042: Creating registry directory: ${DEV_SERVER_REGISTRY_DIR}`);
10443
- fs23.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
10589
+ fs24.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
10444
10590
  }
10445
10591
  } catch (error) {
10446
10592
  console.error(`[DevServerRegistry] EP1042: Failed to create registry directory:`, error);
@@ -10451,7 +10597,7 @@ var DevServerRegistry = class {
10451
10597
  * Get the registry file path for a module
10452
10598
  */
10453
10599
  getEntryPath(moduleUid) {
10454
- return path23.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
10600
+ return path24.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
10455
10601
  }
10456
10602
  /**
10457
10603
  * Register a dev server
@@ -10462,8 +10608,10 @@ var DevServerRegistry = class {
10462
10608
  try {
10463
10609
  this.ensureRegistryDir();
10464
10610
  const entryPath = this.getEntryPath(entry.moduleUid);
10465
- fs23.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
10466
- console.log(`[DevServerRegistry] EP1042: Registered ${entry.moduleUid} (PID ${entry.pid}, port ${entry.port})`);
10611
+ fs24.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
10612
+ console.log(
10613
+ `[DevServerRegistry] EP1042: Registered ${entry.moduleUid} (PID ${entry.pid}, port ${entry.port}${entry.wsPort ? `, wsPort ${entry.wsPort}` : ""})`
10614
+ );
10467
10615
  } catch (error) {
10468
10616
  console.error(`[DevServerRegistry] EP1042: Failed to register ${entry.moduleUid}:`, error);
10469
10617
  }
@@ -10476,8 +10624,8 @@ var DevServerRegistry = class {
10476
10624
  unregister(moduleUid) {
10477
10625
  try {
10478
10626
  const entryPath = this.getEntryPath(moduleUid);
10479
- if (fs23.existsSync(entryPath)) {
10480
- fs23.unlinkSync(entryPath);
10627
+ if (fs24.existsSync(entryPath)) {
10628
+ fs24.unlinkSync(entryPath);
10481
10629
  console.log(`[DevServerRegistry] EP1042: Unregistered ${moduleUid}`);
10482
10630
  }
10483
10631
  } catch (error) {
@@ -10493,16 +10641,21 @@ var DevServerRegistry = class {
10493
10641
  getByModule(moduleUid) {
10494
10642
  try {
10495
10643
  const entryPath = this.getEntryPath(moduleUid);
10496
- if (!fs23.existsSync(entryPath)) {
10644
+ if (!fs24.existsSync(entryPath)) {
10497
10645
  return null;
10498
10646
  }
10499
- const content = fs23.readFileSync(entryPath, "utf8");
10647
+ const content = fs24.readFileSync(entryPath, "utf8");
10500
10648
  const entry = JSON.parse(content);
10501
10649
  if (!entry.pid || !entry.port || !entry.worktreePath) {
10502
10650
  console.warn(`[DevServerRegistry] EP1042: Invalid entry for ${moduleUid}, removing`);
10503
10651
  this.unregister(moduleUid);
10504
10652
  return null;
10505
10653
  }
10654
+ if (entry.wsPort !== void 0 && (typeof entry.wsPort !== "number" || entry.wsPort <= 0)) {
10655
+ console.warn(`[DevServerRegistry] EP1042: Invalid wsPort for ${moduleUid}, removing`);
10656
+ this.unregister(moduleUid);
10657
+ return null;
10658
+ }
10506
10659
  if (!this.isProcessRunning(entry.pid)) {
10507
10660
  console.log(`[DevServerRegistry] EP1042: PID ${entry.pid} for ${moduleUid} is not running, removing stale entry`);
10508
10661
  this.unregister(moduleUid);
@@ -10534,7 +10687,7 @@ var DevServerRegistry = class {
10534
10687
  const entries = [];
10535
10688
  try {
10536
10689
  this.ensureRegistryDir();
10537
- const files = fs23.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
10690
+ const files = fs24.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
10538
10691
  for (const file of files) {
10539
10692
  const moduleUid = file.replace(".json", "");
10540
10693
  const entry = this.getByModule(moduleUid);
@@ -10592,7 +10745,7 @@ var DevServerRegistry = class {
10592
10745
  return null;
10593
10746
  } catch {
10594
10747
  try {
10595
- return fs23.readlinkSync(`/proc/${pid}/cwd`);
10748
+ return fs24.readlinkSync(`/proc/${pid}/cwd`);
10596
10749
  } catch {
10597
10750
  return null;
10598
10751
  }
@@ -10649,6 +10802,10 @@ var DevServerRegistry = class {
10649
10802
  }
10650
10803
  cleaned.push(entry.pid);
10651
10804
  }
10805
+ if (entry.wsPort) {
10806
+ const wsPids = this.killWsServerOnPort(entry.wsPort, entry.worktreePath);
10807
+ cleaned.push(...wsPids);
10808
+ }
10652
10809
  this.unregister(entry.moduleUid);
10653
10810
  }
10654
10811
  }
@@ -10702,7 +10859,39 @@ var DevServerRegistry = class {
10702
10859
  return killed;
10703
10860
  }
10704
10861
  wait(ms) {
10705
- return new Promise((resolve4) => setTimeout(resolve4, ms));
10862
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
10863
+ }
10864
+ killWsServerOnPort(wsPort, worktreePath) {
10865
+ const killed = [];
10866
+ try {
10867
+ const output = (0, import_child_process12.execSync)(`lsof -ti:${wsPort} 2>/dev/null || true`, {
10868
+ encoding: "utf8",
10869
+ timeout: 5e3
10870
+ }).trim();
10871
+ if (!output) {
10872
+ return killed;
10873
+ }
10874
+ const expectedPath = path24.resolve(worktreePath);
10875
+ const pids = output.split("\n").map((line) => parseInt(line, 10)).filter((pid) => !isNaN(pid) && pid > 0);
10876
+ for (const pid of pids) {
10877
+ const command = (0, import_child_process12.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8", timeout: 5e3 }).trim();
10878
+ const cwd = this.getProcessCwd(pid);
10879
+ const cwdMatches = cwd ? path24.resolve(cwd) === expectedPath : false;
10880
+ const isWsServer = command.includes("ws-server.js") || command.includes("build:ws-server");
10881
+ if (!isWsServer || !cwdMatches) {
10882
+ continue;
10883
+ }
10884
+ this.killProcess(pid, "SIGTERM");
10885
+ killed.push(pid);
10886
+ }
10887
+ for (const pid of killed) {
10888
+ if (this.isProcessRunning(pid)) {
10889
+ this.killProcess(pid, "SIGKILL");
10890
+ }
10891
+ }
10892
+ } catch {
10893
+ }
10894
+ return killed;
10706
10895
  }
10707
10896
  };
10708
10897
  var registryInstance = null;
@@ -10729,6 +10918,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10729
10918
  const {
10730
10919
  projectPath,
10731
10920
  port,
10921
+ wsPort,
10732
10922
  moduleUid,
10733
10923
  customCommand,
10734
10924
  autoRestart = true
@@ -10738,13 +10928,21 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10738
10928
  const existingEntry = registry.getByPort(port);
10739
10929
  if (existingEntry) {
10740
10930
  if (existingEntry.worktreePath === projectPath && existingEntry.moduleUid === moduleUid) {
10931
+ const wsHealthy = await this.checkWsHealth(wsPort);
10932
+ if (!wsHealthy) {
10933
+ console.warn(
10934
+ `[DevServerRunner] EP1412: Existing server for ${moduleUid} has unhealthy ws-server on ${wsPort}; preserving preview because app port ${port} is healthy`
10935
+ );
10936
+ }
10741
10937
  console.log(`[DevServerRunner] EP1042: Correct server already running on port ${port} for ${moduleUid}`);
10742
10938
  return { success: true, alreadyRunning: true };
10743
10939
  }
10744
- console.log(`[DevServerRunner] EP1042: Port ${port} owned by ${existingEntry.moduleUid} (${existingEntry.worktreePath}), killing...`);
10745
- await this.killProcessOnPort(port);
10746
- registry.unregister(existingEntry.moduleUid);
10747
- this.servers.delete(existingEntry.moduleUid);
10940
+ if (existingEntry.worktreePath !== projectPath || existingEntry.moduleUid !== moduleUid) {
10941
+ console.log(`[DevServerRunner] EP1042: Port ${port} owned by ${existingEntry.moduleUid} (${existingEntry.worktreePath}), killing...`);
10942
+ await this.killProcessOnPort(port);
10943
+ registry.unregister(existingEntry.moduleUid);
10944
+ this.servers.delete(existingEntry.moduleUid);
10945
+ }
10748
10946
  } else {
10749
10947
  console.log(`[DevServerRunner] EP1042: Unknown process on port ${port}, killing...`);
10750
10948
  await this.killProcessOnPort(port);
@@ -10754,21 +10952,32 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10754
10952
  return { success: false, error: `Port ${port} still in use after cleanup` };
10755
10953
  }
10756
10954
  }
10955
+ const wsPortAvailability = await this.ensureWsPortAvailable(projectPath, wsPort);
10956
+ if (wsPortAvailability) {
10957
+ return wsPortAvailability;
10958
+ }
10757
10959
  const existing = this.servers.get(moduleUid);
10758
10960
  if (existing && !existing.process.killed) {
10961
+ const wsHealthy = await this.checkWsHealth(existing.wsPort);
10962
+ if (!wsHealthy) {
10963
+ console.warn(
10964
+ `[DevServerRunner] EP1412: In-memory server exists for ${moduleUid} with unhealthy ws-server (${existing.wsPort}); preserving running app process`
10965
+ );
10966
+ }
10759
10967
  console.log(`[DevServerRunner] Process already exists for ${moduleUid}`);
10760
10968
  return { success: true, alreadyRunning: true };
10761
10969
  }
10762
- console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port}...`);
10970
+ console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port} (ws:${wsPort})...`);
10763
10971
  const injectedEnvVars = await this.fetchEnvVars(projectPath);
10764
10972
  try {
10765
10973
  const logPath = this.getLogFilePath(moduleUid);
10766
- const process2 = this.spawnProcess(projectPath, port, moduleUid, logPath, customCommand, injectedEnvVars);
10974
+ const process2 = this.spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, injectedEnvVars);
10767
10975
  const state = {
10768
10976
  process: process2,
10769
10977
  moduleUid,
10770
10978
  projectPath,
10771
10979
  port,
10980
+ wsPort,
10772
10981
  startedAt: /* @__PURE__ */ new Date(),
10773
10982
  restartCount: 0,
10774
10983
  lastRestartAt: null,
@@ -10788,16 +10997,26 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10788
10997
  this.writeToLog(logPath, "Failed to start within timeout", true);
10789
10998
  return { success: false, error: "Dev server failed to start within timeout" };
10790
10999
  }
11000
+ console.log(`[DevServerRunner] Waiting for ws-server on port ${wsPort}...`);
11001
+ const wsReady = await this.waitForWsPort(wsPort, DEV_SERVER_CONSTANTS.STARTUP_TIMEOUT_MS);
11002
+ if (!wsReady) {
11003
+ const wsStartError = this.detectWsStartupError(logPath, wsPort);
11004
+ this.writeToLog(logPath, `WARN: ${wsStartError}`, true);
11005
+ console.warn(
11006
+ `[DevServerRunner] EP1412: ws-server did not become healthy on ${wsPort}; continuing because preview liveness is app/tunnel based`
11007
+ );
11008
+ }
10791
11009
  if (process2.pid) {
10792
11010
  registry.register({
10793
11011
  pid: process2.pid,
10794
11012
  port,
11013
+ wsPort,
10795
11014
  worktreePath: projectPath,
10796
11015
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
10797
11016
  moduleUid
10798
11017
  });
10799
11018
  }
10800
- console.log(`[DevServerRunner] Server started successfully on port ${port}`);
11019
+ console.log(`[DevServerRunner] Server started successfully on ports app:${port} ws:${wsPort}`);
10801
11020
  this.writeToLog(logPath, "Server started successfully");
10802
11021
  this.emit("started", moduleUid, port);
10803
11022
  return { success: true };
@@ -10830,6 +11049,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10830
11049
  state.process.kill("SIGKILL");
10831
11050
  }
10832
11051
  }
11052
+ await this.cleanupWsServerOnPort(state.wsPort, state.projectPath);
10833
11053
  } else if (registryEntry) {
10834
11054
  console.log(`[DevServerRunner] EP1042: Stopping orphaned server for ${moduleUid} (PID ${registryEntry.pid})`);
10835
11055
  if (registry.isProcessRunning(registryEntry.pid)) {
@@ -10839,6 +11059,9 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10839
11059
  registry.killProcess(registryEntry.pid, "SIGKILL");
10840
11060
  }
10841
11061
  }
11062
+ if (registryEntry.wsPort) {
11063
+ await this.cleanupWsServerOnPort(registryEntry.wsPort, registryEntry.worktreePath);
11064
+ }
10842
11065
  }
10843
11066
  this.servers.delete(moduleUid);
10844
11067
  registry.unregister(moduleUid);
@@ -10852,7 +11075,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10852
11075
  if (!state) {
10853
11076
  return { success: false, error: `No dev server found for ${moduleUid}` };
10854
11077
  }
10855
- const { projectPath, port, autoRestartEnabled, customCommand, logFile } = state;
11078
+ const { projectPath, port, wsPort, autoRestartEnabled, customCommand, logFile } = state;
10856
11079
  console.log(`[DevServerRunner] Restarting server for ${moduleUid}...`);
10857
11080
  this.writeToLog(logFile, "Manual restart requested");
10858
11081
  await this.stop(moduleUid);
@@ -10863,6 +11086,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10863
11086
  return this.start({
10864
11087
  projectPath,
10865
11088
  port,
11089
+ wsPort,
10866
11090
  moduleUid,
10867
11091
  customCommand,
10868
11092
  autoRestart: autoRestartEnabled
@@ -10878,6 +11102,16 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10878
11102
  }
10879
11103
  return this.checkHealth(state.port);
10880
11104
  }
11105
+ /**
11106
+ * Check if ws-server for a module is healthy
11107
+ */
11108
+ async isWsHealthy(moduleUid) {
11109
+ const state = this.servers.get(moduleUid);
11110
+ if (!state) {
11111
+ return false;
11112
+ }
11113
+ return this.checkWsHealth(state.wsPort);
11114
+ }
10881
11115
  /**
10882
11116
  * Check if a dev server is running
10883
11117
  */
@@ -10949,6 +11183,125 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10949
11183
  return false;
10950
11184
  }
10951
11185
  }
11186
+ async ensureWsPortAvailable(projectPath, wsPort) {
11187
+ if (!await isPortInUse(wsPort)) {
11188
+ return null;
11189
+ }
11190
+ const pids = this.getPidsOnPort(wsPort);
11191
+ if (pids.length === 0) {
11192
+ return { success: false, error: `WS_BIND_CONFLICT: WS port ${wsPort} is already in use.` };
11193
+ }
11194
+ const registry = getDevServerRegistry();
11195
+ const expectedPath = path25.resolve(projectPath);
11196
+ const unsafe = [];
11197
+ const killable = [];
11198
+ for (const pid of pids) {
11199
+ const command = this.getProcessCommand(pid);
11200
+ const cwd = registry.getProcessCwd(pid);
11201
+ const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11202
+ if (this.isLikelyWsServerProcess(command) && cwdMatches) {
11203
+ killable.push(pid);
11204
+ } else {
11205
+ unsafe.push({ pid, command });
11206
+ }
11207
+ }
11208
+ if (unsafe.length > 0) {
11209
+ const details = unsafe.map((item) => `PID ${item.pid} (${item.command})`).join(", ");
11210
+ return {
11211
+ success: false,
11212
+ error: `WS_BIND_CONFLICT: WS port ${wsPort} is in use by another process: ${details}`
11213
+ };
11214
+ }
11215
+ for (const pid of killable) {
11216
+ try {
11217
+ process.kill(pid, "SIGTERM");
11218
+ } catch {
11219
+ }
11220
+ }
11221
+ await this.wait(800);
11222
+ for (const pid of killable) {
11223
+ try {
11224
+ process.kill(pid, 0);
11225
+ process.kill(pid, "SIGKILL");
11226
+ } catch {
11227
+ }
11228
+ }
11229
+ await this.wait(400);
11230
+ if (await isPortInUse(wsPort)) {
11231
+ return {
11232
+ success: false,
11233
+ error: `WS_BIND_CONFLICT: WS port ${wsPort} remained in use after stale ws-server cleanup`
11234
+ };
11235
+ }
11236
+ return null;
11237
+ }
11238
+ async cleanupWsServerOnPort(wsPort, worktreePath) {
11239
+ if (!wsPort || !await isPortInUse(wsPort)) return;
11240
+ const registry = getDevServerRegistry();
11241
+ const expectedPath = path25.resolve(worktreePath);
11242
+ const pids = this.getPidsOnPort(wsPort);
11243
+ for (const pid of pids) {
11244
+ const command = this.getProcessCommand(pid);
11245
+ const cwd = registry.getProcessCwd(pid);
11246
+ const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11247
+ if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
11248
+ continue;
11249
+ }
11250
+ try {
11251
+ process.kill(pid, "SIGTERM");
11252
+ } catch {
11253
+ }
11254
+ }
11255
+ await this.wait(500);
11256
+ for (const pid of pids) {
11257
+ const command = this.getProcessCommand(pid);
11258
+ const cwd = registry.getProcessCwd(pid);
11259
+ const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11260
+ if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
11261
+ continue;
11262
+ }
11263
+ try {
11264
+ process.kill(pid, 0);
11265
+ process.kill(pid, "SIGKILL");
11266
+ } catch {
11267
+ }
11268
+ }
11269
+ }
11270
+ getPidsOnPort(port) {
11271
+ try {
11272
+ const output = (0, import_child_process13.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
11273
+ if (!output) return [];
11274
+ return output.split("\n").map((value) => parseInt(value, 10)).filter((value) => !isNaN(value) && value > 0);
11275
+ } catch {
11276
+ return [];
11277
+ }
11278
+ }
11279
+ getProcessCommand(pid) {
11280
+ try {
11281
+ return (0, import_child_process13.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim() || "unknown";
11282
+ } catch {
11283
+ return "unknown";
11284
+ }
11285
+ }
11286
+ isLikelyWsServerProcess(command) {
11287
+ return command.includes("ws-server.js") || command.includes("build:ws-server");
11288
+ }
11289
+ detectWsStartupError(logPath, wsPort) {
11290
+ const fallback = `WS_BIND_CONFLICT: ws-server failed to become healthy on WS port ${wsPort}`;
11291
+ if (!logPath) return fallback;
11292
+ try {
11293
+ if (!fs25.existsSync(logPath)) return fallback;
11294
+ const content = fs25.readFileSync(logPath, "utf8");
11295
+ if (content.includes("EADDRINUSE") && content.includes(`${wsPort}`)) {
11296
+ return `WS_BIND_CONFLICT: ws-server could not bind to WS port ${wsPort} (EADDRINUSE)`;
11297
+ }
11298
+ if (content.includes("Failed to initialize")) {
11299
+ return `WS_SERVER_START_FAILED: ws-server failed during startup on WS port ${wsPort}`;
11300
+ }
11301
+ } catch {
11302
+ }
11303
+ return fallback;
11304
+ }
10952
11305
  // ============ Private Methods ============
10953
11306
  async fetchEnvVars(projectPath) {
10954
11307
  try {
@@ -10962,8 +11315,8 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10962
11315
  cacheTtl: 300
10963
11316
  });
10964
11317
  console.log(`[DevServerRunner] Loaded ${Object.keys(result.envVars).length} env vars`);
10965
- const envFilePath = path24.join(projectPath, ".env");
10966
- if (!fs24.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
11318
+ const envFilePath = path25.join(projectPath, ".env");
11319
+ if (!fs25.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
10967
11320
  console.log(`[DevServerRunner] Writing .env file`);
10968
11321
  writeEnvFile(projectPath, result.envVars);
10969
11322
  }
@@ -10973,7 +11326,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10973
11326
  return {};
10974
11327
  }
10975
11328
  }
10976
- spawnProcess(projectPath, port, moduleUid, logPath, customCommand, envVars) {
11329
+ spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, envVars) {
10977
11330
  this.rotateLogIfNeeded(logPath);
10978
11331
  const nodeOptions = process.env.NODE_OPTIONS || "";
10979
11332
  const memoryFlag = `--max-old-space-size=${DEV_SERVER_CONSTANTS.NODE_MEMORY_LIMIT_MB}`;
@@ -10985,6 +11338,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10985
11338
  ...process.env,
10986
11339
  ...envVars,
10987
11340
  PORT: String(port),
11341
+ WS_PORT: String(wsPort),
10988
11342
  NODE_OPTIONS: enhancedNodeOptions
10989
11343
  };
10990
11344
  const proc = (0, import_child_process13.spawn)(cmd, args, {
@@ -11048,6 +11402,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11048
11402
  const newProcess = this.spawnProcess(
11049
11403
  state.projectPath,
11050
11404
  state.port,
11405
+ state.wsPort,
11051
11406
  moduleUid,
11052
11407
  logPath,
11053
11408
  state.customCommand,
@@ -11078,7 +11433,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11078
11433
  return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
11079
11434
  }
11080
11435
  async checkHealth(port) {
11081
- return new Promise((resolve4) => {
11436
+ return new Promise((resolve6) => {
11082
11437
  const req = http.request(
11083
11438
  {
11084
11439
  hostname: "localhost",
@@ -11087,12 +11442,35 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11087
11442
  method: "HEAD",
11088
11443
  timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
11089
11444
  },
11090
- () => resolve4(true)
11445
+ () => resolve6(true)
11446
+ );
11447
+ req.on("error", () => resolve6(false));
11448
+ req.on("timeout", () => {
11449
+ req.destroy();
11450
+ resolve6(false);
11451
+ });
11452
+ req.end();
11453
+ });
11454
+ }
11455
+ async checkWsHealth(wsPort) {
11456
+ return new Promise((resolve6) => {
11457
+ const req = http.request(
11458
+ {
11459
+ hostname: "127.0.0.1",
11460
+ port: wsPort,
11461
+ path: "/health",
11462
+ method: "GET",
11463
+ timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
11464
+ },
11465
+ (res) => {
11466
+ resolve6(res.statusCode === 200);
11467
+ res.resume();
11468
+ }
11091
11469
  );
11092
- req.on("error", () => resolve4(false));
11470
+ req.on("error", () => resolve6(false));
11093
11471
  req.on("timeout", () => {
11094
11472
  req.destroy();
11095
- resolve4(false);
11473
+ resolve6(false);
11096
11474
  });
11097
11475
  req.end();
11098
11476
  });
@@ -11108,29 +11486,40 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11108
11486
  }
11109
11487
  return false;
11110
11488
  }
11489
+ async waitForWsPort(wsPort, timeoutMs) {
11490
+ const startTime = Date.now();
11491
+ const checkInterval = 500;
11492
+ while (Date.now() - startTime < timeoutMs) {
11493
+ if (await this.checkWsHealth(wsPort)) {
11494
+ return true;
11495
+ }
11496
+ await this.wait(checkInterval);
11497
+ }
11498
+ return false;
11499
+ }
11111
11500
  wait(ms) {
11112
- return new Promise((resolve4) => setTimeout(resolve4, ms));
11501
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
11113
11502
  }
11114
11503
  getLogsDir() {
11115
- const logsDir = path24.join((0, import_core12.getConfigDir)(), "logs");
11116
- if (!fs24.existsSync(logsDir)) {
11117
- fs24.mkdirSync(logsDir, { recursive: true });
11504
+ const logsDir = path25.join((0, import_core12.getConfigDir)(), "logs");
11505
+ if (!fs25.existsSync(logsDir)) {
11506
+ fs25.mkdirSync(logsDir, { recursive: true });
11118
11507
  }
11119
11508
  return logsDir;
11120
11509
  }
11121
11510
  getLogFilePath(moduleUid) {
11122
- return path24.join(this.getLogsDir(), `dev-${moduleUid}.log`);
11511
+ return path25.join(this.getLogsDir(), `dev-${moduleUid}.log`);
11123
11512
  }
11124
11513
  rotateLogIfNeeded(logPath) {
11125
11514
  try {
11126
- if (fs24.existsSync(logPath)) {
11127
- const stats = fs24.statSync(logPath);
11515
+ if (fs25.existsSync(logPath)) {
11516
+ const stats = fs25.statSync(logPath);
11128
11517
  if (stats.size > DEV_SERVER_CONSTANTS.MAX_LOG_SIZE_BYTES) {
11129
11518
  const backupPath = `${logPath}.1`;
11130
- if (fs24.existsSync(backupPath)) {
11131
- fs24.unlinkSync(backupPath);
11519
+ if (fs25.existsSync(backupPath)) {
11520
+ fs25.unlinkSync(backupPath);
11132
11521
  }
11133
- fs24.renameSync(logPath, backupPath);
11522
+ fs25.renameSync(logPath, backupPath);
11134
11523
  }
11135
11524
  }
11136
11525
  } catch {
@@ -11141,7 +11530,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11141
11530
  try {
11142
11531
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11143
11532
  const prefix = isError ? "ERR" : "OUT";
11144
- fs24.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
11533
+ fs25.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
11145
11534
  `);
11146
11535
  } catch {
11147
11536
  }
@@ -11150,6 +11539,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11150
11539
  return {
11151
11540
  moduleUid: state.moduleUid,
11152
11541
  port: state.port,
11542
+ wsPort: state.wsPort,
11153
11543
  pid: state.process.pid,
11154
11544
  startedAt: state.startedAt,
11155
11545
  uptime: Math.floor((Date.now() - state.startedAt.getTime()) / 1e3),
@@ -11210,7 +11600,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
11210
11600
  * @returns Result with success status and preview URL
11211
11601
  */
11212
11602
  async startPreview(config) {
11213
- const { moduleUid, worktreePath, port = DEFAULT_PORT, customCommand } = config;
11603
+ const { moduleUid, worktreePath, port = DEFAULT_PORT, wsPort: requestedWsPort, customCommand } = config;
11604
+ let wsPort = requestedWsPort;
11214
11605
  if (!worktreePath) {
11215
11606
  return { success: false, error: "Worktree path is required" };
11216
11607
  }
@@ -11237,29 +11628,50 @@ var PreviewManager = class extends import_events3.EventEmitter {
11237
11628
  }
11238
11629
  const existing = this.previews.get(moduleUid);
11239
11630
  if (existing && (existing.state === "live" || existing.state === "running")) {
11240
- console.log(`[PreviewManager] Preview already running for ${moduleUid}`);
11241
- return {
11242
- success: true,
11243
- previewUrl: existing.tunnelUrl,
11244
- alreadyRunning: true
11245
- };
11631
+ const appHealthy = await this.devServer.isHealthy(moduleUid);
11632
+ const wsHealthy = await this.devServer.isWsHealthy(moduleUid);
11633
+ if (appHealthy) {
11634
+ if (!wsHealthy) {
11635
+ console.warn(
11636
+ `[PreviewManager] EP1412: Preview app is healthy for ${moduleUid}; ws-server unhealthy (${existing.wsPort}) is diagnostics-only for preview liveness`
11637
+ );
11638
+ }
11639
+ console.log(`[PreviewManager] Preview already running for ${moduleUid}`);
11640
+ return {
11641
+ success: true,
11642
+ previewUrl: existing.tunnelUrl,
11643
+ alreadyRunning: true
11644
+ };
11645
+ }
11646
+ console.warn(
11647
+ `[PreviewManager] EP1412: Existing preview for ${moduleUid} is unhealthy (app=${appHealthy}, ws=${wsHealthy}); restarting due to app health failure`
11648
+ );
11649
+ await this.stopPreview(moduleUid);
11246
11650
  }
11247
11651
  this.startingModules.add(moduleUid);
11248
- console.log(`[PreviewManager] Starting preview for ${moduleUid} at ${worktreePath}:${port}`);
11249
- const state = {
11250
- moduleUid,
11251
- worktreePath,
11252
- port,
11253
- state: "starting",
11254
- startedAt: /* @__PURE__ */ new Date()
11255
- };
11256
- this.previews.set(moduleUid, state);
11257
- this.emitStateChange(moduleUid, "starting");
11652
+ let state = null;
11258
11653
  try {
11654
+ if (!wsPort) {
11655
+ wsPort = allocateWsPort(moduleUid);
11656
+ }
11657
+ wsPort = assignWsPort(moduleUid, wsPort);
11658
+ console.log(`[PreviewManager] Starting preview for ${moduleUid} at ${worktreePath}:${port} (ws:${wsPort})`);
11659
+ state = {
11660
+ moduleUid,
11661
+ worktreePath,
11662
+ port,
11663
+ wsPort,
11664
+ state: "starting",
11665
+ startedAt: /* @__PURE__ */ new Date()
11666
+ };
11667
+ const activeState = state;
11668
+ this.previews.set(moduleUid, state);
11669
+ this.emitStateChange(moduleUid, "starting");
11259
11670
  console.log(`[PreviewManager] Starting dev server for ${moduleUid}...`);
11260
11671
  const devResult = await this.devServer.start({
11261
11672
  projectPath: worktreePath,
11262
11673
  port,
11674
+ wsPort,
11263
11675
  moduleUid,
11264
11676
  customCommand,
11265
11677
  autoRestart: true
@@ -11267,6 +11679,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
11267
11679
  if (!devResult.success) {
11268
11680
  state.state = "error";
11269
11681
  state.error = devResult.error || "Failed to start dev server";
11682
+ releasePort(moduleUid);
11683
+ releaseWsPort(moduleUid);
11270
11684
  this.emitStateChange(moduleUid, "error");
11271
11685
  this.emit("error", moduleUid, new Error(state.error));
11272
11686
  return { success: false, error: state.error };
@@ -11275,7 +11689,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11275
11689
  this.emitStateChange(moduleUid, "running");
11276
11690
  console.log(`[PreviewManager] Dev server running on port ${port}`);
11277
11691
  console.log(`[PreviewManager] Starting Named Tunnel for ${moduleUid}...`);
11278
- state.state = "tunneling";
11692
+ activeState.state = "tunneling";
11279
11693
  this.emitStateChange(moduleUid, "tunneling");
11280
11694
  const MAX_TUNNEL_RETRIES = 2;
11281
11695
  let tunnelResult = { success: false };
@@ -11283,7 +11697,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11283
11697
  for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
11284
11698
  if (attempt > 1) {
11285
11699
  console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
11286
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
11700
+ await new Promise((resolve6) => setTimeout(resolve6, 2e3));
11287
11701
  }
11288
11702
  tunnelResult = await this.tunnel.startTunnel({
11289
11703
  moduleUid,
@@ -11293,20 +11707,20 @@ var PreviewManager = class extends import_events3.EventEmitter {
11293
11707
  onStatusChange: (status, error) => {
11294
11708
  console.log(`[PreviewManager] Tunnel status for ${moduleUid}: ${status}${error ? ` - ${error}` : ""}`);
11295
11709
  if (status === "error") {
11296
- state.state = "error";
11297
- state.error = error || "Tunnel error";
11710
+ activeState.state = "error";
11711
+ activeState.error = error || "Tunnel error";
11298
11712
  this.emitStateChange(moduleUid, "error");
11299
11713
  } else if (status === "disconnected") {
11300
- state.state = "running";
11301
- state.tunnelUrl = void 0;
11714
+ activeState.state = "running";
11715
+ activeState.tunnelUrl = void 0;
11302
11716
  this.emitStateChange(moduleUid, "running");
11303
11717
  } else if (status === "reconnecting") {
11304
- state.state = "tunneling";
11718
+ activeState.state = "tunneling";
11305
11719
  this.emitStateChange(moduleUid, "tunneling");
11306
11720
  }
11307
11721
  },
11308
11722
  onUrl: (url) => {
11309
- state.tunnelUrl = url;
11723
+ activeState.tunnelUrl = url;
11310
11724
  }
11311
11725
  });
11312
11726
  if (tunnelResult.success) {
@@ -11322,8 +11736,10 @@ var PreviewManager = class extends import_events3.EventEmitter {
11322
11736
  } catch (cleanupError) {
11323
11737
  console.warn(`[PreviewManager] Error cleaning up dev server after tunnel failure:`, cleanupError);
11324
11738
  }
11325
- state.state = "error";
11326
- state.error = lastError;
11739
+ releasePort(moduleUid);
11740
+ releaseWsPort(moduleUid);
11741
+ activeState.state = "error";
11742
+ activeState.error = lastError;
11327
11743
  this.previews.delete(moduleUid);
11328
11744
  this.emitStateChange(moduleUid, "error");
11329
11745
  return {
@@ -11331,9 +11747,9 @@ var PreviewManager = class extends import_events3.EventEmitter {
11331
11747
  error: `Tunnel failed after ${MAX_TUNNEL_RETRIES} attempts: ${lastError}`
11332
11748
  };
11333
11749
  }
11334
- state.state = "live";
11335
- state.tunnelUrl = tunnelResult.url;
11336
- state.error = void 0;
11750
+ activeState.state = "live";
11751
+ activeState.tunnelUrl = tunnelResult.url;
11752
+ activeState.error = void 0;
11337
11753
  this.emitStateChange(moduleUid, "live");
11338
11754
  this.emit("live", moduleUid, tunnelResult.url);
11339
11755
  console.log(`[PreviewManager] Preview live for ${moduleUid}: ${tunnelResult.url}`);
@@ -11344,9 +11760,17 @@ var PreviewManager = class extends import_events3.EventEmitter {
11344
11760
  } catch (error) {
11345
11761
  const errorMsg = error instanceof Error ? error.message : String(error);
11346
11762
  console.error(`[PreviewManager] Error starting preview for ${moduleUid}:`, error);
11347
- state.state = "error";
11348
- state.error = errorMsg;
11349
- this.emitStateChange(moduleUid, "error");
11763
+ if (state) {
11764
+ state.state = "error";
11765
+ state.error = errorMsg;
11766
+ } else {
11767
+ this.previews.delete(moduleUid);
11768
+ }
11769
+ releasePort(moduleUid);
11770
+ releaseWsPort(moduleUid);
11771
+ if (state) {
11772
+ this.emitStateChange(moduleUid, "error");
11773
+ }
11350
11774
  this.emit("error", moduleUid, error instanceof Error ? error : new Error(errorMsg));
11351
11775
  return { success: false, error: errorMsg };
11352
11776
  } finally {
@@ -11382,6 +11806,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11382
11806
  console.warn(`[PreviewManager] Error clearing tunnel URL for ${moduleUid}:`, error);
11383
11807
  }
11384
11808
  releasePort(moduleUid);
11809
+ releaseWsPort(moduleUid);
11385
11810
  if (state) {
11386
11811
  state.state = "stopped";
11387
11812
  state.tunnelUrl = void 0;
@@ -11404,11 +11829,12 @@ var PreviewManager = class extends import_events3.EventEmitter {
11404
11829
  }
11405
11830
  console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
11406
11831
  await this.stopPreview(moduleUid);
11407
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
11832
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
11408
11833
  return this.startPreview({
11409
11834
  moduleUid,
11410
11835
  worktreePath: state.worktreePath,
11411
- port: state.port
11836
+ port: state.port,
11837
+ wsPort: state.wsPort
11412
11838
  });
11413
11839
  }
11414
11840
  /**
@@ -11431,6 +11857,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11431
11857
  tunnelUrl: state.tunnelUrl,
11432
11858
  tunnelState: tunnelInfo?.status === "connected" ? "connected" : tunnelInfo?.status === "starting" ? "starting" : tunnelInfo?.status === "error" ? "error" : tunnelInfo?.status === "disconnected" ? "disconnected" : void 0,
11433
11859
  port: state.port,
11860
+ wsPort: state.wsPort,
11434
11861
  error: state.error,
11435
11862
  startedAt: state.startedAt
11436
11863
  };
@@ -11595,8 +12022,8 @@ async function deleteWorktree(config, moduleUid) {
11595
12022
  }
11596
12023
 
11597
12024
  // src/daemon/package-manager.ts
11598
- var fs25 = __toESM(require("fs"));
11599
- var path25 = __toESM(require("path"));
12025
+ var fs26 = __toESM(require("fs"));
12026
+ var path26 = __toESM(require("path"));
11600
12027
  function pnpmCommand(args) {
11601
12028
  return [
11602
12029
  "if command -v pnpm >/dev/null 2>&1; then",
@@ -11614,13 +12041,13 @@ var PACKAGE_MANAGERS = {
11614
12041
  {
11615
12042
  name: "pnpm",
11616
12043
  detector: (p) => {
11617
- if (fs25.existsSync(path25.join(p, "pnpm-lock.yaml"))) {
12044
+ if (fs26.existsSync(path26.join(p, "pnpm-lock.yaml"))) {
11618
12045
  return "pnpm-lock.yaml";
11619
12046
  }
11620
- const pkgJsonPath = path25.join(p, "package.json");
11621
- if (fs25.existsSync(pkgJsonPath)) {
12047
+ const pkgJsonPath = path26.join(p, "package.json");
12048
+ if (fs26.existsSync(pkgJsonPath)) {
11622
12049
  try {
11623
- const pkg = JSON.parse(fs25.readFileSync(pkgJsonPath, "utf-8"));
12050
+ const pkg = JSON.parse(fs26.readFileSync(pkgJsonPath, "utf-8"));
11624
12051
  if (pkg.packageManager?.startsWith("pnpm")) {
11625
12052
  return "package.json (packageManager field)";
11626
12053
  }
@@ -11636,7 +12063,7 @@ var PACKAGE_MANAGERS = {
11636
12063
  },
11637
12064
  {
11638
12065
  name: "yarn",
11639
- detector: (p) => fs25.existsSync(path25.join(p, "yarn.lock")) ? "yarn.lock" : null,
12066
+ detector: (p) => fs26.existsSync(path26.join(p, "yarn.lock")) ? "yarn.lock" : null,
11640
12067
  config: {
11641
12068
  installCmd: "yarn install --frozen-lockfile",
11642
12069
  cacheEnvVar: "YARN_CACHE_FOLDER"
@@ -11644,14 +12071,14 @@ var PACKAGE_MANAGERS = {
11644
12071
  },
11645
12072
  {
11646
12073
  name: "bun",
11647
- detector: (p) => fs25.existsSync(path25.join(p, "bun.lockb")) ? "bun.lockb" : null,
12074
+ detector: (p) => fs26.existsSync(path26.join(p, "bun.lockb")) ? "bun.lockb" : null,
11648
12075
  config: {
11649
12076
  installCmd: "bun install --frozen-lockfile"
11650
12077
  }
11651
12078
  },
11652
12079
  {
11653
12080
  name: "npm",
11654
- detector: (p) => fs25.existsSync(path25.join(p, "package-lock.json")) ? "package-lock.json" : null,
12081
+ detector: (p) => fs26.existsSync(path26.join(p, "package-lock.json")) ? "package-lock.json" : null,
11655
12082
  config: {
11656
12083
  installCmd: "npm ci"
11657
12084
  }
@@ -11660,7 +12087,7 @@ var PACKAGE_MANAGERS = {
11660
12087
  // EP1222: Default to pnpm for new projects without lockfile
11661
12088
  // This encourages standardization on pnpm across Episoda projects
11662
12089
  name: "pnpm",
11663
- detector: (p) => fs25.existsSync(path25.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
12090
+ detector: (p) => fs26.existsSync(path26.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
11664
12091
  config: {
11665
12092
  installCmd: pnpmCommand("install"),
11666
12093
  cacheEnvVar: "PNPM_HOME"
@@ -11671,13 +12098,13 @@ var PACKAGE_MANAGERS = {
11671
12098
  {
11672
12099
  name: "uv",
11673
12100
  detector: (p) => {
11674
- const pyprojectPath = path25.join(p, "pyproject.toml");
11675
- if (fs25.existsSync(path25.join(p, "uv.lock"))) {
12101
+ const pyprojectPath = path26.join(p, "pyproject.toml");
12102
+ if (fs26.existsSync(path26.join(p, "uv.lock"))) {
11676
12103
  return "uv.lock";
11677
12104
  }
11678
- if (fs25.existsSync(pyprojectPath)) {
12105
+ if (fs26.existsSync(pyprojectPath)) {
11679
12106
  try {
11680
- const content = fs25.readFileSync(pyprojectPath, "utf-8");
12107
+ const content = fs26.readFileSync(pyprojectPath, "utf-8");
11681
12108
  if (content.includes("[tool.uv]") || content.includes("[project]")) {
11682
12109
  return "pyproject.toml";
11683
12110
  }
@@ -11693,7 +12120,7 @@ var PACKAGE_MANAGERS = {
11693
12120
  },
11694
12121
  {
11695
12122
  name: "pip",
11696
- detector: (p) => fs25.existsSync(path25.join(p, "requirements.txt")) ? "requirements.txt" : null,
12123
+ detector: (p) => fs26.existsSync(path26.join(p, "requirements.txt")) ? "requirements.txt" : null,
11697
12124
  config: {
11698
12125
  installCmd: "pip install -r requirements.txt"
11699
12126
  }
@@ -11745,15 +12172,15 @@ function detectPackageManager(worktreePath, preferredLanguages) {
11745
12172
  }
11746
12173
 
11747
12174
  // src/daemon/build-packages.ts
11748
- var fs26 = __toESM(require("fs"));
11749
- var path26 = __toESM(require("path"));
12175
+ var fs27 = __toESM(require("fs"));
12176
+ var path27 = __toESM(require("path"));
11750
12177
  function hasPackageScript(worktreePath, scriptName) {
11751
12178
  try {
11752
- const packageJsonPath = path26.join(worktreePath, "package.json");
11753
- if (!fs26.existsSync(packageJsonPath)) {
12179
+ const packageJsonPath = path27.join(worktreePath, "package.json");
12180
+ if (!fs27.existsSync(packageJsonPath)) {
11754
12181
  return false;
11755
12182
  }
11756
- const packageJson2 = JSON.parse(fs26.readFileSync(packageJsonPath, "utf-8"));
12183
+ const packageJson2 = JSON.parse(fs27.readFileSync(packageJsonPath, "utf-8"));
11757
12184
  return typeof packageJson2.scripts?.[scriptName] === "string";
11758
12185
  } catch {
11759
12186
  return false;
@@ -11811,19 +12238,19 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
11811
12238
  if (process.env.EPISODA_MODE !== "cloud") {
11812
12239
  return;
11813
12240
  }
11814
- const homeDir = process.env.HOME || os12.homedir();
11815
- const workspaceConfigPath = path27.join(
12241
+ const homeDir = process.env.HOME || os13.homedir();
12242
+ const workspaceConfigPath = path28.join(
11816
12243
  homeDir,
11817
12244
  "episoda",
11818
12245
  workspaceSlug,
11819
12246
  ".episoda",
11820
12247
  "config.json"
11821
12248
  );
11822
- if (!fs27.existsSync(workspaceConfigPath)) {
12249
+ if (!fs28.existsSync(workspaceConfigPath)) {
11823
12250
  return;
11824
12251
  }
11825
12252
  try {
11826
- const content = fs27.readFileSync(workspaceConfigPath, "utf8");
12253
+ const content = fs28.readFileSync(workspaceConfigPath, "utf8");
11827
12254
  const workspaceConfig = JSON.parse(content);
11828
12255
  let changed = false;
11829
12256
  if (projectId && workspaceConfig.projectId !== projectId && workspaceConfig.project_id !== projectId) {
@@ -11835,7 +12262,7 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
11835
12262
  changed = true;
11836
12263
  }
11837
12264
  if (changed) {
11838
- fs27.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
12265
+ fs28.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
11839
12266
  console.log("[Worktree] Updated workspace config with project context");
11840
12267
  }
11841
12268
  } catch (error) {
@@ -11882,19 +12309,19 @@ async function handleWorktreeCreate(request2) {
11882
12309
  }
11883
12310
  try {
11884
12311
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
11885
- const bareRepoPath = path27.join(projectPath, ".bare");
12312
+ const bareRepoPath = path28.join(projectPath, ".bare");
11886
12313
  const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
11887
- if (!fs27.existsSync(bareRepoPath)) {
12314
+ if (!fs28.existsSync(bareRepoPath)) {
11888
12315
  console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
11889
12316
  console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
11890
- const episodaDir = path27.join(projectPath, ".episoda");
11891
- fs27.mkdirSync(episodaDir, { recursive: true });
12317
+ const episodaDir = path28.join(projectPath, ".episoda");
12318
+ fs28.mkdirSync(episodaDir, { recursive: true });
11892
12319
  try {
11893
12320
  console.log(`[Worktree] K1273: Starting git clone...`);
11894
12321
  await execAsync2(`git clone --bare "${repoUrl}" "${bareRepoPath}"`, { env: gitEnv });
11895
12322
  console.log(`[Worktree] K1273: Clone successful`);
11896
12323
  await execAsync2(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
11897
- const configPath = path27.join(episodaDir, "config.json");
12324
+ const configPath = path28.join(episodaDir, "config.json");
11898
12325
  const config = {
11899
12326
  projectId,
11900
12327
  workspaceSlug,
@@ -11903,7 +12330,7 @@ async function handleWorktreeCreate(request2) {
11903
12330
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11904
12331
  worktrees: []
11905
12332
  };
11906
- fs27.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
12333
+ fs28.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
11907
12334
  console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
11908
12335
  } catch (cloneError) {
11909
12336
  console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
@@ -11916,8 +12343,8 @@ async function handleWorktreeCreate(request2) {
11916
12343
  console.log(`[Worktree] EP1373: Project exists, skipping handler-level fetch (manager handles narrow fetch)`);
11917
12344
  }
11918
12345
  const manager = new WorktreeManager(projectPath);
11919
- const initialized2 = await manager.initialize();
11920
- if (!initialized2) {
12346
+ const initialized3 = await manager.initialize();
12347
+ if (!initialized3) {
11921
12348
  return {
11922
12349
  success: false,
11923
12350
  error: `Failed to initialize WorktreeManager at ${projectPath}`
@@ -11938,8 +12365,8 @@ async function handleWorktreeCreate(request2) {
11938
12365
  let finalError;
11939
12366
  if (envVars && Object.keys(envVars).length > 0) {
11940
12367
  const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
11941
- const envPath = path27.join(worktreePath, ".env");
11942
- fs27.writeFileSync(envPath, envContent + "\n", "utf-8");
12368
+ const envPath = path28.join(worktreePath, ".env");
12369
+ fs28.writeFileSync(envPath, envContent + "\n", "utf-8");
11943
12370
  console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
11944
12371
  }
11945
12372
  const isCloud = process.env.EPISODA_MODE === "cloud";
@@ -12011,8 +12438,8 @@ async function handleWorktreeRelease(request2) {
12011
12438
  try {
12012
12439
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12013
12440
  const manager = new WorktreeManager(projectPath);
12014
- const initialized2 = await manager.initialize();
12015
- if (!initialized2) {
12441
+ const initialized3 = await manager.initialize();
12442
+ if (!initialized3) {
12016
12443
  console.log(`[Worktree] EP1143: Project not initialized, nothing to release`);
12017
12444
  return { success: true };
12018
12445
  }
@@ -12065,8 +12492,8 @@ async function handleWorktreeList(workspaceSlug, projectSlug) {
12065
12492
  try {
12066
12493
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12067
12494
  const manager = new WorktreeManager(projectPath);
12068
- const initialized2 = await manager.initialize();
12069
- if (!initialized2) {
12495
+ const initialized3 = await manager.initialize();
12496
+ if (!initialized3) {
12070
12497
  return { success: true, worktrees: [] };
12071
12498
  }
12072
12499
  const worktrees = manager.listWorktrees();
@@ -12080,18 +12507,18 @@ async function handleProjectEject(request2) {
12080
12507
  console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
12081
12508
  try {
12082
12509
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12083
- const bareRepoPath = path27.join(projectPath, ".bare");
12084
- if (!fs27.existsSync(projectPath)) {
12510
+ const bareRepoPath = path28.join(projectPath, ".bare");
12511
+ if (!fs28.existsSync(projectPath)) {
12085
12512
  console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
12086
12513
  return { success: true };
12087
12514
  }
12088
- if (!fs27.existsSync(bareRepoPath)) {
12515
+ if (!fs28.existsSync(bareRepoPath)) {
12089
12516
  console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
12090
12517
  return { success: true };
12091
12518
  }
12092
12519
  const manager = new WorktreeManager(projectPath);
12093
- const initialized2 = await manager.initialize();
12094
- if (initialized2) {
12520
+ const initialized3 = await manager.initialize();
12521
+ if (initialized3) {
12095
12522
  const worktrees = manager.listWorktrees();
12096
12523
  if (worktrees.length > 0) {
12097
12524
  return {
@@ -12100,12 +12527,12 @@ async function handleProjectEject(request2) {
12100
12527
  };
12101
12528
  }
12102
12529
  }
12103
- const artifactsPath = path27.join(projectPath, "artifacts");
12104
- if (fs27.existsSync(artifactsPath)) {
12530
+ const artifactsPath = path28.join(projectPath, "artifacts");
12531
+ if (fs28.existsSync(artifactsPath)) {
12105
12532
  await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
12106
12533
  }
12107
12534
  console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
12108
- await fs27.promises.rm(projectPath, { recursive: true, force: true });
12535
+ await fs28.promises.rm(projectPath, { recursive: true, force: true });
12109
12536
  console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
12110
12537
  return { success: true };
12111
12538
  } catch (error) {
@@ -12119,7 +12546,7 @@ async function handleProjectEject(request2) {
12119
12546
  async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
12120
12547
  const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
12121
12548
  try {
12122
- const files = await fs27.promises.readdir(artifactsPath);
12549
+ const files = await fs28.promises.readdir(artifactsPath);
12123
12550
  if (files.length === 0) {
12124
12551
  return;
12125
12552
  }
@@ -12133,8 +12560,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12133
12560
  }
12134
12561
  const artifacts = [];
12135
12562
  for (const fileName of files) {
12136
- const filePath = path27.join(artifactsPath, fileName);
12137
- const stat = await fs27.promises.stat(filePath);
12563
+ const filePath = path28.join(artifactsPath, fileName);
12564
+ const stat = await fs28.promises.stat(filePath);
12138
12565
  if (stat.isDirectory()) {
12139
12566
  continue;
12140
12567
  }
@@ -12143,9 +12570,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12143
12570
  continue;
12144
12571
  }
12145
12572
  try {
12146
- const content = await fs27.promises.readFile(filePath);
12573
+ const content = await fs28.promises.readFile(filePath);
12147
12574
  const base64Content = content.toString("base64");
12148
- const ext = path27.extname(fileName).toLowerCase();
12575
+ const ext = path28.extname(fileName).toLowerCase();
12149
12576
  const mimeTypes = {
12150
12577
  ".json": "application/json",
12151
12578
  ".txt": "text/plain",
@@ -12201,8 +12628,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12201
12628
  }
12202
12629
 
12203
12630
  // src/daemon/handlers/project-handlers.ts
12204
- var path28 = __toESM(require("path"));
12205
- var fs28 = __toESM(require("fs"));
12631
+ var path29 = __toESM(require("path"));
12632
+ var fs29 = __toESM(require("fs"));
12206
12633
  function validateSlug(slug, fieldName) {
12207
12634
  if (!slug || typeof slug !== "string") {
12208
12635
  return `${fieldName} is required`;
@@ -12242,14 +12669,14 @@ async function handleProjectSetup(params) {
12242
12669
  console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
12243
12670
  try {
12244
12671
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12245
- const artifactsPath = path28.join(projectPath, "artifacts");
12246
- const configDir = path28.join(projectPath, ".episoda");
12247
- const configPath = path28.join(configDir, "config.json");
12248
- await fs28.promises.mkdir(artifactsPath, { recursive: true });
12249
- await fs28.promises.mkdir(configDir, { recursive: true });
12672
+ const artifactsPath = path29.join(projectPath, "artifacts");
12673
+ const configDir = path29.join(projectPath, ".episoda");
12674
+ const configPath = path29.join(configDir, "config.json");
12675
+ await fs29.promises.mkdir(artifactsPath, { recursive: true });
12676
+ await fs29.promises.mkdir(configDir, { recursive: true });
12250
12677
  let existingConfig = {};
12251
12678
  try {
12252
- const existing = await fs28.promises.readFile(configPath, "utf-8");
12679
+ const existing = await fs29.promises.readFile(configPath, "utf-8");
12253
12680
  existingConfig = JSON.parse(existing);
12254
12681
  } catch {
12255
12682
  }
@@ -12262,7 +12689,7 @@ async function handleProjectSetup(params) {
12262
12689
  // Only set created_at if not already present
12263
12690
  created_at: existingConfig.created_at || (/* @__PURE__ */ new Date()).toISOString()
12264
12691
  };
12265
- await fs28.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12692
+ await fs29.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12266
12693
  console.log(`[ProjectSetup] EP1199: Project setup complete at ${projectPath}`);
12267
12694
  return {
12268
12695
  success: true,
@@ -12282,8 +12709,8 @@ async function handleProjectSetup(params) {
12282
12709
  // src/utils/dev-server.ts
12283
12710
  var import_child_process15 = require("child_process");
12284
12711
  var import_core14 = __toESM(require_dist());
12285
- var fs29 = __toESM(require("fs"));
12286
- var path29 = __toESM(require("path"));
12712
+ var fs30 = __toESM(require("fs"));
12713
+ var path30 = __toESM(require("path"));
12287
12714
  var MAX_RESTART_ATTEMPTS = 5;
12288
12715
  var INITIAL_RESTART_DELAY_MS = 2e3;
12289
12716
  var MAX_RESTART_DELAY_MS = 3e4;
@@ -12291,26 +12718,26 @@ var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
12291
12718
  var NODE_MEMORY_LIMIT_MB = 2048;
12292
12719
  var activeServers = /* @__PURE__ */ new Map();
12293
12720
  function getLogsDir() {
12294
- const logsDir = path29.join((0, import_core14.getConfigDir)(), "logs");
12295
- if (!fs29.existsSync(logsDir)) {
12296
- fs29.mkdirSync(logsDir, { recursive: true });
12721
+ const logsDir = path30.join((0, import_core14.getConfigDir)(), "logs");
12722
+ if (!fs30.existsSync(logsDir)) {
12723
+ fs30.mkdirSync(logsDir, { recursive: true });
12297
12724
  }
12298
12725
  return logsDir;
12299
12726
  }
12300
12727
  function getLogFilePath(moduleUid) {
12301
- return path29.join(getLogsDir(), `dev-${moduleUid}.log`);
12728
+ return path30.join(getLogsDir(), `dev-${moduleUid}.log`);
12302
12729
  }
12303
12730
  function rotateLogIfNeeded(logPath) {
12304
12731
  try {
12305
- if (fs29.existsSync(logPath)) {
12306
- const stats = fs29.statSync(logPath);
12732
+ if (fs30.existsSync(logPath)) {
12733
+ const stats = fs30.statSync(logPath);
12307
12734
  if (stats.size > MAX_LOG_SIZE_BYTES2) {
12308
12735
  const backupPath = `${logPath}.1`;
12309
- if (fs29.existsSync(backupPath)) {
12310
- fs29.unlinkSync(backupPath);
12736
+ if (fs30.existsSync(backupPath)) {
12737
+ fs30.unlinkSync(backupPath);
12311
12738
  }
12312
- fs29.renameSync(logPath, backupPath);
12313
- console.log(`[DevServer] EP932: Rotated log file for ${path29.basename(logPath)}`);
12739
+ fs30.renameSync(logPath, backupPath);
12740
+ console.log(`[DevServer] EP932: Rotated log file for ${path30.basename(logPath)}`);
12314
12741
  }
12315
12742
  }
12316
12743
  } catch (error) {
@@ -12323,7 +12750,7 @@ function writeToLog(logPath, line, isError = false) {
12323
12750
  const prefix = isError ? "ERR" : "OUT";
12324
12751
  const logLine = `[${timestamp}] [${prefix}] ${line}
12325
12752
  `;
12326
- fs29.appendFileSync(logPath, logLine);
12753
+ fs30.appendFileSync(logPath, logLine);
12327
12754
  } catch {
12328
12755
  }
12329
12756
  }
@@ -12343,7 +12770,7 @@ async function killProcessOnPort(port) {
12343
12770
  } catch {
12344
12771
  }
12345
12772
  }
12346
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
12773
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
12347
12774
  for (const pid of pids) {
12348
12775
  try {
12349
12776
  (0, import_child_process15.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
@@ -12352,7 +12779,7 @@ async function killProcessOnPort(port) {
12352
12779
  } catch {
12353
12780
  }
12354
12781
  }
12355
- await new Promise((resolve4) => setTimeout(resolve4, 500));
12782
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
12356
12783
  const stillInUse = await isPortInUse(port);
12357
12784
  if (stillInUse) {
12358
12785
  console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
@@ -12372,7 +12799,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
12372
12799
  if (await isPortInUse(port)) {
12373
12800
  return true;
12374
12801
  }
12375
- await new Promise((resolve4) => setTimeout(resolve4, checkInterval));
12802
+ await new Promise((resolve6) => setTimeout(resolve6, checkInterval));
12376
12803
  }
12377
12804
  return false;
12378
12805
  }
@@ -12444,7 +12871,7 @@ async function handleProcessExit(moduleUid, code, signal) {
12444
12871
  const delay = calculateRestartDelay(serverInfo.restartCount);
12445
12872
  console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
12446
12873
  writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
12447
- await new Promise((resolve4) => setTimeout(resolve4, delay));
12874
+ await new Promise((resolve6) => setTimeout(resolve6, delay));
12448
12875
  if (!activeServers.has(moduleUid)) {
12449
12876
  console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
12450
12877
  return;
@@ -12502,8 +12929,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
12502
12929
  });
12503
12930
  injectedEnvVars = result.envVars;
12504
12931
  console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
12505
- const envFilePath = path29.join(projectPath, ".env");
12506
- if (!fs29.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
12932
+ const envFilePath = path30.join(projectPath, ".env");
12933
+ if (!fs30.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
12507
12934
  console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
12508
12935
  writeEnvFile(projectPath, injectedEnvVars);
12509
12936
  }
@@ -12569,7 +12996,7 @@ async function stopDevServer(moduleUid) {
12569
12996
  writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
12570
12997
  }
12571
12998
  serverInfo.process.kill("SIGTERM");
12572
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
12999
+ await new Promise((resolve6) => setTimeout(resolve6, 2e3));
12573
13000
  if (!serverInfo.process.killed) {
12574
13001
  serverInfo.process.kill("SIGKILL");
12575
13002
  }
@@ -12587,7 +13014,7 @@ async function restartDevServer(moduleUid) {
12587
13014
  writeToLog(logFile, `Manual restart requested`, false);
12588
13015
  }
12589
13016
  await stopDevServer(moduleUid);
12590
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
13017
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
12591
13018
  if (await isPortInUse(port)) {
12592
13019
  await killProcessOnPort(port);
12593
13020
  }
@@ -12641,9 +13068,9 @@ var IPCRouter = class {
12641
13068
  // EP726: Kept for backward compatibility
12642
13069
  machineName: this.host.deviceName,
12643
13070
  // EP1186: User-friendly machine name from server
12644
- hostname: os13.hostname(),
12645
- platform: os13.platform(),
12646
- arch: os13.arch(),
13071
+ hostname: os14.hostname(),
13072
+ platform: os14.platform(),
13073
+ arch: os14.arch(),
12647
13074
  projects
12648
13075
  };
12649
13076
  });
@@ -12669,7 +13096,7 @@ var IPCRouter = class {
12669
13096
  if (attempt < MAX_RETRIES) {
12670
13097
  const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
12671
13098
  console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
12672
- await new Promise((resolve4) => setTimeout(resolve4, delay));
13099
+ await new Promise((resolve6) => setTimeout(resolve6, delay));
12673
13100
  await this.host.disconnectProject(projectPath);
12674
13101
  }
12675
13102
  }
@@ -12807,6 +13234,7 @@ var IPCRouter = class {
12807
13234
  await tunnelManager.stopTunnel(moduleUid);
12808
13235
  await stopDevServer(moduleUid);
12809
13236
  releasePort(moduleUid);
13237
+ releaseWsPort(moduleUid);
12810
13238
  await clearTunnelUrl(moduleUid);
12811
13239
  this.host.deleteTunnelHealthFailure(moduleUid);
12812
13240
  console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`);
@@ -12844,8 +13272,8 @@ var IPCRouter = class {
12844
13272
  };
12845
13273
 
12846
13274
  // src/daemon/update-manager.ts
12847
- var fs30 = __toESM(require("fs"));
12848
- var path30 = __toESM(require("path"));
13275
+ var fs31 = __toESM(require("fs"));
13276
+ var path31 = __toESM(require("path"));
12849
13277
  var import_child_process17 = require("child_process");
12850
13278
  var import_core17 = __toESM(require_dist());
12851
13279
  var semver2 = __toESM(require("semver"));
@@ -13103,8 +13531,8 @@ var UpdateManager = class _UpdateManager {
13103
13531
  console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
13104
13532
  await this.host.shutdown();
13105
13533
  const configDir = (0, import_core17.getConfigDir)();
13106
- const logPath = path30.join(configDir, "daemon.log");
13107
- const logFd = fs30.openSync(logPath, "a");
13534
+ const logPath = path31.join(configDir, "daemon.log");
13535
+ const logFd = fs31.openSync(logPath, "a");
13108
13536
  const child = (0, import_child_process17.spawn)("node", [this.daemonEntryFile], {
13109
13537
  detached: true,
13110
13538
  stdio: ["ignore", logFd, logFd],
@@ -13112,7 +13540,7 @@ var UpdateManager = class _UpdateManager {
13112
13540
  });
13113
13541
  if (!child.pid) {
13114
13542
  try {
13115
- fs30.closeSync(logFd);
13543
+ fs31.closeSync(logFd);
13116
13544
  } catch {
13117
13545
  }
13118
13546
  this.recordUpdateFailure(targetVersion, "Failed to spawn replacement daemon (missing pid)");
@@ -13120,7 +13548,7 @@ var UpdateManager = class _UpdateManager {
13120
13548
  }
13121
13549
  child.unref();
13122
13550
  const pidPath = getPidFilePath();
13123
- fs30.writeFileSync(pidPath, child.pid.toString(), "utf-8");
13551
+ fs31.writeFileSync(pidPath, child.pid.toString(), "utf-8");
13124
13552
  console.log(`[Daemon] EP1324: New daemon spawned (PID: ${child.pid}), exiting old process`);
13125
13553
  process.exit(0);
13126
13554
  } catch (error) {
@@ -13171,8 +13599,8 @@ var UpdateManager = class _UpdateManager {
13171
13599
  };
13172
13600
 
13173
13601
  // src/daemon/health-orchestrator.ts
13174
- var fs31 = __toESM(require("fs"));
13175
- var path31 = __toESM(require("path"));
13602
+ var fs32 = __toESM(require("fs"));
13603
+ var path32 = __toESM(require("path"));
13176
13604
  var import_core18 = __toESM(require_dist());
13177
13605
  var HealthOrchestrator = class _HealthOrchestrator {
13178
13606
  constructor(host) {
@@ -13376,10 +13804,15 @@ var HealthOrchestrator = class _HealthOrchestrator {
13376
13804
  console.log(`[Daemon] EP1042: Killed ${cleanup.cleaned} orphaned dev server(s)`);
13377
13805
  }
13378
13806
  const registryPorts = /* @__PURE__ */ new Map();
13807
+ const registryWsPorts = /* @__PURE__ */ new Map();
13379
13808
  for (const entry of registry.getAll()) {
13380
13809
  registryPorts.set(entry.moduleUid, entry.port);
13810
+ if (entry.wsPort) {
13811
+ registryWsPorts.set(entry.moduleUid, entry.wsPort);
13812
+ }
13381
13813
  }
13382
13814
  reconcileWithRegistry(registryPorts);
13815
+ reconcileWsPortsWithRegistry(registryWsPorts);
13383
13816
  console.log("[Daemon] EP1042: Orphaned dev server cleanup complete");
13384
13817
  } catch (error) {
13385
13818
  console.error("[Daemon] EP1042: Failed to clean up orphaned dev servers:", error);
@@ -13421,26 +13854,26 @@ var HealthOrchestrator = class _HealthOrchestrator {
13421
13854
  checkAndRotateLog() {
13422
13855
  try {
13423
13856
  const configDir = (0, import_core18.getConfigDir)();
13424
- const logPath = path31.join(configDir, "daemon.log");
13425
- if (!fs31.existsSync(logPath)) return;
13426
- const stats = fs31.statSync(logPath);
13857
+ const logPath = path32.join(configDir, "daemon.log");
13858
+ if (!fs32.existsSync(logPath)) return;
13859
+ const stats = fs32.statSync(logPath);
13427
13860
  if (stats.size < _HealthOrchestrator.MAX_LOG_SIZE_BYTES) return;
13428
13861
  console.log(`[Daemon] EP1351: daemon.log is ${Math.round(stats.size / 1024 / 1024)}MB, rotating...`);
13429
13862
  for (let i = _HealthOrchestrator.MAX_LOG_FILES - 1; i >= 1; i--) {
13430
13863
  const src = `${logPath}.${i}`;
13431
13864
  const dst = `${logPath}.${i + 1}`;
13432
- if (fs31.existsSync(src)) {
13865
+ if (fs32.existsSync(src)) {
13433
13866
  try {
13434
- fs31.renameSync(src, dst);
13867
+ fs32.renameSync(src, dst);
13435
13868
  } catch {
13436
13869
  }
13437
13870
  }
13438
13871
  }
13439
13872
  try {
13440
- fs31.copyFileSync(logPath, `${logPath}.1`);
13873
+ fs32.copyFileSync(logPath, `${logPath}.1`);
13441
13874
  } catch {
13442
13875
  }
13443
- fs31.truncateSync(logPath, 0);
13876
+ fs32.truncateSync(logPath, 0);
13444
13877
  console.log("[Daemon] EP1351: Log rotated successfully");
13445
13878
  } catch (error) {
13446
13879
  console.warn("[Daemon] EP1351: Log rotation failed:", error instanceof Error ? error.message : error);
@@ -13489,9 +13922,9 @@ var HealthOrchestrator = class _HealthOrchestrator {
13489
13922
  if (Number.isFinite(lastAccessedMs)) {
13490
13923
  referenceTimestampMs = lastAccessedMs;
13491
13924
  fallbackAgeSource.push(`${w.moduleUid}(lastAccessed)`);
13492
- } else if (w.worktreePath && fs31.existsSync(w.worktreePath)) {
13925
+ } else if (w.worktreePath && fs32.existsSync(w.worktreePath)) {
13493
13926
  try {
13494
- const stats = fs31.statSync(w.worktreePath);
13927
+ const stats = fs32.statSync(w.worktreePath);
13495
13928
  referenceTimestampMs = stats.mtimeMs;
13496
13929
  fallbackAgeSource.push(`${w.moduleUid}(fs.mtime)`);
13497
13930
  } catch {
@@ -13857,7 +14290,7 @@ var ConnectionManager = class {
13857
14290
  * handlers are removed deterministically on every exit path.
13858
14291
  */
13859
14292
  async waitForAuthentication(client, timeoutMs = 3e4) {
13860
- await new Promise((resolve4, reject) => {
14293
+ await new Promise((resolve6, reject) => {
13861
14294
  let settled = false;
13862
14295
  const cleanup = () => {
13863
14296
  clearTimeout(timeout);
@@ -13874,7 +14307,7 @@ var ConnectionManager = class {
13874
14307
  if (settled) return;
13875
14308
  settled = true;
13876
14309
  cleanup();
13877
- resolve4();
14310
+ resolve6();
13878
14311
  };
13879
14312
  const errorHandler = (message) => {
13880
14313
  if (settled) return;
@@ -13937,7 +14370,7 @@ function resolveDaemonWsEndpoint(config, env = process.env) {
13937
14370
 
13938
14371
  // src/daemon/project-message-router.ts
13939
14372
  var import_core19 = __toESM(require_dist());
13940
- var path32 = __toESM(require("path"));
14373
+ var path33 = __toESM(require("path"));
13941
14374
  var ProjectMessageRouter = class {
13942
14375
  constructor(host) {
13943
14376
  this.host = host;
@@ -13954,7 +14387,7 @@ var ProjectMessageRouter = class {
13954
14387
  client.updateActivity();
13955
14388
  try {
13956
14389
  const gitCmd = message.command;
13957
- const bareRepoPath = path32.join(projectPath, ".bare");
14390
+ const bareRepoPath = path33.join(projectPath, ".bare");
13958
14391
  const cwd = gitCmd.worktreePath || bareRepoPath;
13959
14392
  if (gitCmd.worktreePath) {
13960
14393
  console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
@@ -14294,32 +14727,32 @@ async function fetchEnvVars2(projectId) {
14294
14727
  }
14295
14728
 
14296
14729
  // src/daemon/project-git-config.ts
14297
- var fs32 = __toESM(require("fs"));
14298
- var path33 = __toESM(require("path"));
14730
+ var fs33 = __toESM(require("fs"));
14731
+ var path34 = __toESM(require("path"));
14299
14732
  var import_core21 = __toESM(require_dist());
14300
14733
  function getGitDirs(projectPath) {
14301
- const bareDir = path33.join(projectPath, ".bare");
14302
- const gitPath = path33.join(projectPath, ".git");
14303
- if (fs32.existsSync(bareDir) && fs32.statSync(bareDir).isDirectory()) {
14734
+ const bareDir = path34.join(projectPath, ".bare");
14735
+ const gitPath = path34.join(projectPath, ".git");
14736
+ if (fs33.existsSync(bareDir) && fs33.statSync(bareDir).isDirectory()) {
14304
14737
  return { gitDir: bareDir, workDir: projectPath };
14305
14738
  }
14306
- if (fs32.existsSync(gitPath) && fs32.statSync(gitPath).isDirectory()) {
14739
+ if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isDirectory()) {
14307
14740
  return { gitDir: null, workDir: projectPath };
14308
14741
  }
14309
- if (fs32.existsSync(gitPath) && fs32.statSync(gitPath).isFile()) {
14742
+ if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isFile()) {
14310
14743
  return { gitDir: null, workDir: projectPath };
14311
14744
  }
14312
- const entries = fs32.readdirSync(projectPath, { withFileTypes: true });
14745
+ const entries = fs33.readdirSync(projectPath, { withFileTypes: true });
14313
14746
  for (const entry of entries) {
14314
14747
  if (entry.isDirectory() && entry.name.startsWith("EP")) {
14315
- const worktreePath = path33.join(projectPath, entry.name);
14316
- const worktreeGit = path33.join(worktreePath, ".git");
14317
- if (fs32.existsSync(worktreeGit)) {
14748
+ const worktreePath = path34.join(projectPath, entry.name);
14749
+ const worktreeGit = path34.join(worktreePath, ".git");
14750
+ if (fs33.existsSync(worktreeGit)) {
14318
14751
  return { gitDir: null, workDir: worktreePath };
14319
14752
  }
14320
14753
  }
14321
14754
  }
14322
- if (fs32.existsSync(bareDir)) {
14755
+ if (fs33.existsSync(bareDir)) {
14323
14756
  return { gitDir: bareDir, workDir: projectPath };
14324
14757
  }
14325
14758
  return { gitDir: null, workDir: projectPath };
@@ -14364,24 +14797,24 @@ async function configureGitUser(projectPath, userId, workspaceId, machineId, pro
14364
14797
  async function installGitHooks(projectPath) {
14365
14798
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
14366
14799
  let hooksDir;
14367
- const bareHooksDir = path33.join(projectPath, ".bare", "hooks");
14368
- const gitHooksDir = path33.join(projectPath, ".git", "hooks");
14369
- if (fs32.existsSync(bareHooksDir)) {
14800
+ const bareHooksDir = path34.join(projectPath, ".bare", "hooks");
14801
+ const gitHooksDir = path34.join(projectPath, ".git", "hooks");
14802
+ if (fs33.existsSync(bareHooksDir)) {
14370
14803
  hooksDir = bareHooksDir;
14371
- } else if (fs32.existsSync(gitHooksDir) && fs32.statSync(path33.join(projectPath, ".git")).isDirectory()) {
14804
+ } else if (fs33.existsSync(gitHooksDir) && fs33.statSync(path34.join(projectPath, ".git")).isDirectory()) {
14372
14805
  hooksDir = gitHooksDir;
14373
14806
  } else {
14374
- const parentBareHooks = path33.join(projectPath, "..", ".bare", "hooks");
14375
- if (fs32.existsSync(parentBareHooks)) {
14807
+ const parentBareHooks = path34.join(projectPath, "..", ".bare", "hooks");
14808
+ if (fs33.existsSync(parentBareHooks)) {
14376
14809
  hooksDir = parentBareHooks;
14377
14810
  } else {
14378
14811
  console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
14379
14812
  return;
14380
14813
  }
14381
14814
  }
14382
- if (!fs32.existsSync(hooksDir)) {
14815
+ if (!fs33.existsSync(hooksDir)) {
14383
14816
  try {
14384
- fs32.mkdirSync(hooksDir, { recursive: true });
14817
+ fs33.mkdirSync(hooksDir, { recursive: true });
14385
14818
  } catch {
14386
14819
  console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
14387
14820
  return;
@@ -14389,20 +14822,20 @@ async function installGitHooks(projectPath) {
14389
14822
  }
14390
14823
  for (const hookName of hooks) {
14391
14824
  try {
14392
- const hookPath = path33.join(hooksDir, hookName);
14393
- const bundledHookPath = path33.join(__dirname, "..", "hooks", hookName);
14394
- if (!fs32.existsSync(bundledHookPath)) {
14825
+ const hookPath = path34.join(hooksDir, hookName);
14826
+ const bundledHookPath = path34.join(__dirname, "..", "hooks", hookName);
14827
+ if (!fs33.existsSync(bundledHookPath)) {
14395
14828
  console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
14396
14829
  continue;
14397
14830
  }
14398
- const hookContent = fs32.readFileSync(bundledHookPath, "utf-8");
14399
- if (fs32.existsSync(hookPath)) {
14400
- const existingContent = fs32.readFileSync(hookPath, "utf-8");
14831
+ const hookContent = fs33.readFileSync(bundledHookPath, "utf-8");
14832
+ if (fs33.existsSync(hookPath)) {
14833
+ const existingContent = fs33.readFileSync(hookPath, "utf-8");
14401
14834
  if (existingContent === hookContent) {
14402
14835
  continue;
14403
14836
  }
14404
14837
  }
14405
- fs32.writeFileSync(hookPath, hookContent, { mode: 493 });
14838
+ fs33.writeFileSync(hookPath, hookContent, { mode: 493 });
14406
14839
  console.log(`[Daemon] Installed git hook: ${hookName}`);
14407
14840
  } catch (error) {
14408
14841
  console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
@@ -14545,9 +14978,9 @@ var Daemon = class _Daemon {
14545
14978
  this.healthServer = http2.createServer((req, res) => {
14546
14979
  if (req.url === "/health" || req.url === "/") {
14547
14980
  const isConnected = this.connectionManager.liveConnectionCount() > 0;
14548
- const projects = Array.from(this.connectionManager.entries()).map(([path34, conn]) => ({
14549
- path: path34,
14550
- connected: this.connectionManager.hasLiveConnection(path34)
14981
+ const projects = Array.from(this.connectionManager.entries()).map(([path35, conn]) => ({
14982
+ path: path35,
14983
+ connected: this.connectionManager.hasLiveConnection(path35)
14551
14984
  }));
14552
14985
  const status = {
14553
14986
  status: isConnected ? "healthy" : "degraded",
@@ -14620,8 +15053,8 @@ var Daemon = class _Daemon {
14620
15053
  await this.shutdown();
14621
15054
  try {
14622
15055
  const pidPath = getPidFilePath();
14623
- if (fs33.existsSync(pidPath)) {
14624
- fs33.unlinkSync(pidPath);
15056
+ if (fs34.existsSync(pidPath)) {
15057
+ fs34.unlinkSync(pidPath);
14625
15058
  console.log("[Daemon] PID file cleaned up (watchdog)");
14626
15059
  }
14627
15060
  } catch (error) {
@@ -14684,8 +15117,8 @@ var Daemon = class _Daemon {
14684
15117
  }
14685
15118
  }
14686
15119
  let releaseLock;
14687
- const lockPromise = new Promise((resolve4) => {
14688
- releaseLock = resolve4;
15120
+ const lockPromise = new Promise((resolve6) => {
15121
+ releaseLock = resolve6;
14689
15122
  });
14690
15123
  this.tunnelOperationLocks.set(moduleUid, lockPromise);
14691
15124
  try {
@@ -14712,7 +15145,7 @@ var Daemon = class _Daemon {
14712
15145
  const maxWait = 35e3;
14713
15146
  const startTime = Date.now();
14714
15147
  while (this.connectionManager.hasPendingConnection(projectPath) && Date.now() - startTime < maxWait) {
14715
- await new Promise((resolve4) => setTimeout(resolve4, 500));
15148
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
14716
15149
  }
14717
15150
  if (this.connectionManager.hasLiveConnection(projectPath)) {
14718
15151
  console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
@@ -15011,8 +15444,8 @@ var Daemon = class _Daemon {
15011
15444
  };
15012
15445
  } else {
15013
15446
  const manager = new WorktreeManager(projectRootPath);
15014
- const initialized2 = await manager.initialize();
15015
- if (!initialized2) {
15447
+ const initialized3 = await manager.initialize();
15448
+ if (!initialized3) {
15016
15449
  console.warn(`[Daemon] EP1035: Failed to initialize WorktreeManager for ${projectRootPath}`);
15017
15450
  result = {
15018
15451
  success: false,
@@ -15210,8 +15643,8 @@ var Daemon = class _Daemon {
15210
15643
  let daemonPid;
15211
15644
  try {
15212
15645
  const pidPath = getPidFilePath();
15213
- if (fs33.existsSync(pidPath)) {
15214
- const pidStr = fs33.readFileSync(pidPath, "utf-8").trim();
15646
+ if (fs34.existsSync(pidPath)) {
15647
+ const pidStr = fs34.readFileSync(pidPath, "utf-8").trim();
15215
15648
  daemonPid = parseInt(pidStr, 10);
15216
15649
  }
15217
15650
  } catch (pidError) {
@@ -15222,9 +15655,9 @@ var Daemon = class _Daemon {
15222
15655
  const containerId = process.env.EPISODA_CONTAINER_ID;
15223
15656
  const capabilities = ["worktree_create_v1"];
15224
15657
  await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, {
15225
- hostname: os14.hostname(),
15226
- osPlatform: os14.platform(),
15227
- osArch: os14.arch(),
15658
+ hostname: os15.hostname(),
15659
+ osPlatform: os15.platform(),
15660
+ osArch: os15.arch(),
15228
15661
  daemonPid,
15229
15662
  cliVersion: this.cliRuntimeVersion,
15230
15663
  cliPackageName: packageJson.name,
@@ -15763,6 +16196,7 @@ var Daemon = class _Daemon {
15763
16196
  console.error("[Daemon] Failed to stop tunnels:", error);
15764
16197
  }
15765
16198
  clearAllPorts();
16199
+ clearAllWsPorts();
15766
16200
  if (this.disconnectWatchdogTimer) {
15767
16201
  clearInterval(this.disconnectWatchdogTimer);
15768
16202
  this.disconnectWatchdogTimer = null;
@@ -15785,8 +16219,8 @@ var Daemon = class _Daemon {
15785
16219
  await this.shutdown();
15786
16220
  try {
15787
16221
  const pidPath = getPidFilePath();
15788
- if (fs33.existsSync(pidPath)) {
15789
- fs33.unlinkSync(pidPath);
16222
+ if (fs34.existsSync(pidPath)) {
16223
+ fs34.unlinkSync(pidPath);
15790
16224
  console.log("[Daemon] PID file cleaned up");
15791
16225
  }
15792
16226
  } catch (error) {