@episoda/cli 0.2.171 → 0.2.172

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.172",
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,25 @@ 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) {
10741
- console.log(`[DevServerRunner] EP1042: Correct server already running on port ${port} for ${moduleUid}`);
10742
- return { success: true, alreadyRunning: true };
10931
+ const wsHealthy = await this.checkWsHealth(wsPort);
10932
+ if (wsHealthy) {
10933
+ console.log(`[DevServerRunner] EP1042: Correct server already running on port ${port} for ${moduleUid}`);
10934
+ return { success: true, alreadyRunning: true };
10935
+ }
10936
+ console.warn(
10937
+ `[DevServerRunner] EP1406: Existing dev server for ${moduleUid} has unhealthy ws-server on port ${wsPort}, restarting`
10938
+ );
10939
+ await this.killProcessOnPort(port);
10940
+ await this.cleanupWsServerOnPort(wsPort, projectPath);
10941
+ registry.unregister(existingEntry.moduleUid);
10942
+ this.servers.delete(existingEntry.moduleUid);
10943
+ }
10944
+ if (existingEntry.worktreePath !== projectPath || existingEntry.moduleUid !== moduleUid) {
10945
+ console.log(`[DevServerRunner] EP1042: Port ${port} owned by ${existingEntry.moduleUid} (${existingEntry.worktreePath}), killing...`);
10946
+ await this.killProcessOnPort(port);
10947
+ registry.unregister(existingEntry.moduleUid);
10948
+ this.servers.delete(existingEntry.moduleUid);
10743
10949
  }
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);
10748
10950
  } else {
10749
10951
  console.log(`[DevServerRunner] EP1042: Unknown process on port ${port}, killing...`);
10750
10952
  await this.killProcessOnPort(port);
@@ -10754,21 +10956,34 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10754
10956
  return { success: false, error: `Port ${port} still in use after cleanup` };
10755
10957
  }
10756
10958
  }
10959
+ const wsPortAvailability = await this.ensureWsPortAvailable(projectPath, wsPort);
10960
+ if (wsPortAvailability) {
10961
+ return wsPortAvailability;
10962
+ }
10757
10963
  const existing = this.servers.get(moduleUid);
10758
10964
  if (existing && !existing.process.killed) {
10759
- console.log(`[DevServerRunner] Process already exists for ${moduleUid}`);
10760
- return { success: true, alreadyRunning: true };
10965
+ const wsHealthy = await this.checkWsHealth(existing.wsPort);
10966
+ if (wsHealthy) {
10967
+ console.log(`[DevServerRunner] Process already exists for ${moduleUid}`);
10968
+ return { success: true, alreadyRunning: true };
10969
+ }
10970
+ console.warn(
10971
+ `[DevServerRunner] EP1406: In-memory process exists for ${moduleUid} but ws-server is unhealthy on ${existing.wsPort}, restarting`
10972
+ );
10973
+ await this.stop(moduleUid);
10974
+ await this.wait(500);
10761
10975
  }
10762
- console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port}...`);
10976
+ console.log(`[DevServerRunner] Starting dev server for ${moduleUid} on port ${port} (ws:${wsPort})...`);
10763
10977
  const injectedEnvVars = await this.fetchEnvVars(projectPath);
10764
10978
  try {
10765
10979
  const logPath = this.getLogFilePath(moduleUid);
10766
- const process2 = this.spawnProcess(projectPath, port, moduleUid, logPath, customCommand, injectedEnvVars);
10980
+ const process2 = this.spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, injectedEnvVars);
10767
10981
  const state = {
10768
10982
  process: process2,
10769
10983
  moduleUid,
10770
10984
  projectPath,
10771
10985
  port,
10986
+ wsPort,
10772
10987
  startedAt: /* @__PURE__ */ new Date(),
10773
10988
  restartCount: 0,
10774
10989
  lastRestartAt: null,
@@ -10788,16 +11003,26 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10788
11003
  this.writeToLog(logPath, "Failed to start within timeout", true);
10789
11004
  return { success: false, error: "Dev server failed to start within timeout" };
10790
11005
  }
11006
+ console.log(`[DevServerRunner] Waiting for ws-server on port ${wsPort}...`);
11007
+ const wsReady = await this.waitForWsPort(wsPort, DEV_SERVER_CONSTANTS.STARTUP_TIMEOUT_MS);
11008
+ if (!wsReady) {
11009
+ process2.kill();
11010
+ this.servers.delete(moduleUid);
11011
+ const wsStartError = this.detectWsStartupError(logPath, wsPort);
11012
+ this.writeToLog(logPath, wsStartError, true);
11013
+ return { success: false, error: wsStartError };
11014
+ }
10791
11015
  if (process2.pid) {
10792
11016
  registry.register({
10793
11017
  pid: process2.pid,
10794
11018
  port,
11019
+ wsPort,
10795
11020
  worktreePath: projectPath,
10796
11021
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
10797
11022
  moduleUid
10798
11023
  });
10799
11024
  }
10800
- console.log(`[DevServerRunner] Server started successfully on port ${port}`);
11025
+ console.log(`[DevServerRunner] Server started successfully on ports app:${port} ws:${wsPort}`);
10801
11026
  this.writeToLog(logPath, "Server started successfully");
10802
11027
  this.emit("started", moduleUid, port);
10803
11028
  return { success: true };
@@ -10830,6 +11055,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10830
11055
  state.process.kill("SIGKILL");
10831
11056
  }
10832
11057
  }
11058
+ await this.cleanupWsServerOnPort(state.wsPort, state.projectPath);
10833
11059
  } else if (registryEntry) {
10834
11060
  console.log(`[DevServerRunner] EP1042: Stopping orphaned server for ${moduleUid} (PID ${registryEntry.pid})`);
10835
11061
  if (registry.isProcessRunning(registryEntry.pid)) {
@@ -10839,6 +11065,9 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10839
11065
  registry.killProcess(registryEntry.pid, "SIGKILL");
10840
11066
  }
10841
11067
  }
11068
+ if (registryEntry.wsPort) {
11069
+ await this.cleanupWsServerOnPort(registryEntry.wsPort, registryEntry.worktreePath);
11070
+ }
10842
11071
  }
10843
11072
  this.servers.delete(moduleUid);
10844
11073
  registry.unregister(moduleUid);
@@ -10852,7 +11081,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10852
11081
  if (!state) {
10853
11082
  return { success: false, error: `No dev server found for ${moduleUid}` };
10854
11083
  }
10855
- const { projectPath, port, autoRestartEnabled, customCommand, logFile } = state;
11084
+ const { projectPath, port, wsPort, autoRestartEnabled, customCommand, logFile } = state;
10856
11085
  console.log(`[DevServerRunner] Restarting server for ${moduleUid}...`);
10857
11086
  this.writeToLog(logFile, "Manual restart requested");
10858
11087
  await this.stop(moduleUid);
@@ -10863,6 +11092,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10863
11092
  return this.start({
10864
11093
  projectPath,
10865
11094
  port,
11095
+ wsPort,
10866
11096
  moduleUid,
10867
11097
  customCommand,
10868
11098
  autoRestart: autoRestartEnabled
@@ -10878,6 +11108,16 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10878
11108
  }
10879
11109
  return this.checkHealth(state.port);
10880
11110
  }
11111
+ /**
11112
+ * Check if ws-server for a module is healthy
11113
+ */
11114
+ async isWsHealthy(moduleUid) {
11115
+ const state = this.servers.get(moduleUid);
11116
+ if (!state) {
11117
+ return false;
11118
+ }
11119
+ return this.checkWsHealth(state.wsPort);
11120
+ }
10881
11121
  /**
10882
11122
  * Check if a dev server is running
10883
11123
  */
@@ -10949,6 +11189,125 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10949
11189
  return false;
10950
11190
  }
10951
11191
  }
11192
+ async ensureWsPortAvailable(projectPath, wsPort) {
11193
+ if (!await isPortInUse(wsPort)) {
11194
+ return null;
11195
+ }
11196
+ const pids = this.getPidsOnPort(wsPort);
11197
+ if (pids.length === 0) {
11198
+ return { success: false, error: `WS_BIND_CONFLICT: WS port ${wsPort} is already in use.` };
11199
+ }
11200
+ const registry = getDevServerRegistry();
11201
+ const expectedPath = path25.resolve(projectPath);
11202
+ const unsafe = [];
11203
+ const killable = [];
11204
+ for (const pid of pids) {
11205
+ const command = this.getProcessCommand(pid);
11206
+ const cwd = registry.getProcessCwd(pid);
11207
+ const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11208
+ if (this.isLikelyWsServerProcess(command) && cwdMatches) {
11209
+ killable.push(pid);
11210
+ } else {
11211
+ unsafe.push({ pid, command });
11212
+ }
11213
+ }
11214
+ if (unsafe.length > 0) {
11215
+ const details = unsafe.map((item) => `PID ${item.pid} (${item.command})`).join(", ");
11216
+ return {
11217
+ success: false,
11218
+ error: `WS_BIND_CONFLICT: WS port ${wsPort} is in use by another process: ${details}`
11219
+ };
11220
+ }
11221
+ for (const pid of killable) {
11222
+ try {
11223
+ process.kill(pid, "SIGTERM");
11224
+ } catch {
11225
+ }
11226
+ }
11227
+ await this.wait(800);
11228
+ for (const pid of killable) {
11229
+ try {
11230
+ process.kill(pid, 0);
11231
+ process.kill(pid, "SIGKILL");
11232
+ } catch {
11233
+ }
11234
+ }
11235
+ await this.wait(400);
11236
+ if (await isPortInUse(wsPort)) {
11237
+ return {
11238
+ success: false,
11239
+ error: `WS_BIND_CONFLICT: WS port ${wsPort} remained in use after stale ws-server cleanup`
11240
+ };
11241
+ }
11242
+ return null;
11243
+ }
11244
+ async cleanupWsServerOnPort(wsPort, worktreePath) {
11245
+ if (!wsPort || !await isPortInUse(wsPort)) return;
11246
+ const registry = getDevServerRegistry();
11247
+ const expectedPath = path25.resolve(worktreePath);
11248
+ const pids = this.getPidsOnPort(wsPort);
11249
+ for (const pid of pids) {
11250
+ const command = this.getProcessCommand(pid);
11251
+ const cwd = registry.getProcessCwd(pid);
11252
+ const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11253
+ if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
11254
+ continue;
11255
+ }
11256
+ try {
11257
+ process.kill(pid, "SIGTERM");
11258
+ } catch {
11259
+ }
11260
+ }
11261
+ await this.wait(500);
11262
+ for (const pid of pids) {
11263
+ const command = this.getProcessCommand(pid);
11264
+ const cwd = registry.getProcessCwd(pid);
11265
+ const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11266
+ if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
11267
+ continue;
11268
+ }
11269
+ try {
11270
+ process.kill(pid, 0);
11271
+ process.kill(pid, "SIGKILL");
11272
+ } catch {
11273
+ }
11274
+ }
11275
+ }
11276
+ getPidsOnPort(port) {
11277
+ try {
11278
+ const output = (0, import_child_process13.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
11279
+ if (!output) return [];
11280
+ return output.split("\n").map((value) => parseInt(value, 10)).filter((value) => !isNaN(value) && value > 0);
11281
+ } catch {
11282
+ return [];
11283
+ }
11284
+ }
11285
+ getProcessCommand(pid) {
11286
+ try {
11287
+ return (0, import_child_process13.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim() || "unknown";
11288
+ } catch {
11289
+ return "unknown";
11290
+ }
11291
+ }
11292
+ isLikelyWsServerProcess(command) {
11293
+ return command.includes("ws-server.js") || command.includes("build:ws-server");
11294
+ }
11295
+ detectWsStartupError(logPath, wsPort) {
11296
+ const fallback = `WS_BIND_CONFLICT: ws-server failed to become healthy on WS port ${wsPort}`;
11297
+ if (!logPath) return fallback;
11298
+ try {
11299
+ if (!fs25.existsSync(logPath)) return fallback;
11300
+ const content = fs25.readFileSync(logPath, "utf8");
11301
+ if (content.includes("EADDRINUSE") && content.includes(`${wsPort}`)) {
11302
+ return `WS_BIND_CONFLICT: ws-server could not bind to WS port ${wsPort} (EADDRINUSE)`;
11303
+ }
11304
+ if (content.includes("Failed to initialize")) {
11305
+ return `WS_SERVER_START_FAILED: ws-server failed during startup on WS port ${wsPort}`;
11306
+ }
11307
+ } catch {
11308
+ }
11309
+ return fallback;
11310
+ }
10952
11311
  // ============ Private Methods ============
10953
11312
  async fetchEnvVars(projectPath) {
10954
11313
  try {
@@ -10962,8 +11321,8 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10962
11321
  cacheTtl: 300
10963
11322
  });
10964
11323
  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) {
11324
+ const envFilePath = path25.join(projectPath, ".env");
11325
+ if (!fs25.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
10967
11326
  console.log(`[DevServerRunner] Writing .env file`);
10968
11327
  writeEnvFile(projectPath, result.envVars);
10969
11328
  }
@@ -10973,7 +11332,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10973
11332
  return {};
10974
11333
  }
10975
11334
  }
10976
- spawnProcess(projectPath, port, moduleUid, logPath, customCommand, envVars) {
11335
+ spawnProcess(projectPath, port, wsPort, moduleUid, logPath, customCommand, envVars) {
10977
11336
  this.rotateLogIfNeeded(logPath);
10978
11337
  const nodeOptions = process.env.NODE_OPTIONS || "";
10979
11338
  const memoryFlag = `--max-old-space-size=${DEV_SERVER_CONSTANTS.NODE_MEMORY_LIMIT_MB}`;
@@ -10985,6 +11344,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
10985
11344
  ...process.env,
10986
11345
  ...envVars,
10987
11346
  PORT: String(port),
11347
+ WS_PORT: String(wsPort),
10988
11348
  NODE_OPTIONS: enhancedNodeOptions
10989
11349
  };
10990
11350
  const proc = (0, import_child_process13.spawn)(cmd, args, {
@@ -11048,6 +11408,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11048
11408
  const newProcess = this.spawnProcess(
11049
11409
  state.projectPath,
11050
11410
  state.port,
11411
+ state.wsPort,
11051
11412
  moduleUid,
11052
11413
  logPath,
11053
11414
  state.customCommand,
@@ -11078,7 +11439,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11078
11439
  return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
11079
11440
  }
11080
11441
  async checkHealth(port) {
11081
- return new Promise((resolve4) => {
11442
+ return new Promise((resolve6) => {
11082
11443
  const req = http.request(
11083
11444
  {
11084
11445
  hostname: "localhost",
@@ -11087,12 +11448,35 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11087
11448
  method: "HEAD",
11088
11449
  timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
11089
11450
  },
11090
- () => resolve4(true)
11451
+ () => resolve6(true)
11452
+ );
11453
+ req.on("error", () => resolve6(false));
11454
+ req.on("timeout", () => {
11455
+ req.destroy();
11456
+ resolve6(false);
11457
+ });
11458
+ req.end();
11459
+ });
11460
+ }
11461
+ async checkWsHealth(wsPort) {
11462
+ return new Promise((resolve6) => {
11463
+ const req = http.request(
11464
+ {
11465
+ hostname: "127.0.0.1",
11466
+ port: wsPort,
11467
+ path: "/health",
11468
+ method: "GET",
11469
+ timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
11470
+ },
11471
+ (res) => {
11472
+ resolve6(res.statusCode === 200);
11473
+ res.resume();
11474
+ }
11091
11475
  );
11092
- req.on("error", () => resolve4(false));
11476
+ req.on("error", () => resolve6(false));
11093
11477
  req.on("timeout", () => {
11094
11478
  req.destroy();
11095
- resolve4(false);
11479
+ resolve6(false);
11096
11480
  });
11097
11481
  req.end();
11098
11482
  });
@@ -11108,29 +11492,40 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11108
11492
  }
11109
11493
  return false;
11110
11494
  }
11495
+ async waitForWsPort(wsPort, timeoutMs) {
11496
+ const startTime = Date.now();
11497
+ const checkInterval = 500;
11498
+ while (Date.now() - startTime < timeoutMs) {
11499
+ if (await this.checkWsHealth(wsPort)) {
11500
+ return true;
11501
+ }
11502
+ await this.wait(checkInterval);
11503
+ }
11504
+ return false;
11505
+ }
11111
11506
  wait(ms) {
11112
- return new Promise((resolve4) => setTimeout(resolve4, ms));
11507
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
11113
11508
  }
11114
11509
  getLogsDir() {
11115
- const logsDir = path24.join((0, import_core12.getConfigDir)(), "logs");
11116
- if (!fs24.existsSync(logsDir)) {
11117
- fs24.mkdirSync(logsDir, { recursive: true });
11510
+ const logsDir = path25.join((0, import_core12.getConfigDir)(), "logs");
11511
+ if (!fs25.existsSync(logsDir)) {
11512
+ fs25.mkdirSync(logsDir, { recursive: true });
11118
11513
  }
11119
11514
  return logsDir;
11120
11515
  }
11121
11516
  getLogFilePath(moduleUid) {
11122
- return path24.join(this.getLogsDir(), `dev-${moduleUid}.log`);
11517
+ return path25.join(this.getLogsDir(), `dev-${moduleUid}.log`);
11123
11518
  }
11124
11519
  rotateLogIfNeeded(logPath) {
11125
11520
  try {
11126
- if (fs24.existsSync(logPath)) {
11127
- const stats = fs24.statSync(logPath);
11521
+ if (fs25.existsSync(logPath)) {
11522
+ const stats = fs25.statSync(logPath);
11128
11523
  if (stats.size > DEV_SERVER_CONSTANTS.MAX_LOG_SIZE_BYTES) {
11129
11524
  const backupPath = `${logPath}.1`;
11130
- if (fs24.existsSync(backupPath)) {
11131
- fs24.unlinkSync(backupPath);
11525
+ if (fs25.existsSync(backupPath)) {
11526
+ fs25.unlinkSync(backupPath);
11132
11527
  }
11133
- fs24.renameSync(logPath, backupPath);
11528
+ fs25.renameSync(logPath, backupPath);
11134
11529
  }
11135
11530
  }
11136
11531
  } catch {
@@ -11141,7 +11536,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11141
11536
  try {
11142
11537
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11143
11538
  const prefix = isError ? "ERR" : "OUT";
11144
- fs24.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
11539
+ fs25.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
11145
11540
  `);
11146
11541
  } catch {
11147
11542
  }
@@ -11150,6 +11545,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11150
11545
  return {
11151
11546
  moduleUid: state.moduleUid,
11152
11547
  port: state.port,
11548
+ wsPort: state.wsPort,
11153
11549
  pid: state.process.pid,
11154
11550
  startedAt: state.startedAt,
11155
11551
  uptime: Math.floor((Date.now() - state.startedAt.getTime()) / 1e3),
@@ -11210,7 +11606,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
11210
11606
  * @returns Result with success status and preview URL
11211
11607
  */
11212
11608
  async startPreview(config) {
11213
- const { moduleUid, worktreePath, port = DEFAULT_PORT, customCommand } = config;
11609
+ const { moduleUid, worktreePath, port = DEFAULT_PORT, wsPort: requestedWsPort, customCommand } = config;
11610
+ let wsPort = requestedWsPort;
11214
11611
  if (!worktreePath) {
11215
11612
  return { success: false, error: "Worktree path is required" };
11216
11613
  }
@@ -11237,29 +11634,45 @@ var PreviewManager = class extends import_events3.EventEmitter {
11237
11634
  }
11238
11635
  const existing = this.previews.get(moduleUid);
11239
11636
  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
- };
11637
+ const appHealthy = await this.devServer.isHealthy(moduleUid);
11638
+ const wsHealthy = await this.devServer.isWsHealthy(moduleUid);
11639
+ if (appHealthy && wsHealthy) {
11640
+ console.log(`[PreviewManager] Preview already running for ${moduleUid}`);
11641
+ return {
11642
+ success: true,
11643
+ previewUrl: existing.tunnelUrl,
11644
+ alreadyRunning: true
11645
+ };
11646
+ }
11647
+ console.warn(
11648
+ `[PreviewManager] EP1406: Existing preview for ${moduleUid} is unhealthy (app=${appHealthy}, ws=${wsHealthy}), restarting`
11649
+ );
11650
+ await this.stopPreview(moduleUid);
11246
11651
  }
11247
11652
  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");
11653
+ let state = null;
11258
11654
  try {
11655
+ if (!wsPort) {
11656
+ wsPort = allocateWsPort(moduleUid);
11657
+ }
11658
+ wsPort = assignWsPort(moduleUid, wsPort);
11659
+ console.log(`[PreviewManager] Starting preview for ${moduleUid} at ${worktreePath}:${port} (ws:${wsPort})`);
11660
+ state = {
11661
+ moduleUid,
11662
+ worktreePath,
11663
+ port,
11664
+ wsPort,
11665
+ state: "starting",
11666
+ startedAt: /* @__PURE__ */ new Date()
11667
+ };
11668
+ const activeState = state;
11669
+ this.previews.set(moduleUid, state);
11670
+ this.emitStateChange(moduleUid, "starting");
11259
11671
  console.log(`[PreviewManager] Starting dev server for ${moduleUid}...`);
11260
11672
  const devResult = await this.devServer.start({
11261
11673
  projectPath: worktreePath,
11262
11674
  port,
11675
+ wsPort,
11263
11676
  moduleUid,
11264
11677
  customCommand,
11265
11678
  autoRestart: true
@@ -11267,6 +11680,8 @@ var PreviewManager = class extends import_events3.EventEmitter {
11267
11680
  if (!devResult.success) {
11268
11681
  state.state = "error";
11269
11682
  state.error = devResult.error || "Failed to start dev server";
11683
+ releasePort(moduleUid);
11684
+ releaseWsPort(moduleUid);
11270
11685
  this.emitStateChange(moduleUid, "error");
11271
11686
  this.emit("error", moduleUid, new Error(state.error));
11272
11687
  return { success: false, error: state.error };
@@ -11275,7 +11690,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11275
11690
  this.emitStateChange(moduleUid, "running");
11276
11691
  console.log(`[PreviewManager] Dev server running on port ${port}`);
11277
11692
  console.log(`[PreviewManager] Starting Named Tunnel for ${moduleUid}...`);
11278
- state.state = "tunneling";
11693
+ activeState.state = "tunneling";
11279
11694
  this.emitStateChange(moduleUid, "tunneling");
11280
11695
  const MAX_TUNNEL_RETRIES = 2;
11281
11696
  let tunnelResult = { success: false };
@@ -11283,7 +11698,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11283
11698
  for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
11284
11699
  if (attempt > 1) {
11285
11700
  console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
11286
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
11701
+ await new Promise((resolve6) => setTimeout(resolve6, 2e3));
11287
11702
  }
11288
11703
  tunnelResult = await this.tunnel.startTunnel({
11289
11704
  moduleUid,
@@ -11293,20 +11708,20 @@ var PreviewManager = class extends import_events3.EventEmitter {
11293
11708
  onStatusChange: (status, error) => {
11294
11709
  console.log(`[PreviewManager] Tunnel status for ${moduleUid}: ${status}${error ? ` - ${error}` : ""}`);
11295
11710
  if (status === "error") {
11296
- state.state = "error";
11297
- state.error = error || "Tunnel error";
11711
+ activeState.state = "error";
11712
+ activeState.error = error || "Tunnel error";
11298
11713
  this.emitStateChange(moduleUid, "error");
11299
11714
  } else if (status === "disconnected") {
11300
- state.state = "running";
11301
- state.tunnelUrl = void 0;
11715
+ activeState.state = "running";
11716
+ activeState.tunnelUrl = void 0;
11302
11717
  this.emitStateChange(moduleUid, "running");
11303
11718
  } else if (status === "reconnecting") {
11304
- state.state = "tunneling";
11719
+ activeState.state = "tunneling";
11305
11720
  this.emitStateChange(moduleUid, "tunneling");
11306
11721
  }
11307
11722
  },
11308
11723
  onUrl: (url) => {
11309
- state.tunnelUrl = url;
11724
+ activeState.tunnelUrl = url;
11310
11725
  }
11311
11726
  });
11312
11727
  if (tunnelResult.success) {
@@ -11322,8 +11737,10 @@ var PreviewManager = class extends import_events3.EventEmitter {
11322
11737
  } catch (cleanupError) {
11323
11738
  console.warn(`[PreviewManager] Error cleaning up dev server after tunnel failure:`, cleanupError);
11324
11739
  }
11325
- state.state = "error";
11326
- state.error = lastError;
11740
+ releasePort(moduleUid);
11741
+ releaseWsPort(moduleUid);
11742
+ activeState.state = "error";
11743
+ activeState.error = lastError;
11327
11744
  this.previews.delete(moduleUid);
11328
11745
  this.emitStateChange(moduleUid, "error");
11329
11746
  return {
@@ -11331,9 +11748,9 @@ var PreviewManager = class extends import_events3.EventEmitter {
11331
11748
  error: `Tunnel failed after ${MAX_TUNNEL_RETRIES} attempts: ${lastError}`
11332
11749
  };
11333
11750
  }
11334
- state.state = "live";
11335
- state.tunnelUrl = tunnelResult.url;
11336
- state.error = void 0;
11751
+ activeState.state = "live";
11752
+ activeState.tunnelUrl = tunnelResult.url;
11753
+ activeState.error = void 0;
11337
11754
  this.emitStateChange(moduleUid, "live");
11338
11755
  this.emit("live", moduleUid, tunnelResult.url);
11339
11756
  console.log(`[PreviewManager] Preview live for ${moduleUid}: ${tunnelResult.url}`);
@@ -11344,9 +11761,17 @@ var PreviewManager = class extends import_events3.EventEmitter {
11344
11761
  } catch (error) {
11345
11762
  const errorMsg = error instanceof Error ? error.message : String(error);
11346
11763
  console.error(`[PreviewManager] Error starting preview for ${moduleUid}:`, error);
11347
- state.state = "error";
11348
- state.error = errorMsg;
11349
- this.emitStateChange(moduleUid, "error");
11764
+ if (state) {
11765
+ state.state = "error";
11766
+ state.error = errorMsg;
11767
+ } else {
11768
+ this.previews.delete(moduleUid);
11769
+ }
11770
+ releasePort(moduleUid);
11771
+ releaseWsPort(moduleUid);
11772
+ if (state) {
11773
+ this.emitStateChange(moduleUid, "error");
11774
+ }
11350
11775
  this.emit("error", moduleUid, error instanceof Error ? error : new Error(errorMsg));
11351
11776
  return { success: false, error: errorMsg };
11352
11777
  } finally {
@@ -11382,6 +11807,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11382
11807
  console.warn(`[PreviewManager] Error clearing tunnel URL for ${moduleUid}:`, error);
11383
11808
  }
11384
11809
  releasePort(moduleUid);
11810
+ releaseWsPort(moduleUid);
11385
11811
  if (state) {
11386
11812
  state.state = "stopped";
11387
11813
  state.tunnelUrl = void 0;
@@ -11404,11 +11830,12 @@ var PreviewManager = class extends import_events3.EventEmitter {
11404
11830
  }
11405
11831
  console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
11406
11832
  await this.stopPreview(moduleUid);
11407
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
11833
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
11408
11834
  return this.startPreview({
11409
11835
  moduleUid,
11410
11836
  worktreePath: state.worktreePath,
11411
- port: state.port
11837
+ port: state.port,
11838
+ wsPort: state.wsPort
11412
11839
  });
11413
11840
  }
11414
11841
  /**
@@ -11431,6 +11858,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11431
11858
  tunnelUrl: state.tunnelUrl,
11432
11859
  tunnelState: tunnelInfo?.status === "connected" ? "connected" : tunnelInfo?.status === "starting" ? "starting" : tunnelInfo?.status === "error" ? "error" : tunnelInfo?.status === "disconnected" ? "disconnected" : void 0,
11433
11860
  port: state.port,
11861
+ wsPort: state.wsPort,
11434
11862
  error: state.error,
11435
11863
  startedAt: state.startedAt
11436
11864
  };
@@ -11595,8 +12023,8 @@ async function deleteWorktree(config, moduleUid) {
11595
12023
  }
11596
12024
 
11597
12025
  // src/daemon/package-manager.ts
11598
- var fs25 = __toESM(require("fs"));
11599
- var path25 = __toESM(require("path"));
12026
+ var fs26 = __toESM(require("fs"));
12027
+ var path26 = __toESM(require("path"));
11600
12028
  function pnpmCommand(args) {
11601
12029
  return [
11602
12030
  "if command -v pnpm >/dev/null 2>&1; then",
@@ -11614,13 +12042,13 @@ var PACKAGE_MANAGERS = {
11614
12042
  {
11615
12043
  name: "pnpm",
11616
12044
  detector: (p) => {
11617
- if (fs25.existsSync(path25.join(p, "pnpm-lock.yaml"))) {
12045
+ if (fs26.existsSync(path26.join(p, "pnpm-lock.yaml"))) {
11618
12046
  return "pnpm-lock.yaml";
11619
12047
  }
11620
- const pkgJsonPath = path25.join(p, "package.json");
11621
- if (fs25.existsSync(pkgJsonPath)) {
12048
+ const pkgJsonPath = path26.join(p, "package.json");
12049
+ if (fs26.existsSync(pkgJsonPath)) {
11622
12050
  try {
11623
- const pkg = JSON.parse(fs25.readFileSync(pkgJsonPath, "utf-8"));
12051
+ const pkg = JSON.parse(fs26.readFileSync(pkgJsonPath, "utf-8"));
11624
12052
  if (pkg.packageManager?.startsWith("pnpm")) {
11625
12053
  return "package.json (packageManager field)";
11626
12054
  }
@@ -11636,7 +12064,7 @@ var PACKAGE_MANAGERS = {
11636
12064
  },
11637
12065
  {
11638
12066
  name: "yarn",
11639
- detector: (p) => fs25.existsSync(path25.join(p, "yarn.lock")) ? "yarn.lock" : null,
12067
+ detector: (p) => fs26.existsSync(path26.join(p, "yarn.lock")) ? "yarn.lock" : null,
11640
12068
  config: {
11641
12069
  installCmd: "yarn install --frozen-lockfile",
11642
12070
  cacheEnvVar: "YARN_CACHE_FOLDER"
@@ -11644,14 +12072,14 @@ var PACKAGE_MANAGERS = {
11644
12072
  },
11645
12073
  {
11646
12074
  name: "bun",
11647
- detector: (p) => fs25.existsSync(path25.join(p, "bun.lockb")) ? "bun.lockb" : null,
12075
+ detector: (p) => fs26.existsSync(path26.join(p, "bun.lockb")) ? "bun.lockb" : null,
11648
12076
  config: {
11649
12077
  installCmd: "bun install --frozen-lockfile"
11650
12078
  }
11651
12079
  },
11652
12080
  {
11653
12081
  name: "npm",
11654
- detector: (p) => fs25.existsSync(path25.join(p, "package-lock.json")) ? "package-lock.json" : null,
12082
+ detector: (p) => fs26.existsSync(path26.join(p, "package-lock.json")) ? "package-lock.json" : null,
11655
12083
  config: {
11656
12084
  installCmd: "npm ci"
11657
12085
  }
@@ -11660,7 +12088,7 @@ var PACKAGE_MANAGERS = {
11660
12088
  // EP1222: Default to pnpm for new projects without lockfile
11661
12089
  // This encourages standardization on pnpm across Episoda projects
11662
12090
  name: "pnpm",
11663
- detector: (p) => fs25.existsSync(path25.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
12091
+ detector: (p) => fs26.existsSync(path26.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
11664
12092
  config: {
11665
12093
  installCmd: pnpmCommand("install"),
11666
12094
  cacheEnvVar: "PNPM_HOME"
@@ -11671,13 +12099,13 @@ var PACKAGE_MANAGERS = {
11671
12099
  {
11672
12100
  name: "uv",
11673
12101
  detector: (p) => {
11674
- const pyprojectPath = path25.join(p, "pyproject.toml");
11675
- if (fs25.existsSync(path25.join(p, "uv.lock"))) {
12102
+ const pyprojectPath = path26.join(p, "pyproject.toml");
12103
+ if (fs26.existsSync(path26.join(p, "uv.lock"))) {
11676
12104
  return "uv.lock";
11677
12105
  }
11678
- if (fs25.existsSync(pyprojectPath)) {
12106
+ if (fs26.existsSync(pyprojectPath)) {
11679
12107
  try {
11680
- const content = fs25.readFileSync(pyprojectPath, "utf-8");
12108
+ const content = fs26.readFileSync(pyprojectPath, "utf-8");
11681
12109
  if (content.includes("[tool.uv]") || content.includes("[project]")) {
11682
12110
  return "pyproject.toml";
11683
12111
  }
@@ -11693,7 +12121,7 @@ var PACKAGE_MANAGERS = {
11693
12121
  },
11694
12122
  {
11695
12123
  name: "pip",
11696
- detector: (p) => fs25.existsSync(path25.join(p, "requirements.txt")) ? "requirements.txt" : null,
12124
+ detector: (p) => fs26.existsSync(path26.join(p, "requirements.txt")) ? "requirements.txt" : null,
11697
12125
  config: {
11698
12126
  installCmd: "pip install -r requirements.txt"
11699
12127
  }
@@ -11745,15 +12173,15 @@ function detectPackageManager(worktreePath, preferredLanguages) {
11745
12173
  }
11746
12174
 
11747
12175
  // src/daemon/build-packages.ts
11748
- var fs26 = __toESM(require("fs"));
11749
- var path26 = __toESM(require("path"));
12176
+ var fs27 = __toESM(require("fs"));
12177
+ var path27 = __toESM(require("path"));
11750
12178
  function hasPackageScript(worktreePath, scriptName) {
11751
12179
  try {
11752
- const packageJsonPath = path26.join(worktreePath, "package.json");
11753
- if (!fs26.existsSync(packageJsonPath)) {
12180
+ const packageJsonPath = path27.join(worktreePath, "package.json");
12181
+ if (!fs27.existsSync(packageJsonPath)) {
11754
12182
  return false;
11755
12183
  }
11756
- const packageJson2 = JSON.parse(fs26.readFileSync(packageJsonPath, "utf-8"));
12184
+ const packageJson2 = JSON.parse(fs27.readFileSync(packageJsonPath, "utf-8"));
11757
12185
  return typeof packageJson2.scripts?.[scriptName] === "string";
11758
12186
  } catch {
11759
12187
  return false;
@@ -11811,19 +12239,19 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
11811
12239
  if (process.env.EPISODA_MODE !== "cloud") {
11812
12240
  return;
11813
12241
  }
11814
- const homeDir = process.env.HOME || os12.homedir();
11815
- const workspaceConfigPath = path27.join(
12242
+ const homeDir = process.env.HOME || os13.homedir();
12243
+ const workspaceConfigPath = path28.join(
11816
12244
  homeDir,
11817
12245
  "episoda",
11818
12246
  workspaceSlug,
11819
12247
  ".episoda",
11820
12248
  "config.json"
11821
12249
  );
11822
- if (!fs27.existsSync(workspaceConfigPath)) {
12250
+ if (!fs28.existsSync(workspaceConfigPath)) {
11823
12251
  return;
11824
12252
  }
11825
12253
  try {
11826
- const content = fs27.readFileSync(workspaceConfigPath, "utf8");
12254
+ const content = fs28.readFileSync(workspaceConfigPath, "utf8");
11827
12255
  const workspaceConfig = JSON.parse(content);
11828
12256
  let changed = false;
11829
12257
  if (projectId && workspaceConfig.projectId !== projectId && workspaceConfig.project_id !== projectId) {
@@ -11835,7 +12263,7 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
11835
12263
  changed = true;
11836
12264
  }
11837
12265
  if (changed) {
11838
- fs27.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
12266
+ fs28.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
11839
12267
  console.log("[Worktree] Updated workspace config with project context");
11840
12268
  }
11841
12269
  } catch (error) {
@@ -11882,19 +12310,19 @@ async function handleWorktreeCreate(request2) {
11882
12310
  }
11883
12311
  try {
11884
12312
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
11885
- const bareRepoPath = path27.join(projectPath, ".bare");
12313
+ const bareRepoPath = path28.join(projectPath, ".bare");
11886
12314
  const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
11887
- if (!fs27.existsSync(bareRepoPath)) {
12315
+ if (!fs28.existsSync(bareRepoPath)) {
11888
12316
  console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
11889
12317
  console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
11890
- const episodaDir = path27.join(projectPath, ".episoda");
11891
- fs27.mkdirSync(episodaDir, { recursive: true });
12318
+ const episodaDir = path28.join(projectPath, ".episoda");
12319
+ fs28.mkdirSync(episodaDir, { recursive: true });
11892
12320
  try {
11893
12321
  console.log(`[Worktree] K1273: Starting git clone...`);
11894
12322
  await execAsync2(`git clone --bare "${repoUrl}" "${bareRepoPath}"`, { env: gitEnv });
11895
12323
  console.log(`[Worktree] K1273: Clone successful`);
11896
12324
  await execAsync2(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
11897
- const configPath = path27.join(episodaDir, "config.json");
12325
+ const configPath = path28.join(episodaDir, "config.json");
11898
12326
  const config = {
11899
12327
  projectId,
11900
12328
  workspaceSlug,
@@ -11903,7 +12331,7 @@ async function handleWorktreeCreate(request2) {
11903
12331
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11904
12332
  worktrees: []
11905
12333
  };
11906
- fs27.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
12334
+ fs28.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
11907
12335
  console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
11908
12336
  } catch (cloneError) {
11909
12337
  console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
@@ -11916,8 +12344,8 @@ async function handleWorktreeCreate(request2) {
11916
12344
  console.log(`[Worktree] EP1373: Project exists, skipping handler-level fetch (manager handles narrow fetch)`);
11917
12345
  }
11918
12346
  const manager = new WorktreeManager(projectPath);
11919
- const initialized2 = await manager.initialize();
11920
- if (!initialized2) {
12347
+ const initialized3 = await manager.initialize();
12348
+ if (!initialized3) {
11921
12349
  return {
11922
12350
  success: false,
11923
12351
  error: `Failed to initialize WorktreeManager at ${projectPath}`
@@ -11938,8 +12366,8 @@ async function handleWorktreeCreate(request2) {
11938
12366
  let finalError;
11939
12367
  if (envVars && Object.keys(envVars).length > 0) {
11940
12368
  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");
12369
+ const envPath = path28.join(worktreePath, ".env");
12370
+ fs28.writeFileSync(envPath, envContent + "\n", "utf-8");
11943
12371
  console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
11944
12372
  }
11945
12373
  const isCloud = process.env.EPISODA_MODE === "cloud";
@@ -12011,8 +12439,8 @@ async function handleWorktreeRelease(request2) {
12011
12439
  try {
12012
12440
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12013
12441
  const manager = new WorktreeManager(projectPath);
12014
- const initialized2 = await manager.initialize();
12015
- if (!initialized2) {
12442
+ const initialized3 = await manager.initialize();
12443
+ if (!initialized3) {
12016
12444
  console.log(`[Worktree] EP1143: Project not initialized, nothing to release`);
12017
12445
  return { success: true };
12018
12446
  }
@@ -12065,8 +12493,8 @@ async function handleWorktreeList(workspaceSlug, projectSlug) {
12065
12493
  try {
12066
12494
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12067
12495
  const manager = new WorktreeManager(projectPath);
12068
- const initialized2 = await manager.initialize();
12069
- if (!initialized2) {
12496
+ const initialized3 = await manager.initialize();
12497
+ if (!initialized3) {
12070
12498
  return { success: true, worktrees: [] };
12071
12499
  }
12072
12500
  const worktrees = manager.listWorktrees();
@@ -12080,18 +12508,18 @@ async function handleProjectEject(request2) {
12080
12508
  console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
12081
12509
  try {
12082
12510
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12083
- const bareRepoPath = path27.join(projectPath, ".bare");
12084
- if (!fs27.existsSync(projectPath)) {
12511
+ const bareRepoPath = path28.join(projectPath, ".bare");
12512
+ if (!fs28.existsSync(projectPath)) {
12085
12513
  console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
12086
12514
  return { success: true };
12087
12515
  }
12088
- if (!fs27.existsSync(bareRepoPath)) {
12516
+ if (!fs28.existsSync(bareRepoPath)) {
12089
12517
  console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
12090
12518
  return { success: true };
12091
12519
  }
12092
12520
  const manager = new WorktreeManager(projectPath);
12093
- const initialized2 = await manager.initialize();
12094
- if (initialized2) {
12521
+ const initialized3 = await manager.initialize();
12522
+ if (initialized3) {
12095
12523
  const worktrees = manager.listWorktrees();
12096
12524
  if (worktrees.length > 0) {
12097
12525
  return {
@@ -12100,12 +12528,12 @@ async function handleProjectEject(request2) {
12100
12528
  };
12101
12529
  }
12102
12530
  }
12103
- const artifactsPath = path27.join(projectPath, "artifacts");
12104
- if (fs27.existsSync(artifactsPath)) {
12531
+ const artifactsPath = path28.join(projectPath, "artifacts");
12532
+ if (fs28.existsSync(artifactsPath)) {
12105
12533
  await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
12106
12534
  }
12107
12535
  console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
12108
- await fs27.promises.rm(projectPath, { recursive: true, force: true });
12536
+ await fs28.promises.rm(projectPath, { recursive: true, force: true });
12109
12537
  console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
12110
12538
  return { success: true };
12111
12539
  } catch (error) {
@@ -12119,7 +12547,7 @@ async function handleProjectEject(request2) {
12119
12547
  async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
12120
12548
  const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
12121
12549
  try {
12122
- const files = await fs27.promises.readdir(artifactsPath);
12550
+ const files = await fs28.promises.readdir(artifactsPath);
12123
12551
  if (files.length === 0) {
12124
12552
  return;
12125
12553
  }
@@ -12133,8 +12561,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12133
12561
  }
12134
12562
  const artifacts = [];
12135
12563
  for (const fileName of files) {
12136
- const filePath = path27.join(artifactsPath, fileName);
12137
- const stat = await fs27.promises.stat(filePath);
12564
+ const filePath = path28.join(artifactsPath, fileName);
12565
+ const stat = await fs28.promises.stat(filePath);
12138
12566
  if (stat.isDirectory()) {
12139
12567
  continue;
12140
12568
  }
@@ -12143,9 +12571,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12143
12571
  continue;
12144
12572
  }
12145
12573
  try {
12146
- const content = await fs27.promises.readFile(filePath);
12574
+ const content = await fs28.promises.readFile(filePath);
12147
12575
  const base64Content = content.toString("base64");
12148
- const ext = path27.extname(fileName).toLowerCase();
12576
+ const ext = path28.extname(fileName).toLowerCase();
12149
12577
  const mimeTypes = {
12150
12578
  ".json": "application/json",
12151
12579
  ".txt": "text/plain",
@@ -12201,8 +12629,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12201
12629
  }
12202
12630
 
12203
12631
  // src/daemon/handlers/project-handlers.ts
12204
- var path28 = __toESM(require("path"));
12205
- var fs28 = __toESM(require("fs"));
12632
+ var path29 = __toESM(require("path"));
12633
+ var fs29 = __toESM(require("fs"));
12206
12634
  function validateSlug(slug, fieldName) {
12207
12635
  if (!slug || typeof slug !== "string") {
12208
12636
  return `${fieldName} is required`;
@@ -12242,14 +12670,14 @@ async function handleProjectSetup(params) {
12242
12670
  console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
12243
12671
  try {
12244
12672
  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 });
12673
+ const artifactsPath = path29.join(projectPath, "artifacts");
12674
+ const configDir = path29.join(projectPath, ".episoda");
12675
+ const configPath = path29.join(configDir, "config.json");
12676
+ await fs29.promises.mkdir(artifactsPath, { recursive: true });
12677
+ await fs29.promises.mkdir(configDir, { recursive: true });
12250
12678
  let existingConfig = {};
12251
12679
  try {
12252
- const existing = await fs28.promises.readFile(configPath, "utf-8");
12680
+ const existing = await fs29.promises.readFile(configPath, "utf-8");
12253
12681
  existingConfig = JSON.parse(existing);
12254
12682
  } catch {
12255
12683
  }
@@ -12262,7 +12690,7 @@ async function handleProjectSetup(params) {
12262
12690
  // Only set created_at if not already present
12263
12691
  created_at: existingConfig.created_at || (/* @__PURE__ */ new Date()).toISOString()
12264
12692
  };
12265
- await fs28.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12693
+ await fs29.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12266
12694
  console.log(`[ProjectSetup] EP1199: Project setup complete at ${projectPath}`);
12267
12695
  return {
12268
12696
  success: true,
@@ -12282,8 +12710,8 @@ async function handleProjectSetup(params) {
12282
12710
  // src/utils/dev-server.ts
12283
12711
  var import_child_process15 = require("child_process");
12284
12712
  var import_core14 = __toESM(require_dist());
12285
- var fs29 = __toESM(require("fs"));
12286
- var path29 = __toESM(require("path"));
12713
+ var fs30 = __toESM(require("fs"));
12714
+ var path30 = __toESM(require("path"));
12287
12715
  var MAX_RESTART_ATTEMPTS = 5;
12288
12716
  var INITIAL_RESTART_DELAY_MS = 2e3;
12289
12717
  var MAX_RESTART_DELAY_MS = 3e4;
@@ -12291,26 +12719,26 @@ var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
12291
12719
  var NODE_MEMORY_LIMIT_MB = 2048;
12292
12720
  var activeServers = /* @__PURE__ */ new Map();
12293
12721
  function getLogsDir() {
12294
- const logsDir = path29.join((0, import_core14.getConfigDir)(), "logs");
12295
- if (!fs29.existsSync(logsDir)) {
12296
- fs29.mkdirSync(logsDir, { recursive: true });
12722
+ const logsDir = path30.join((0, import_core14.getConfigDir)(), "logs");
12723
+ if (!fs30.existsSync(logsDir)) {
12724
+ fs30.mkdirSync(logsDir, { recursive: true });
12297
12725
  }
12298
12726
  return logsDir;
12299
12727
  }
12300
12728
  function getLogFilePath(moduleUid) {
12301
- return path29.join(getLogsDir(), `dev-${moduleUid}.log`);
12729
+ return path30.join(getLogsDir(), `dev-${moduleUid}.log`);
12302
12730
  }
12303
12731
  function rotateLogIfNeeded(logPath) {
12304
12732
  try {
12305
- if (fs29.existsSync(logPath)) {
12306
- const stats = fs29.statSync(logPath);
12733
+ if (fs30.existsSync(logPath)) {
12734
+ const stats = fs30.statSync(logPath);
12307
12735
  if (stats.size > MAX_LOG_SIZE_BYTES2) {
12308
12736
  const backupPath = `${logPath}.1`;
12309
- if (fs29.existsSync(backupPath)) {
12310
- fs29.unlinkSync(backupPath);
12737
+ if (fs30.existsSync(backupPath)) {
12738
+ fs30.unlinkSync(backupPath);
12311
12739
  }
12312
- fs29.renameSync(logPath, backupPath);
12313
- console.log(`[DevServer] EP932: Rotated log file for ${path29.basename(logPath)}`);
12740
+ fs30.renameSync(logPath, backupPath);
12741
+ console.log(`[DevServer] EP932: Rotated log file for ${path30.basename(logPath)}`);
12314
12742
  }
12315
12743
  }
12316
12744
  } catch (error) {
@@ -12323,7 +12751,7 @@ function writeToLog(logPath, line, isError = false) {
12323
12751
  const prefix = isError ? "ERR" : "OUT";
12324
12752
  const logLine = `[${timestamp}] [${prefix}] ${line}
12325
12753
  `;
12326
- fs29.appendFileSync(logPath, logLine);
12754
+ fs30.appendFileSync(logPath, logLine);
12327
12755
  } catch {
12328
12756
  }
12329
12757
  }
@@ -12343,7 +12771,7 @@ async function killProcessOnPort(port) {
12343
12771
  } catch {
12344
12772
  }
12345
12773
  }
12346
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
12774
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
12347
12775
  for (const pid of pids) {
12348
12776
  try {
12349
12777
  (0, import_child_process15.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
@@ -12352,7 +12780,7 @@ async function killProcessOnPort(port) {
12352
12780
  } catch {
12353
12781
  }
12354
12782
  }
12355
- await new Promise((resolve4) => setTimeout(resolve4, 500));
12783
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
12356
12784
  const stillInUse = await isPortInUse(port);
12357
12785
  if (stillInUse) {
12358
12786
  console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
@@ -12372,7 +12800,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
12372
12800
  if (await isPortInUse(port)) {
12373
12801
  return true;
12374
12802
  }
12375
- await new Promise((resolve4) => setTimeout(resolve4, checkInterval));
12803
+ await new Promise((resolve6) => setTimeout(resolve6, checkInterval));
12376
12804
  }
12377
12805
  return false;
12378
12806
  }
@@ -12444,7 +12872,7 @@ async function handleProcessExit(moduleUid, code, signal) {
12444
12872
  const delay = calculateRestartDelay(serverInfo.restartCount);
12445
12873
  console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
12446
12874
  writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
12447
- await new Promise((resolve4) => setTimeout(resolve4, delay));
12875
+ await new Promise((resolve6) => setTimeout(resolve6, delay));
12448
12876
  if (!activeServers.has(moduleUid)) {
12449
12877
  console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
12450
12878
  return;
@@ -12502,8 +12930,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
12502
12930
  });
12503
12931
  injectedEnvVars = result.envVars;
12504
12932
  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) {
12933
+ const envFilePath = path30.join(projectPath, ".env");
12934
+ if (!fs30.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
12507
12935
  console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
12508
12936
  writeEnvFile(projectPath, injectedEnvVars);
12509
12937
  }
@@ -12569,7 +12997,7 @@ async function stopDevServer(moduleUid) {
12569
12997
  writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
12570
12998
  }
12571
12999
  serverInfo.process.kill("SIGTERM");
12572
- await new Promise((resolve4) => setTimeout(resolve4, 2e3));
13000
+ await new Promise((resolve6) => setTimeout(resolve6, 2e3));
12573
13001
  if (!serverInfo.process.killed) {
12574
13002
  serverInfo.process.kill("SIGKILL");
12575
13003
  }
@@ -12587,7 +13015,7 @@ async function restartDevServer(moduleUid) {
12587
13015
  writeToLog(logFile, `Manual restart requested`, false);
12588
13016
  }
12589
13017
  await stopDevServer(moduleUid);
12590
- await new Promise((resolve4) => setTimeout(resolve4, 1e3));
13018
+ await new Promise((resolve6) => setTimeout(resolve6, 1e3));
12591
13019
  if (await isPortInUse(port)) {
12592
13020
  await killProcessOnPort(port);
12593
13021
  }
@@ -12641,9 +13069,9 @@ var IPCRouter = class {
12641
13069
  // EP726: Kept for backward compatibility
12642
13070
  machineName: this.host.deviceName,
12643
13071
  // EP1186: User-friendly machine name from server
12644
- hostname: os13.hostname(),
12645
- platform: os13.platform(),
12646
- arch: os13.arch(),
13072
+ hostname: os14.hostname(),
13073
+ platform: os14.platform(),
13074
+ arch: os14.arch(),
12647
13075
  projects
12648
13076
  };
12649
13077
  });
@@ -12669,7 +13097,7 @@ var IPCRouter = class {
12669
13097
  if (attempt < MAX_RETRIES) {
12670
13098
  const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
12671
13099
  console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
12672
- await new Promise((resolve4) => setTimeout(resolve4, delay));
13100
+ await new Promise((resolve6) => setTimeout(resolve6, delay));
12673
13101
  await this.host.disconnectProject(projectPath);
12674
13102
  }
12675
13103
  }
@@ -12807,6 +13235,7 @@ var IPCRouter = class {
12807
13235
  await tunnelManager.stopTunnel(moduleUid);
12808
13236
  await stopDevServer(moduleUid);
12809
13237
  releasePort(moduleUid);
13238
+ releaseWsPort(moduleUid);
12810
13239
  await clearTunnelUrl(moduleUid);
12811
13240
  this.host.deleteTunnelHealthFailure(moduleUid);
12812
13241
  console.log(`[Daemon] EP823: Tunnel stopped for ${moduleUid}`);
@@ -12844,8 +13273,8 @@ var IPCRouter = class {
12844
13273
  };
12845
13274
 
12846
13275
  // src/daemon/update-manager.ts
12847
- var fs30 = __toESM(require("fs"));
12848
- var path30 = __toESM(require("path"));
13276
+ var fs31 = __toESM(require("fs"));
13277
+ var path31 = __toESM(require("path"));
12849
13278
  var import_child_process17 = require("child_process");
12850
13279
  var import_core17 = __toESM(require_dist());
12851
13280
  var semver2 = __toESM(require("semver"));
@@ -13103,8 +13532,8 @@ var UpdateManager = class _UpdateManager {
13103
13532
  console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
13104
13533
  await this.host.shutdown();
13105
13534
  const configDir = (0, import_core17.getConfigDir)();
13106
- const logPath = path30.join(configDir, "daemon.log");
13107
- const logFd = fs30.openSync(logPath, "a");
13535
+ const logPath = path31.join(configDir, "daemon.log");
13536
+ const logFd = fs31.openSync(logPath, "a");
13108
13537
  const child = (0, import_child_process17.spawn)("node", [this.daemonEntryFile], {
13109
13538
  detached: true,
13110
13539
  stdio: ["ignore", logFd, logFd],
@@ -13112,7 +13541,7 @@ var UpdateManager = class _UpdateManager {
13112
13541
  });
13113
13542
  if (!child.pid) {
13114
13543
  try {
13115
- fs30.closeSync(logFd);
13544
+ fs31.closeSync(logFd);
13116
13545
  } catch {
13117
13546
  }
13118
13547
  this.recordUpdateFailure(targetVersion, "Failed to spawn replacement daemon (missing pid)");
@@ -13120,7 +13549,7 @@ var UpdateManager = class _UpdateManager {
13120
13549
  }
13121
13550
  child.unref();
13122
13551
  const pidPath = getPidFilePath();
13123
- fs30.writeFileSync(pidPath, child.pid.toString(), "utf-8");
13552
+ fs31.writeFileSync(pidPath, child.pid.toString(), "utf-8");
13124
13553
  console.log(`[Daemon] EP1324: New daemon spawned (PID: ${child.pid}), exiting old process`);
13125
13554
  process.exit(0);
13126
13555
  } catch (error) {
@@ -13171,8 +13600,8 @@ var UpdateManager = class _UpdateManager {
13171
13600
  };
13172
13601
 
13173
13602
  // src/daemon/health-orchestrator.ts
13174
- var fs31 = __toESM(require("fs"));
13175
- var path31 = __toESM(require("path"));
13603
+ var fs32 = __toESM(require("fs"));
13604
+ var path32 = __toESM(require("path"));
13176
13605
  var import_core18 = __toESM(require_dist());
13177
13606
  var HealthOrchestrator = class _HealthOrchestrator {
13178
13607
  constructor(host) {
@@ -13376,10 +13805,15 @@ var HealthOrchestrator = class _HealthOrchestrator {
13376
13805
  console.log(`[Daemon] EP1042: Killed ${cleanup.cleaned} orphaned dev server(s)`);
13377
13806
  }
13378
13807
  const registryPorts = /* @__PURE__ */ new Map();
13808
+ const registryWsPorts = /* @__PURE__ */ new Map();
13379
13809
  for (const entry of registry.getAll()) {
13380
13810
  registryPorts.set(entry.moduleUid, entry.port);
13811
+ if (entry.wsPort) {
13812
+ registryWsPorts.set(entry.moduleUid, entry.wsPort);
13813
+ }
13381
13814
  }
13382
13815
  reconcileWithRegistry(registryPorts);
13816
+ reconcileWsPortsWithRegistry(registryWsPorts);
13383
13817
  console.log("[Daemon] EP1042: Orphaned dev server cleanup complete");
13384
13818
  } catch (error) {
13385
13819
  console.error("[Daemon] EP1042: Failed to clean up orphaned dev servers:", error);
@@ -13421,26 +13855,26 @@ var HealthOrchestrator = class _HealthOrchestrator {
13421
13855
  checkAndRotateLog() {
13422
13856
  try {
13423
13857
  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);
13858
+ const logPath = path32.join(configDir, "daemon.log");
13859
+ if (!fs32.existsSync(logPath)) return;
13860
+ const stats = fs32.statSync(logPath);
13427
13861
  if (stats.size < _HealthOrchestrator.MAX_LOG_SIZE_BYTES) return;
13428
13862
  console.log(`[Daemon] EP1351: daemon.log is ${Math.round(stats.size / 1024 / 1024)}MB, rotating...`);
13429
13863
  for (let i = _HealthOrchestrator.MAX_LOG_FILES - 1; i >= 1; i--) {
13430
13864
  const src = `${logPath}.${i}`;
13431
13865
  const dst = `${logPath}.${i + 1}`;
13432
- if (fs31.existsSync(src)) {
13866
+ if (fs32.existsSync(src)) {
13433
13867
  try {
13434
- fs31.renameSync(src, dst);
13868
+ fs32.renameSync(src, dst);
13435
13869
  } catch {
13436
13870
  }
13437
13871
  }
13438
13872
  }
13439
13873
  try {
13440
- fs31.copyFileSync(logPath, `${logPath}.1`);
13874
+ fs32.copyFileSync(logPath, `${logPath}.1`);
13441
13875
  } catch {
13442
13876
  }
13443
- fs31.truncateSync(logPath, 0);
13877
+ fs32.truncateSync(logPath, 0);
13444
13878
  console.log("[Daemon] EP1351: Log rotated successfully");
13445
13879
  } catch (error) {
13446
13880
  console.warn("[Daemon] EP1351: Log rotation failed:", error instanceof Error ? error.message : error);
@@ -13489,9 +13923,9 @@ var HealthOrchestrator = class _HealthOrchestrator {
13489
13923
  if (Number.isFinite(lastAccessedMs)) {
13490
13924
  referenceTimestampMs = lastAccessedMs;
13491
13925
  fallbackAgeSource.push(`${w.moduleUid}(lastAccessed)`);
13492
- } else if (w.worktreePath && fs31.existsSync(w.worktreePath)) {
13926
+ } else if (w.worktreePath && fs32.existsSync(w.worktreePath)) {
13493
13927
  try {
13494
- const stats = fs31.statSync(w.worktreePath);
13928
+ const stats = fs32.statSync(w.worktreePath);
13495
13929
  referenceTimestampMs = stats.mtimeMs;
13496
13930
  fallbackAgeSource.push(`${w.moduleUid}(fs.mtime)`);
13497
13931
  } catch {
@@ -13857,7 +14291,7 @@ var ConnectionManager = class {
13857
14291
  * handlers are removed deterministically on every exit path.
13858
14292
  */
13859
14293
  async waitForAuthentication(client, timeoutMs = 3e4) {
13860
- await new Promise((resolve4, reject) => {
14294
+ await new Promise((resolve6, reject) => {
13861
14295
  let settled = false;
13862
14296
  const cleanup = () => {
13863
14297
  clearTimeout(timeout);
@@ -13874,7 +14308,7 @@ var ConnectionManager = class {
13874
14308
  if (settled) return;
13875
14309
  settled = true;
13876
14310
  cleanup();
13877
- resolve4();
14311
+ resolve6();
13878
14312
  };
13879
14313
  const errorHandler = (message) => {
13880
14314
  if (settled) return;
@@ -13937,7 +14371,7 @@ function resolveDaemonWsEndpoint(config, env = process.env) {
13937
14371
 
13938
14372
  // src/daemon/project-message-router.ts
13939
14373
  var import_core19 = __toESM(require_dist());
13940
- var path32 = __toESM(require("path"));
14374
+ var path33 = __toESM(require("path"));
13941
14375
  var ProjectMessageRouter = class {
13942
14376
  constructor(host) {
13943
14377
  this.host = host;
@@ -13954,7 +14388,7 @@ var ProjectMessageRouter = class {
13954
14388
  client.updateActivity();
13955
14389
  try {
13956
14390
  const gitCmd = message.command;
13957
- const bareRepoPath = path32.join(projectPath, ".bare");
14391
+ const bareRepoPath = path33.join(projectPath, ".bare");
13958
14392
  const cwd = gitCmd.worktreePath || bareRepoPath;
13959
14393
  if (gitCmd.worktreePath) {
13960
14394
  console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
@@ -14294,32 +14728,32 @@ async function fetchEnvVars2(projectId) {
14294
14728
  }
14295
14729
 
14296
14730
  // src/daemon/project-git-config.ts
14297
- var fs32 = __toESM(require("fs"));
14298
- var path33 = __toESM(require("path"));
14731
+ var fs33 = __toESM(require("fs"));
14732
+ var path34 = __toESM(require("path"));
14299
14733
  var import_core21 = __toESM(require_dist());
14300
14734
  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()) {
14735
+ const bareDir = path34.join(projectPath, ".bare");
14736
+ const gitPath = path34.join(projectPath, ".git");
14737
+ if (fs33.existsSync(bareDir) && fs33.statSync(bareDir).isDirectory()) {
14304
14738
  return { gitDir: bareDir, workDir: projectPath };
14305
14739
  }
14306
- if (fs32.existsSync(gitPath) && fs32.statSync(gitPath).isDirectory()) {
14740
+ if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isDirectory()) {
14307
14741
  return { gitDir: null, workDir: projectPath };
14308
14742
  }
14309
- if (fs32.existsSync(gitPath) && fs32.statSync(gitPath).isFile()) {
14743
+ if (fs33.existsSync(gitPath) && fs33.statSync(gitPath).isFile()) {
14310
14744
  return { gitDir: null, workDir: projectPath };
14311
14745
  }
14312
- const entries = fs32.readdirSync(projectPath, { withFileTypes: true });
14746
+ const entries = fs33.readdirSync(projectPath, { withFileTypes: true });
14313
14747
  for (const entry of entries) {
14314
14748
  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)) {
14749
+ const worktreePath = path34.join(projectPath, entry.name);
14750
+ const worktreeGit = path34.join(worktreePath, ".git");
14751
+ if (fs33.existsSync(worktreeGit)) {
14318
14752
  return { gitDir: null, workDir: worktreePath };
14319
14753
  }
14320
14754
  }
14321
14755
  }
14322
- if (fs32.existsSync(bareDir)) {
14756
+ if (fs33.existsSync(bareDir)) {
14323
14757
  return { gitDir: bareDir, workDir: projectPath };
14324
14758
  }
14325
14759
  return { gitDir: null, workDir: projectPath };
@@ -14364,24 +14798,24 @@ async function configureGitUser(projectPath, userId, workspaceId, machineId, pro
14364
14798
  async function installGitHooks(projectPath) {
14365
14799
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
14366
14800
  let hooksDir;
14367
- const bareHooksDir = path33.join(projectPath, ".bare", "hooks");
14368
- const gitHooksDir = path33.join(projectPath, ".git", "hooks");
14369
- if (fs32.existsSync(bareHooksDir)) {
14801
+ const bareHooksDir = path34.join(projectPath, ".bare", "hooks");
14802
+ const gitHooksDir = path34.join(projectPath, ".git", "hooks");
14803
+ if (fs33.existsSync(bareHooksDir)) {
14370
14804
  hooksDir = bareHooksDir;
14371
- } else if (fs32.existsSync(gitHooksDir) && fs32.statSync(path33.join(projectPath, ".git")).isDirectory()) {
14805
+ } else if (fs33.existsSync(gitHooksDir) && fs33.statSync(path34.join(projectPath, ".git")).isDirectory()) {
14372
14806
  hooksDir = gitHooksDir;
14373
14807
  } else {
14374
- const parentBareHooks = path33.join(projectPath, "..", ".bare", "hooks");
14375
- if (fs32.existsSync(parentBareHooks)) {
14808
+ const parentBareHooks = path34.join(projectPath, "..", ".bare", "hooks");
14809
+ if (fs33.existsSync(parentBareHooks)) {
14376
14810
  hooksDir = parentBareHooks;
14377
14811
  } else {
14378
14812
  console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
14379
14813
  return;
14380
14814
  }
14381
14815
  }
14382
- if (!fs32.existsSync(hooksDir)) {
14816
+ if (!fs33.existsSync(hooksDir)) {
14383
14817
  try {
14384
- fs32.mkdirSync(hooksDir, { recursive: true });
14818
+ fs33.mkdirSync(hooksDir, { recursive: true });
14385
14819
  } catch {
14386
14820
  console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
14387
14821
  return;
@@ -14389,20 +14823,20 @@ async function installGitHooks(projectPath) {
14389
14823
  }
14390
14824
  for (const hookName of hooks) {
14391
14825
  try {
14392
- const hookPath = path33.join(hooksDir, hookName);
14393
- const bundledHookPath = path33.join(__dirname, "..", "hooks", hookName);
14394
- if (!fs32.existsSync(bundledHookPath)) {
14826
+ const hookPath = path34.join(hooksDir, hookName);
14827
+ const bundledHookPath = path34.join(__dirname, "..", "hooks", hookName);
14828
+ if (!fs33.existsSync(bundledHookPath)) {
14395
14829
  console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
14396
14830
  continue;
14397
14831
  }
14398
- const hookContent = fs32.readFileSync(bundledHookPath, "utf-8");
14399
- if (fs32.existsSync(hookPath)) {
14400
- const existingContent = fs32.readFileSync(hookPath, "utf-8");
14832
+ const hookContent = fs33.readFileSync(bundledHookPath, "utf-8");
14833
+ if (fs33.existsSync(hookPath)) {
14834
+ const existingContent = fs33.readFileSync(hookPath, "utf-8");
14401
14835
  if (existingContent === hookContent) {
14402
14836
  continue;
14403
14837
  }
14404
14838
  }
14405
- fs32.writeFileSync(hookPath, hookContent, { mode: 493 });
14839
+ fs33.writeFileSync(hookPath, hookContent, { mode: 493 });
14406
14840
  console.log(`[Daemon] Installed git hook: ${hookName}`);
14407
14841
  } catch (error) {
14408
14842
  console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
@@ -14545,9 +14979,9 @@ var Daemon = class _Daemon {
14545
14979
  this.healthServer = http2.createServer((req, res) => {
14546
14980
  if (req.url === "/health" || req.url === "/") {
14547
14981
  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)
14982
+ const projects = Array.from(this.connectionManager.entries()).map(([path35, conn]) => ({
14983
+ path: path35,
14984
+ connected: this.connectionManager.hasLiveConnection(path35)
14551
14985
  }));
14552
14986
  const status = {
14553
14987
  status: isConnected ? "healthy" : "degraded",
@@ -14620,8 +15054,8 @@ var Daemon = class _Daemon {
14620
15054
  await this.shutdown();
14621
15055
  try {
14622
15056
  const pidPath = getPidFilePath();
14623
- if (fs33.existsSync(pidPath)) {
14624
- fs33.unlinkSync(pidPath);
15057
+ if (fs34.existsSync(pidPath)) {
15058
+ fs34.unlinkSync(pidPath);
14625
15059
  console.log("[Daemon] PID file cleaned up (watchdog)");
14626
15060
  }
14627
15061
  } catch (error) {
@@ -14684,8 +15118,8 @@ var Daemon = class _Daemon {
14684
15118
  }
14685
15119
  }
14686
15120
  let releaseLock;
14687
- const lockPromise = new Promise((resolve4) => {
14688
- releaseLock = resolve4;
15121
+ const lockPromise = new Promise((resolve6) => {
15122
+ releaseLock = resolve6;
14689
15123
  });
14690
15124
  this.tunnelOperationLocks.set(moduleUid, lockPromise);
14691
15125
  try {
@@ -14712,7 +15146,7 @@ var Daemon = class _Daemon {
14712
15146
  const maxWait = 35e3;
14713
15147
  const startTime = Date.now();
14714
15148
  while (this.connectionManager.hasPendingConnection(projectPath) && Date.now() - startTime < maxWait) {
14715
- await new Promise((resolve4) => setTimeout(resolve4, 500));
15149
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
14716
15150
  }
14717
15151
  if (this.connectionManager.hasLiveConnection(projectPath)) {
14718
15152
  console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
@@ -15011,8 +15445,8 @@ var Daemon = class _Daemon {
15011
15445
  };
15012
15446
  } else {
15013
15447
  const manager = new WorktreeManager(projectRootPath);
15014
- const initialized2 = await manager.initialize();
15015
- if (!initialized2) {
15448
+ const initialized3 = await manager.initialize();
15449
+ if (!initialized3) {
15016
15450
  console.warn(`[Daemon] EP1035: Failed to initialize WorktreeManager for ${projectRootPath}`);
15017
15451
  result = {
15018
15452
  success: false,
@@ -15210,8 +15644,8 @@ var Daemon = class _Daemon {
15210
15644
  let daemonPid;
15211
15645
  try {
15212
15646
  const pidPath = getPidFilePath();
15213
- if (fs33.existsSync(pidPath)) {
15214
- const pidStr = fs33.readFileSync(pidPath, "utf-8").trim();
15647
+ if (fs34.existsSync(pidPath)) {
15648
+ const pidStr = fs34.readFileSync(pidPath, "utf-8").trim();
15215
15649
  daemonPid = parseInt(pidStr, 10);
15216
15650
  }
15217
15651
  } catch (pidError) {
@@ -15222,9 +15656,9 @@ var Daemon = class _Daemon {
15222
15656
  const containerId = process.env.EPISODA_CONTAINER_ID;
15223
15657
  const capabilities = ["worktree_create_v1"];
15224
15658
  await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, {
15225
- hostname: os14.hostname(),
15226
- osPlatform: os14.platform(),
15227
- osArch: os14.arch(),
15659
+ hostname: os15.hostname(),
15660
+ osPlatform: os15.platform(),
15661
+ osArch: os15.arch(),
15228
15662
  daemonPid,
15229
15663
  cliVersion: this.cliRuntimeVersion,
15230
15664
  cliPackageName: packageJson.name,
@@ -15763,6 +16197,7 @@ var Daemon = class _Daemon {
15763
16197
  console.error("[Daemon] Failed to stop tunnels:", error);
15764
16198
  }
15765
16199
  clearAllPorts();
16200
+ clearAllWsPorts();
15766
16201
  if (this.disconnectWatchdogTimer) {
15767
16202
  clearInterval(this.disconnectWatchdogTimer);
15768
16203
  this.disconnectWatchdogTimer = null;
@@ -15785,8 +16220,8 @@ var Daemon = class _Daemon {
15785
16220
  await this.shutdown();
15786
16221
  try {
15787
16222
  const pidPath = getPidFilePath();
15788
- if (fs33.existsSync(pidPath)) {
15789
- fs33.unlinkSync(pidPath);
16223
+ if (fs34.existsSync(pidPath)) {
16224
+ fs34.unlinkSync(pidPath);
15790
16225
  console.log("[Daemon] PID file cleaned up");
15791
16226
  }
15792
16227
  } catch (error) {