@episoda/cli 0.2.205 → 0.2.207

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.
@@ -2241,7 +2241,7 @@ var require_websocket_client = __commonJS({
2241
2241
  clearTimeout(this.reconnectTimeout);
2242
2242
  this.reconnectTimeout = void 0;
2243
2243
  }
2244
- return new Promise((resolve8, reject) => {
2244
+ return new Promise((resolve9, reject) => {
2245
2245
  const connectionTimeout = setTimeout(() => {
2246
2246
  if (this.ws) {
2247
2247
  this.ws.terminate();
@@ -2272,7 +2272,7 @@ var require_websocket_client = __commonJS({
2272
2272
  daemonPid: this.daemonPid
2273
2273
  });
2274
2274
  this.startHeartbeat();
2275
- resolve8();
2275
+ resolve9();
2276
2276
  });
2277
2277
  this.ws.on("pong", () => {
2278
2278
  if (this.heartbeatTimeoutTimer) {
@@ -2416,13 +2416,13 @@ var require_websocket_client = __commonJS({
2416
2416
  console.warn("[EpisodaClient] Cannot send - WebSocket not connected");
2417
2417
  return false;
2418
2418
  }
2419
- return new Promise((resolve8) => {
2419
+ return new Promise((resolve9) => {
2420
2420
  this.ws.send(JSON.stringify(message), (error) => {
2421
2421
  if (error) {
2422
2422
  console.error("[EpisodaClient] Failed to send message:", error);
2423
- resolve8(false);
2423
+ resolve9(false);
2424
2424
  } else {
2425
- resolve8(true);
2425
+ resolve9(true);
2426
2426
  }
2427
2427
  });
2428
2428
  });
@@ -3051,7 +3051,7 @@ var require_package = __commonJS({
3051
3051
  "package.json"(exports2, module2) {
3052
3052
  module2.exports = {
3053
3053
  name: "@episoda/cli",
3054
- version: "0.2.205",
3054
+ version: "0.2.207",
3055
3055
  description: "CLI tool for Episoda local development workflow orchestration",
3056
3056
  main: "dist/index.js",
3057
3057
  types: "dist/index.d.ts",
@@ -3088,8 +3088,8 @@ var require_package = __commonJS({
3088
3088
  },
3089
3089
  optionalDependencies: {
3090
3090
  "@anthropic-ai/claude-code": "^2.0.0",
3091
- "@modelcontextprotocol/server-github": "^0.6.0",
3092
- "@openai/codex": "^0.86.0"
3091
+ "@openai/codex": "^0.86.0",
3092
+ "@modelcontextprotocol/server-github": "^0.6.0"
3093
3093
  },
3094
3094
  devDependencies: {
3095
3095
  "@episoda/core": "workspace:*",
@@ -3330,19 +3330,106 @@ function pruneMissingProjectPaths() {
3330
3330
  }
3331
3331
 
3332
3332
  // src/daemon/daemon-manager.ts
3333
+ var path4 = __toESM(require("path"));
3334
+ var import_core5 = __toESM(require_dist());
3335
+
3336
+ // src/daemon/lifecycle-metadata.ts
3337
+ var fs3 = __toESM(require("fs"));
3333
3338
  var path3 = __toESM(require("path"));
3334
3339
  var import_core3 = __toESM(require_dist());
3340
+ var DEFAULT_METADATA = {
3341
+ serviceMode: "detached",
3342
+ lastRestartReason: null,
3343
+ lastStartedAt: null
3344
+ };
3345
+ function getMetadataPath() {
3346
+ return path3.join((0, import_core3.getConfigDir)(), "daemon-metadata.json");
3347
+ }
3348
+ function readDaemonLifecycleMetadata() {
3349
+ try {
3350
+ const metadataPath = getMetadataPath();
3351
+ if (!fs3.existsSync(metadataPath)) {
3352
+ return { ...DEFAULT_METADATA };
3353
+ }
3354
+ const parsed = JSON.parse(fs3.readFileSync(metadataPath, "utf8"));
3355
+ return {
3356
+ serviceMode: parsed.serviceMode === "detached" ? parsed.serviceMode : DEFAULT_METADATA.serviceMode,
3357
+ lastRestartReason: typeof parsed.lastRestartReason === "string" ? parsed.lastRestartReason : DEFAULT_METADATA.lastRestartReason,
3358
+ lastStartedAt: typeof parsed.lastStartedAt === "string" ? parsed.lastStartedAt : DEFAULT_METADATA.lastStartedAt
3359
+ };
3360
+ } catch {
3361
+ return { ...DEFAULT_METADATA };
3362
+ }
3363
+ }
3364
+
3365
+ // src/utils/update-checker.ts
3366
+ var import_child_process2 = require("child_process");
3367
+ var semver = __toESM(require("semver"));
3368
+
3369
+ // src/ipc/ipc-client.ts
3370
+ var import_core4 = __toESM(require_dist());
3371
+
3372
+ // src/utils/update-checker.ts
3373
+ var PACKAGE_NAME = "@episoda/cli";
3374
+ var LEGACY_PACKAGE_NAME = "episoda";
3375
+ function getInstalledVersion() {
3376
+ try {
3377
+ const output = (0, import_child_process2.execSync)(`npm list -g ${PACKAGE_NAME} --json`, {
3378
+ stdio: ["pipe", "pipe", "pipe"],
3379
+ timeout: 1e4
3380
+ }).toString();
3381
+ const data = JSON.parse(output);
3382
+ return data?.dependencies?.[PACKAGE_NAME]?.version || null;
3383
+ } catch {
3384
+ return null;
3385
+ }
3386
+ }
3387
+ function getLegacyInstalledVersion() {
3388
+ try {
3389
+ const output = (0, import_child_process2.execSync)(`npm list -g ${LEGACY_PACKAGE_NAME} --json`, {
3390
+ stdio: ["pipe", "pipe", "pipe"],
3391
+ timeout: 1e4
3392
+ }).toString();
3393
+ const data = JSON.parse(output);
3394
+ return data?.dependencies?.[LEGACY_PACKAGE_NAME]?.version || null;
3395
+ } catch {
3396
+ return null;
3397
+ }
3398
+ }
3399
+ function detectCliInstallChannel(embeddedVersion) {
3400
+ const scopedVersion = getInstalledVersion();
3401
+ const legacyVersion = getLegacyInstalledVersion();
3402
+ const effectiveVersion = scopedVersion || legacyVersion || embeddedVersion || null;
3403
+ return {
3404
+ scopedVersion,
3405
+ legacyVersion,
3406
+ legacyOnly: !scopedVersion && !!legacyVersion,
3407
+ effectiveVersion
3408
+ };
3409
+ }
3410
+ function resolveEffectiveCliVersion(embeddedVersion) {
3411
+ const installedVersion = detectCliInstallChannel(embeddedVersion).effectiveVersion;
3412
+ if (!installedVersion) {
3413
+ return embeddedVersion;
3414
+ }
3415
+ if (semver.valid(installedVersion) && semver.valid(embeddedVersion)) {
3416
+ return semver.gt(installedVersion, embeddedVersion) ? installedVersion : embeddedVersion;
3417
+ }
3418
+ return installedVersion === embeddedVersion ? embeddedVersion : installedVersion;
3419
+ }
3420
+
3421
+ // src/daemon/daemon-manager.ts
3335
3422
  function getPidFilePath() {
3336
- return path3.join((0, import_core3.getConfigDir)(), "daemon.pid");
3423
+ return path4.join((0, import_core5.getConfigDir)(), "daemon.pid");
3337
3424
  }
3338
3425
  var MAX_LOG_SIZE_BYTES = 10 * 1024 * 1024;
3339
3426
 
3340
3427
  // src/ipc/ipc-server.ts
3341
3428
  var net = __toESM(require("net"));
3342
- var fs3 = __toESM(require("fs"));
3343
- var path4 = __toESM(require("path"));
3344
- var import_core4 = __toESM(require_dist());
3345
- var getSocketPath = () => path4.join((0, import_core4.getConfigDir)(), "daemon.sock");
3429
+ var fs4 = __toESM(require("fs"));
3430
+ var path5 = __toESM(require("path"));
3431
+ var import_core6 = __toESM(require_dist());
3432
+ var getSocketPath = () => path5.join((0, import_core6.getConfigDir)(), "daemon.sock");
3346
3433
  var IPCServer = class {
3347
3434
  constructor() {
3348
3435
  this.server = null;
@@ -3364,20 +3451,20 @@ var IPCServer = class {
3364
3451
  */
3365
3452
  async start() {
3366
3453
  const socketPath = getSocketPath();
3367
- if (fs3.existsSync(socketPath)) {
3368
- fs3.unlinkSync(socketPath);
3454
+ if (fs4.existsSync(socketPath)) {
3455
+ fs4.unlinkSync(socketPath);
3369
3456
  }
3370
- const dir = path4.dirname(socketPath);
3371
- if (!fs3.existsSync(dir)) {
3372
- fs3.mkdirSync(dir, { recursive: true });
3457
+ const dir = path5.dirname(socketPath);
3458
+ if (!fs4.existsSync(dir)) {
3459
+ fs4.mkdirSync(dir, { recursive: true });
3373
3460
  }
3374
3461
  this.server = net.createServer((socket) => {
3375
3462
  this.handleConnection(socket);
3376
3463
  });
3377
- return new Promise((resolve8, reject) => {
3464
+ return new Promise((resolve9, reject) => {
3378
3465
  this.server.listen(socketPath, () => {
3379
- fs3.chmodSync(socketPath, 384);
3380
- resolve8();
3466
+ fs4.chmodSync(socketPath, 384);
3467
+ resolve9();
3381
3468
  });
3382
3469
  this.server.on("error", reject);
3383
3470
  });
@@ -3388,12 +3475,12 @@ var IPCServer = class {
3388
3475
  async stop() {
3389
3476
  if (!this.server) return;
3390
3477
  const socketPath = getSocketPath();
3391
- return new Promise((resolve8) => {
3478
+ return new Promise((resolve9) => {
3392
3479
  this.server.close(() => {
3393
- if (fs3.existsSync(socketPath)) {
3394
- fs3.unlinkSync(socketPath);
3480
+ if (fs4.existsSync(socketPath)) {
3481
+ fs4.unlinkSync(socketPath);
3395
3482
  }
3396
- resolve8();
3483
+ resolve9();
3397
3484
  });
3398
3485
  });
3399
3486
  }
@@ -3458,10 +3545,10 @@ var IPCServer = class {
3458
3545
  var import_core22 = __toESM(require_dist());
3459
3546
 
3460
3547
  // src/daemon/handlers/stale-commit-cleanup.ts
3461
- var import_child_process2 = require("child_process");
3548
+ var import_child_process3 = require("child_process");
3462
3549
  var import_util = require("util");
3463
- var import_core5 = __toESM(require_dist());
3464
- var execAsync = (0, import_util.promisify)(import_child_process2.exec);
3550
+ var import_core7 = __toESM(require_dist());
3551
+ var execAsync = (0, import_util.promisify)(import_child_process3.exec);
3465
3552
  async function isGitRepository(projectPath) {
3466
3553
  try {
3467
3554
  await execAsync("git rev-parse --git-dir", { cwd: projectPath, timeout: 5e3 });
@@ -3481,7 +3568,7 @@ async function cleanupStaleCommits(projectPath) {
3481
3568
  };
3482
3569
  }
3483
3570
  const machineId = await getMachineId();
3484
- const config = await (0, import_core5.loadConfig)();
3571
+ const config = await (0, import_core7.loadConfig)();
3485
3572
  if (!config?.access_token) {
3486
3573
  return {
3487
3574
  success: false,
@@ -3572,9 +3659,9 @@ async function cleanupStaleCommits(projectPath) {
3572
3659
  }
3573
3660
 
3574
3661
  // src/tunnel/cloudflared-manager.ts
3575
- var import_child_process3 = require("child_process");
3576
- var fs4 = __toESM(require("fs"));
3577
- var path5 = __toESM(require("path"));
3662
+ var import_child_process4 = require("child_process");
3663
+ var fs5 = __toESM(require("fs"));
3664
+ var path6 = __toESM(require("path"));
3578
3665
  var os = __toESM(require("os"));
3579
3666
  var https = __toESM(require("https"));
3580
3667
  var tar = __toESM(require("tar"));
@@ -3593,17 +3680,17 @@ var DOWNLOAD_URLS = {
3593
3680
  }
3594
3681
  };
3595
3682
  function getEpisodaBinDir() {
3596
- return path5.join(os.homedir(), ".episoda", "bin");
3683
+ return path6.join(os.homedir(), ".episoda", "bin");
3597
3684
  }
3598
3685
  function getCloudflaredPath() {
3599
3686
  const binaryName = os.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
3600
- return path5.join(getEpisodaBinDir(), binaryName);
3687
+ return path6.join(getEpisodaBinDir(), binaryName);
3601
3688
  }
3602
3689
  function isCloudflaredInPath() {
3603
3690
  try {
3604
3691
  const command = os.platform() === "win32" ? "where" : "which";
3605
3692
  const binaryName = os.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
3606
- const result = (0, import_child_process3.spawnSync)(command, [binaryName], { encoding: "utf-8" });
3693
+ const result = (0, import_child_process4.spawnSync)(command, [binaryName], { encoding: "utf-8" });
3607
3694
  if (result.status === 0 && result.stdout.trim()) {
3608
3695
  return result.stdout.trim().split("\n")[0].trim();
3609
3696
  }
@@ -3614,7 +3701,7 @@ function isCloudflaredInPath() {
3614
3701
  function isCloudflaredInstalled() {
3615
3702
  const cloudflaredPath = getCloudflaredPath();
3616
3703
  try {
3617
- fs4.accessSync(cloudflaredPath, fs4.constants.X_OK);
3704
+ fs5.accessSync(cloudflaredPath, fs5.constants.X_OK);
3618
3705
  return true;
3619
3706
  } catch {
3620
3707
  return false;
@@ -3622,7 +3709,7 @@ function isCloudflaredInstalled() {
3622
3709
  }
3623
3710
  function verifyCloudflared(binaryPath) {
3624
3711
  try {
3625
- const result = (0, import_child_process3.spawnSync)(binaryPath, ["version"], { encoding: "utf-8", timeout: 5e3 });
3712
+ const result = (0, import_child_process4.spawnSync)(binaryPath, ["version"], { encoding: "utf-8", timeout: 5e3 });
3626
3713
  return result.status === 0 && result.stdout.includes("cloudflared");
3627
3714
  } catch {
3628
3715
  return false;
@@ -3638,7 +3725,7 @@ function getDownloadUrl() {
3638
3725
  return platformUrls[arch4] || null;
3639
3726
  }
3640
3727
  async function downloadFile(url, destPath) {
3641
- return new Promise((resolve8, reject) => {
3728
+ return new Promise((resolve9, reject) => {
3642
3729
  const followRedirect = (currentUrl, redirectCount = 0) => {
3643
3730
  if (redirectCount > 5) {
3644
3731
  reject(new Error("Too many redirects"));
@@ -3664,14 +3751,14 @@ async function downloadFile(url, destPath) {
3664
3751
  reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
3665
3752
  return;
3666
3753
  }
3667
- const file = fs4.createWriteStream(destPath);
3754
+ const file = fs5.createWriteStream(destPath);
3668
3755
  response.pipe(file);
3669
3756
  file.on("finish", () => {
3670
3757
  file.close();
3671
- resolve8();
3758
+ resolve9();
3672
3759
  });
3673
3760
  file.on("error", (err) => {
3674
- fs4.unlinkSync(destPath);
3761
+ fs5.unlinkSync(destPath);
3675
3762
  reject(err);
3676
3763
  });
3677
3764
  }).on("error", reject);
@@ -3692,21 +3779,21 @@ async function downloadCloudflared() {
3692
3779
  }
3693
3780
  const binDir = getEpisodaBinDir();
3694
3781
  const cloudflaredPath = getCloudflaredPath();
3695
- fs4.mkdirSync(binDir, { recursive: true });
3782
+ fs5.mkdirSync(binDir, { recursive: true });
3696
3783
  const isTgz = url.endsWith(".tgz");
3697
3784
  if (isTgz) {
3698
- const tempFile = path5.join(binDir, "cloudflared.tgz");
3785
+ const tempFile = path6.join(binDir, "cloudflared.tgz");
3699
3786
  console.log(`[Tunnel] Downloading cloudflared from ${url}...`);
3700
3787
  await downloadFile(url, tempFile);
3701
3788
  console.log("[Tunnel] Extracting cloudflared...");
3702
3789
  await extractTgz(tempFile, binDir);
3703
- fs4.unlinkSync(tempFile);
3790
+ fs5.unlinkSync(tempFile);
3704
3791
  } else {
3705
3792
  console.log(`[Tunnel] Downloading cloudflared from ${url}...`);
3706
3793
  await downloadFile(url, cloudflaredPath);
3707
3794
  }
3708
3795
  if (os.platform() !== "win32") {
3709
- fs4.chmodSync(cloudflaredPath, 493);
3796
+ fs5.chmodSync(cloudflaredPath, 493);
3710
3797
  }
3711
3798
  if (!verifyCloudflared(cloudflaredPath)) {
3712
3799
  throw new Error("Downloaded cloudflared binary failed verification");
@@ -3727,14 +3814,14 @@ async function ensureCloudflared() {
3727
3814
  }
3728
3815
 
3729
3816
  // src/tunnel/tunnel-manager.ts
3730
- var import_child_process4 = require("child_process");
3817
+ var import_child_process5 = require("child_process");
3731
3818
  var import_events = require("events");
3732
- var fs5 = __toESM(require("fs"));
3733
- var path6 = __toESM(require("path"));
3819
+ var fs6 = __toESM(require("fs"));
3820
+ var path7 = __toESM(require("path"));
3734
3821
  var os2 = __toESM(require("os"));
3735
3822
 
3736
3823
  // src/tunnel/tunnel-api.ts
3737
- var import_core6 = __toESM(require_dist());
3824
+ var import_core8 = __toESM(require_dist());
3738
3825
  async function readJsonBody(response) {
3739
3826
  const text = await response.text();
3740
3827
  if (!text) {
@@ -3752,7 +3839,7 @@ function formatNonJsonError(status, text) {
3752
3839
  }
3753
3840
  async function provisionNamedTunnel(moduleId, port = 3e3) {
3754
3841
  console.log(`[TunnelAPI] EP1038: provisionNamedTunnel called for moduleId ${moduleId} with port ${port}`);
3755
- const config = await (0, import_core6.loadConfig)();
3842
+ const config = await (0, import_core8.loadConfig)();
3756
3843
  if (!config?.access_token) {
3757
3844
  return { success: false, error: "Not authenticated" };
3758
3845
  }
@@ -3796,7 +3883,7 @@ async function provisionNamedTunnel(moduleId, port = 3e3) {
3796
3883
  }
3797
3884
  async function provisionNamedTunnelByUid(moduleUid, port = 3e3) {
3798
3885
  console.log(`[TunnelAPI] EP1038: provisionNamedTunnelByUid called for ${moduleUid} with port ${port}`);
3799
- const config = await (0, import_core6.loadConfig)();
3886
+ const config = await (0, import_core8.loadConfig)();
3800
3887
  if (!config?.access_token) {
3801
3888
  return { success: false, error: "Not authenticated" };
3802
3889
  }
@@ -3840,7 +3927,7 @@ async function updateTunnelStatus(moduleUid, status, error) {
3840
3927
  if (!moduleUid || moduleUid === "LOCAL") {
3841
3928
  return;
3842
3929
  }
3843
- const config = await (0, import_core6.loadConfig)();
3930
+ const config = await (0, import_core8.loadConfig)();
3844
3931
  if (!config?.access_token) {
3845
3932
  return;
3846
3933
  }
@@ -3865,7 +3952,7 @@ async function clearTunnelUrl(moduleUid) {
3865
3952
  if (!moduleUid || moduleUid === "LOCAL") {
3866
3953
  return;
3867
3954
  }
3868
- const config = await (0, import_core6.loadConfig)();
3955
+ const config = await (0, import_core8.loadConfig)();
3869
3956
  if (!config?.access_token) {
3870
3957
  return;
3871
3958
  }
@@ -3906,7 +3993,7 @@ function getDaemonModeConfig() {
3906
3993
  }
3907
3994
 
3908
3995
  // src/tunnel/tunnel-manager.ts
3909
- var TUNNEL_PID_DIR = path6.join(os2.homedir(), ".episoda", "tunnels");
3996
+ var TUNNEL_PID_DIR = path7.join(os2.homedir(), ".episoda", "tunnels");
3910
3997
  var TUNNEL_TIMEOUTS = {
3911
3998
  /** Time to wait for Named Tunnel connection (includes API token fetch + connect) */
3912
3999
  NAMED_TUNNEL_CONNECT: 6e4,
@@ -3938,9 +4025,9 @@ var TunnelManager = class extends import_events.EventEmitter {
3938
4025
  */
3939
4026
  ensurePidDir() {
3940
4027
  try {
3941
- if (!fs5.existsSync(TUNNEL_PID_DIR)) {
4028
+ if (!fs6.existsSync(TUNNEL_PID_DIR)) {
3942
4029
  console.log(`[Tunnel] EP904: Creating PID directory: ${TUNNEL_PID_DIR}`);
3943
- fs5.mkdirSync(TUNNEL_PID_DIR, { recursive: true });
4030
+ fs6.mkdirSync(TUNNEL_PID_DIR, { recursive: true });
3944
4031
  console.log(`[Tunnel] EP904: PID directory created successfully`);
3945
4032
  }
3946
4033
  } catch (error) {
@@ -3952,7 +4039,7 @@ var TunnelManager = class extends import_events.EventEmitter {
3952
4039
  * EP877: Get PID file path for a module
3953
4040
  */
3954
4041
  getPidFilePath(moduleUid) {
3955
- return path6.join(TUNNEL_PID_DIR, `${moduleUid}.pid`);
4042
+ return path7.join(TUNNEL_PID_DIR, `${moduleUid}.pid`);
3956
4043
  }
3957
4044
  /**
3958
4045
  * EP877: Write PID to file for tracking across restarts
@@ -3962,7 +4049,7 @@ var TunnelManager = class extends import_events.EventEmitter {
3962
4049
  try {
3963
4050
  this.ensurePidDir();
3964
4051
  const pidPath = this.getPidFilePath(moduleUid);
3965
- fs5.writeFileSync(pidPath, pid.toString(), "utf8");
4052
+ fs6.writeFileSync(pidPath, pid.toString(), "utf8");
3966
4053
  console.log(`[Tunnel] EP904: Wrote PID ${pid} for ${moduleUid} to ${pidPath}`);
3967
4054
  } catch (error) {
3968
4055
  console.error(`[Tunnel] EP904: Failed to write PID file for ${moduleUid}:`, error);
@@ -3975,10 +4062,10 @@ var TunnelManager = class extends import_events.EventEmitter {
3975
4062
  readPidFile(moduleUid) {
3976
4063
  try {
3977
4064
  const pidPath = this.getPidFilePath(moduleUid);
3978
- if (!fs5.existsSync(pidPath)) {
4065
+ if (!fs6.existsSync(pidPath)) {
3979
4066
  return null;
3980
4067
  }
3981
- const content = fs5.readFileSync(pidPath, "utf8").trim();
4068
+ const content = fs6.readFileSync(pidPath, "utf8").trim();
3982
4069
  const pid = parseInt(content, 10);
3983
4070
  if (isNaN(pid) || pid <= 0) {
3984
4071
  console.warn(`[Tunnel] EP948: Invalid PID file content for ${moduleUid}: "${content}", removing stale file`);
@@ -4006,8 +4093,8 @@ var TunnelManager = class extends import_events.EventEmitter {
4006
4093
  removePidFile(moduleUid) {
4007
4094
  try {
4008
4095
  const pidPath = this.getPidFilePath(moduleUid);
4009
- if (fs5.existsSync(pidPath)) {
4010
- fs5.unlinkSync(pidPath);
4096
+ if (fs6.existsSync(pidPath)) {
4097
+ fs6.unlinkSync(pidPath);
4011
4098
  console.log(`[Tunnel] EP877: Removed PID file for ${moduleUid}`);
4012
4099
  }
4013
4100
  } catch (error) {
@@ -4042,7 +4129,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4042
4129
  */
4043
4130
  findCloudflaredProcesses() {
4044
4131
  try {
4045
- const output = (0, import_child_process4.execSync)("pgrep -f cloudflared", { encoding: "utf8" });
4132
+ const output = (0, import_child_process5.execSync)("pgrep -f cloudflared", { encoding: "utf8" });
4046
4133
  return output.trim().split("\n").map((pid) => parseInt(pid, 10)).filter((pid) => !isNaN(pid));
4047
4134
  } catch {
4048
4135
  return [];
@@ -4054,7 +4141,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4054
4141
  */
4055
4142
  getProcessPort(pid) {
4056
4143
  try {
4057
- const output = (0, import_child_process4.execSync)(`ps -p ${pid} -o args=`, { encoding: "utf8" }).trim();
4144
+ const output = (0, import_child_process5.execSync)(`ps -p ${pid} -o args=`, { encoding: "utf8" }).trim();
4058
4145
  const portMatch = output.match(/--url\s+https?:\/\/localhost:(\d+)/);
4059
4146
  if (portMatch) {
4060
4147
  return parseInt(portMatch[1], 10);
@@ -4082,10 +4169,10 @@ var TunnelManager = class extends import_events.EventEmitter {
4082
4169
  const isTracked = Array.from(this.tunnelStates.values()).some((s) => s.info.pid === pid);
4083
4170
  console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`);
4084
4171
  this.killByPid(pid, "SIGTERM");
4085
- await new Promise((resolve8) => setTimeout(resolve8, 500));
4172
+ await new Promise((resolve9) => setTimeout(resolve9, 500));
4086
4173
  if (this.isProcessRunning(pid)) {
4087
4174
  this.killByPid(pid, "SIGKILL");
4088
- await new Promise((resolve8) => setTimeout(resolve8, 200));
4175
+ await new Promise((resolve9) => setTimeout(resolve9, 200));
4089
4176
  }
4090
4177
  killed.push(pid);
4091
4178
  }
@@ -4111,7 +4198,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4111
4198
  }
4112
4199
  try {
4113
4200
  this.ensurePidDir();
4114
- const pidFiles = fs5.readdirSync(TUNNEL_PID_DIR).filter((f) => f.endsWith(".pid"));
4201
+ const pidFiles = fs6.readdirSync(TUNNEL_PID_DIR).filter((f) => f.endsWith(".pid"));
4115
4202
  for (const pidFile of pidFiles) {
4116
4203
  const moduleUid = pidFile.replace(".pid", "");
4117
4204
  const pid = this.readPidFile(moduleUid);
@@ -4119,7 +4206,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4119
4206
  if (!this.tunnelStates.has(moduleUid)) {
4120
4207
  console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`);
4121
4208
  this.killByPid(pid, "SIGTERM");
4122
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
4209
+ await new Promise((resolve9) => setTimeout(resolve9, 1e3));
4123
4210
  if (this.isProcessRunning(pid)) {
4124
4211
  this.killByPid(pid, "SIGKILL");
4125
4212
  }
@@ -4134,7 +4221,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4134
4221
  if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {
4135
4222
  console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`);
4136
4223
  this.killByPid(pid, "SIGTERM");
4137
- await new Promise((resolve8) => setTimeout(resolve8, 500));
4224
+ await new Promise((resolve9) => setTimeout(resolve9, 500));
4138
4225
  if (this.isProcessRunning(pid)) {
4139
4226
  this.killByPid(pid, "SIGKILL");
4140
4227
  }
@@ -4224,7 +4311,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4224
4311
  return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
4225
4312
  }
4226
4313
  }
4227
- return new Promise((resolve8) => {
4314
+ return new Promise((resolve9) => {
4228
4315
  const tunnelInfo = {
4229
4316
  moduleUid,
4230
4317
  url: previewUrl || "",
@@ -4235,7 +4322,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4235
4322
  process: null
4236
4323
  };
4237
4324
  console.log(`[Tunnel] EP948: Starting Named Tunnel for ${moduleUid} with preview URL ${previewUrl}`);
4238
- const process2 = (0, import_child_process4.spawn)(this.cloudflaredPath, [
4325
+ const process2 = (0, import_child_process5.spawn)(this.cloudflaredPath, [
4239
4326
  "tunnel",
4240
4327
  "run",
4241
4328
  "--token",
@@ -4290,7 +4377,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4290
4377
  moduleUid,
4291
4378
  url: tunnelInfo.url
4292
4379
  });
4293
- resolve8({ success: true, url: tunnelInfo.url });
4380
+ resolve9({ success: true, url: tunnelInfo.url });
4294
4381
  }
4295
4382
  };
4296
4383
  process2.stderr?.on("data", (data) => {
@@ -4317,7 +4404,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4317
4404
  onStatusChange?.("error", errorMsg);
4318
4405
  this.emitEvent({ type: "error", moduleUid, error: errorMsg });
4319
4406
  }
4320
- resolve8({ success: false, error: errorMsg });
4407
+ resolve9({ success: false, error: errorMsg });
4321
4408
  } else if (wasConnected) {
4322
4409
  if (currentState && !currentState.intentionallyStopped) {
4323
4410
  console.log(`[Tunnel] EP948: Named tunnel ${moduleUid} crashed unexpectedly, attempting reconnect...`);
@@ -4348,7 +4435,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4348
4435
  this.emitEvent({ type: "error", moduleUid, error: error.message });
4349
4436
  }
4350
4437
  if (!connected) {
4351
- resolve8({ success: false, error: error.message });
4438
+ resolve9({ success: false, error: error.message });
4352
4439
  }
4353
4440
  });
4354
4441
  setTimeout(() => {
@@ -4371,7 +4458,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4371
4458
  onStatusChange?.("error", errorMsg);
4372
4459
  this.emitEvent({ type: "error", moduleUid, error: errorMsg });
4373
4460
  }
4374
- resolve8({ success: false, error: errorMsg });
4461
+ resolve9({ success: false, error: errorMsg });
4375
4462
  }
4376
4463
  }, TUNNEL_TIMEOUTS.NAMED_TUNNEL_CONNECT);
4377
4464
  });
@@ -4432,7 +4519,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4432
4519
  if (orphanPid && this.isProcessRunning(orphanPid)) {
4433
4520
  console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`);
4434
4521
  this.killByPid(orphanPid, "SIGTERM");
4435
- await new Promise((resolve8) => setTimeout(resolve8, 500));
4522
+ await new Promise((resolve9) => setTimeout(resolve9, 500));
4436
4523
  if (this.isProcessRunning(orphanPid)) {
4437
4524
  this.killByPid(orphanPid, "SIGKILL");
4438
4525
  }
@@ -4441,7 +4528,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4441
4528
  const killedOnPort = await this.killCloudflaredOnPort(port);
4442
4529
  if (killedOnPort.length > 0) {
4443
4530
  console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`);
4444
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
4531
+ await new Promise((resolve9) => setTimeout(resolve9, 1e3));
4445
4532
  }
4446
4533
  const cleanup = await this.cleanupOrphanedProcesses();
4447
4534
  if (cleanup.cleaned > 0) {
@@ -4481,7 +4568,7 @@ var TunnelManager = class extends import_events.EventEmitter {
4481
4568
  if (orphanPid && this.isProcessRunning(orphanPid)) {
4482
4569
  console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`);
4483
4570
  this.killByPid(orphanPid, "SIGTERM");
4484
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
4571
+ await new Promise((resolve9) => setTimeout(resolve9, 1e3));
4485
4572
  if (this.isProcessRunning(orphanPid)) {
4486
4573
  this.killByPid(orphanPid, "SIGKILL");
4487
4574
  }
@@ -4497,16 +4584,16 @@ var TunnelManager = class extends import_events.EventEmitter {
4497
4584
  const tunnel = state.info;
4498
4585
  if (tunnel.process && !tunnel.process.killed) {
4499
4586
  tunnel.process.kill("SIGTERM");
4500
- await new Promise((resolve8) => {
4587
+ await new Promise((resolve9) => {
4501
4588
  const timeout = setTimeout(() => {
4502
4589
  if (tunnel.process && !tunnel.process.killed) {
4503
4590
  tunnel.process.kill("SIGKILL");
4504
4591
  }
4505
- resolve8();
4592
+ resolve9();
4506
4593
  }, 3e3);
4507
4594
  tunnel.process.once("exit", () => {
4508
4595
  clearTimeout(timeout);
4509
- resolve8();
4596
+ resolve9();
4510
4597
  });
4511
4598
  });
4512
4599
  }
@@ -4567,14 +4654,14 @@ function getTunnelManager() {
4567
4654
  }
4568
4655
 
4569
4656
  // src/agent/providers/claude-binary.ts
4570
- var import_child_process5 = require("child_process");
4571
- var path7 = __toESM(require("path"));
4572
- var fs6 = __toESM(require("fs"));
4657
+ var import_child_process6 = require("child_process");
4658
+ var path8 = __toESM(require("path"));
4659
+ var fs7 = __toESM(require("fs"));
4573
4660
  var cachedBinaryPath = null;
4574
4661
  function isValidClaudeBinary(binaryPath) {
4575
4662
  try {
4576
- fs6.accessSync(binaryPath, fs6.constants.X_OK);
4577
- const version = (0, import_child_process5.execSync)(`"${binaryPath}" --version`, {
4663
+ fs7.accessSync(binaryPath, fs7.constants.X_OK);
4664
+ const version = (0, import_child_process6.execSync)(`"${binaryPath}" --version`, {
4578
4665
  encoding: "utf-8",
4579
4666
  timeout: 5e3,
4580
4667
  stdio: ["pipe", "pipe", "pipe"]
@@ -4593,7 +4680,7 @@ async function ensureClaudeBinary() {
4593
4680
  return cachedBinaryPath;
4594
4681
  }
4595
4682
  try {
4596
- const pathResult = (0, import_child_process5.execSync)("which claude", {
4683
+ const pathResult = (0, import_child_process6.execSync)("which claude", {
4597
4684
  encoding: "utf-8",
4598
4685
  timeout: 5e3,
4599
4686
  stdio: ["pipe", "pipe", "pipe"]
@@ -4606,20 +4693,20 @@ async function ensureClaudeBinary() {
4606
4693
  }
4607
4694
  const bundledPaths = [
4608
4695
  // In production: node_modules/.bin/claude
4609
- path7.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
4696
+ path8.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
4610
4697
  // In monorepo development: packages/episoda/node_modules/.bin/claude
4611
- path7.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
4698
+ path8.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
4612
4699
  // Root monorepo node_modules
4613
- path7.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
4700
+ path8.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
4614
4701
  ];
4615
4702
  for (const bundledPath of bundledPaths) {
4616
- if (fs6.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
4703
+ if (fs7.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
4617
4704
  cachedBinaryPath = bundledPath;
4618
4705
  return cachedBinaryPath;
4619
4706
  }
4620
4707
  }
4621
4708
  try {
4622
- const npxResult = (0, import_child_process5.execSync)("npx --yes @anthropic-ai/claude-code --version", {
4709
+ const npxResult = (0, import_child_process6.execSync)("npx --yes @anthropic-ai/claude-code --version", {
4623
4710
  encoding: "utf-8",
4624
4711
  timeout: 3e4,
4625
4712
  // npx might need to download
@@ -4638,14 +4725,14 @@ async function ensureClaudeBinary() {
4638
4725
  }
4639
4726
 
4640
4727
  // src/agent/providers/codex-binary.ts
4641
- var import_child_process6 = require("child_process");
4642
- var path8 = __toESM(require("path"));
4643
- var fs7 = __toESM(require("fs"));
4728
+ var import_child_process7 = require("child_process");
4729
+ var path9 = __toESM(require("path"));
4730
+ var fs8 = __toESM(require("fs"));
4644
4731
  var cachedBinaryPath2 = null;
4645
4732
  function isValidCodexBinary(binaryPath) {
4646
4733
  try {
4647
- fs7.accessSync(binaryPath, fs7.constants.X_OK);
4648
- const version = (0, import_child_process6.execSync)(`"${binaryPath}" --version`, {
4734
+ fs8.accessSync(binaryPath, fs8.constants.X_OK);
4735
+ const version = (0, import_child_process7.execSync)(`"${binaryPath}" --version`, {
4649
4736
  encoding: "utf-8",
4650
4737
  timeout: 5e3,
4651
4738
  stdio: ["pipe", "pipe", "pipe"]
@@ -4664,7 +4751,7 @@ async function ensureCodexBinary() {
4664
4751
  return cachedBinaryPath2;
4665
4752
  }
4666
4753
  try {
4667
- const pathResult = (0, import_child_process6.execSync)("which codex", {
4754
+ const pathResult = (0, import_child_process7.execSync)("which codex", {
4668
4755
  encoding: "utf-8",
4669
4756
  timeout: 5e3,
4670
4757
  stdio: ["pipe", "pipe", "pipe"]
@@ -4677,20 +4764,20 @@ async function ensureCodexBinary() {
4677
4764
  }
4678
4765
  const bundledPaths = [
4679
4766
  // In production: node_modules/.bin/codex
4680
- path8.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
4767
+ path9.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
4681
4768
  // In monorepo development: packages/episoda/node_modules/.bin/codex
4682
- path8.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
4769
+ path9.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
4683
4770
  // Root monorepo node_modules
4684
- path8.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
4771
+ path9.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
4685
4772
  ];
4686
4773
  for (const bundledPath of bundledPaths) {
4687
- if (fs7.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
4774
+ if (fs8.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
4688
4775
  cachedBinaryPath2 = bundledPath;
4689
4776
  return cachedBinaryPath2;
4690
4777
  }
4691
4778
  }
4692
4779
  try {
4693
- const npxResult = (0, import_child_process6.execSync)("npx --yes @openai/codex --version", {
4780
+ const npxResult = (0, import_child_process7.execSync)("npx --yes @openai/codex --version", {
4694
4781
  encoding: "utf-8",
4695
4782
  timeout: 3e4,
4696
4783
  // npx might need to download
@@ -4782,13 +4869,13 @@ function generateCodexMcpConfigToml(servers, projectPath) {
4782
4869
  }
4783
4870
 
4784
4871
  // src/agent/agent-control-plane.ts
4785
- var path14 = __toESM(require("path"));
4786
- var fs13 = __toESM(require("fs"));
4872
+ var path15 = __toESM(require("path"));
4873
+ var fs14 = __toESM(require("fs"));
4787
4874
  var os7 = __toESM(require("os"));
4788
- var import_core9 = __toESM(require_dist());
4875
+ var import_core11 = __toESM(require_dist());
4789
4876
 
4790
4877
  // src/agent/runtime/claude-runtime.ts
4791
- var import_child_process7 = require("child_process");
4878
+ var import_child_process8 = require("child_process");
4792
4879
 
4793
4880
  // src/agent/runtime/runtime-process-utils.ts
4794
4881
  async function writeToStdinWithBackpressure(process2, data, drainTimeoutMs, label, sessionId) {
@@ -4796,15 +4883,15 @@ async function writeToStdinWithBackpressure(process2, data, drainTimeoutMs, labe
4796
4883
  if (!stdin || stdin.destroyed) {
4797
4884
  throw new Error(`[${label}] stdin not available. session=${sessionId}`);
4798
4885
  }
4799
- await new Promise((resolve8, reject) => {
4886
+ await new Promise((resolve9, reject) => {
4800
4887
  const ok = stdin.write(data);
4801
4888
  if (ok) {
4802
- resolve8();
4889
+ resolve9();
4803
4890
  return;
4804
4891
  }
4805
4892
  const onDrain = () => {
4806
4893
  cleanup();
4807
- resolve8();
4894
+ resolve9();
4808
4895
  };
4809
4896
  const onError = (err) => {
4810
4897
  cleanup();
@@ -4825,18 +4912,18 @@ async function writeToStdinWithBackpressure(process2, data, drainTimeoutMs, labe
4825
4912
  });
4826
4913
  }
4827
4914
  function waitForProcessExit(process2, alive, timeoutMs) {
4828
- return new Promise((resolve8) => {
4915
+ return new Promise((resolve9) => {
4829
4916
  if (!process2 || !alive) {
4830
- resolve8(true);
4917
+ resolve9(true);
4831
4918
  return;
4832
4919
  }
4833
4920
  const onExit = () => {
4834
4921
  clearTimeout(timer);
4835
- resolve8(true);
4922
+ resolve9(true);
4836
4923
  };
4837
4924
  const timer = setTimeout(() => {
4838
4925
  process2.removeListener("exit", onExit);
4839
- resolve8(false);
4926
+ resolve9(false);
4840
4927
  }, timeoutMs);
4841
4928
  process2.once("exit", onExit);
4842
4929
  });
@@ -5024,7 +5111,7 @@ var ClaudePersistentRuntime = class {
5024
5111
  this.spawnTimestamp = Date.now();
5025
5112
  const spawnRssMb = Math.round(process.memoryUsage().rss / 1024 / 1024);
5026
5113
  console.log(`[ClaudePersistentRuntime] Spawning persistent Claude process for session ${this.sessionId}, RSS=${spawnRssMb}MB`);
5027
- this.process = (0, import_child_process7.spawn)(binaryPath, args, {
5114
+ this.process = (0, import_child_process8.spawn)(binaryPath, args, {
5028
5115
  cwd,
5029
5116
  env,
5030
5117
  // CLAUDE_CODE_DISABLE_PLUGIN_CACHE=1 is set by agent-manager in envVars
@@ -5429,7 +5516,7 @@ var ClaudePersistentRuntime = class {
5429
5516
  };
5430
5517
 
5431
5518
  // src/agent/runtime/codex-runtime.ts
5432
- var import_child_process8 = require("child_process");
5519
+ var import_child_process9 = require("child_process");
5433
5520
  var INIT_TIMEOUT_MS = 3e4;
5434
5521
  var INACTIVITY_TIMEOUT_MS2 = parseInt(process.env.AGENT_STREAM_INACTIVITY_TIMEOUT_MS || "180000", 10);
5435
5522
  var SHUTDOWN_SIGTERM_WAIT_MS2 = 2e3;
@@ -5486,7 +5573,7 @@ var CodexPersistentRuntime = class {
5486
5573
  this.spawnTimestamp = Date.now();
5487
5574
  const spawnRssMb = Math.round(process.memoryUsage().rss / 1024 / 1024);
5488
5575
  console.log(`[CodexPersistentRuntime] Spawning persistent Codex app-server for session ${this.sessionId}, RSS=${spawnRssMb}MB`);
5489
- this.process = (0, import_child_process8.spawn)(binaryPath, args, {
5576
+ this.process = (0, import_child_process9.spawn)(binaryPath, args, {
5490
5577
  cwd,
5491
5578
  env,
5492
5579
  stdio: ["pipe", "pipe", "pipe"]
@@ -5832,10 +5919,10 @@ var CodexPersistentRuntime = class {
5832
5919
  }
5833
5920
  waitForThreadId(timeoutMs) {
5834
5921
  if (this._agentSessionId) return Promise.resolve(this._agentSessionId);
5835
- return new Promise((resolve8, reject) => {
5922
+ return new Promise((resolve9, reject) => {
5836
5923
  const onId = (id) => {
5837
5924
  clearTimeout(timer);
5838
- resolve8(id);
5925
+ resolve9(id);
5839
5926
  };
5840
5927
  const timer = setTimeout(() => {
5841
5928
  this.threadIdWaiters = this.threadIdWaiters.filter((w) => w !== onId);
@@ -5866,12 +5953,12 @@ var CodexPersistentRuntime = class {
5866
5953
  sendRequest(method, params, timeoutMs = INIT_TIMEOUT_MS) {
5867
5954
  const id = this.nextId++;
5868
5955
  const msg = { jsonrpc: "2.0", id, method, params };
5869
- return new Promise(async (resolve8, reject) => {
5956
+ return new Promise(async (resolve9, reject) => {
5870
5957
  const timeout = setTimeout(() => {
5871
5958
  this.pending.delete(id);
5872
5959
  reject(new Error(`JSON-RPC timeout: ${method}`));
5873
5960
  }, timeoutMs);
5874
- this.pending.set(id, { resolve: resolve8, reject, timeout });
5961
+ this.pending.set(id, { resolve: resolve9, reject, timeout });
5875
5962
  try {
5876
5963
  await this.writeToStdin(JSON.stringify(msg) + "\n");
5877
5964
  } catch (err) {
@@ -6021,11 +6108,11 @@ var UnifiedAgentRuntime = class {
6021
6108
  if (!this.currentTurn && this.impl.turnState === "idle") {
6022
6109
  return this.dispatchTurn(message, callbacks);
6023
6110
  }
6024
- return new Promise((resolve8, reject) => {
6111
+ return new Promise((resolve9, reject) => {
6025
6112
  this.queuedTurns.push({
6026
6113
  message,
6027
6114
  callbacks,
6028
- resolve: resolve8,
6115
+ resolve: resolve9,
6029
6116
  reject
6030
6117
  });
6031
6118
  });
@@ -6220,18 +6307,18 @@ var AgentSessionManager = class {
6220
6307
  };
6221
6308
 
6222
6309
  // src/agent/credential-manager.ts
6223
- var path9 = __toESM(require("path"));
6224
- var fs8 = __toESM(require("fs"));
6310
+ var path10 = __toESM(require("path"));
6311
+ var fs9 = __toESM(require("fs"));
6225
6312
  var os3 = __toESM(require("os"));
6226
- var import_core7 = __toESM(require_dist());
6313
+ var import_core9 = __toESM(require_dist());
6227
6314
  var CredentialManager = class {
6228
6315
  // ---------------------------------------------------------------------------
6229
6316
  // Token file reading
6230
6317
  // ---------------------------------------------------------------------------
6231
6318
  readJsonFileIfExists(filePath) {
6232
- if (!fs8.existsSync(filePath)) return null;
6319
+ if (!fs9.existsSync(filePath)) return null;
6233
6320
  try {
6234
- const content = fs8.readFileSync(filePath, "utf8");
6321
+ const content = fs9.readFileSync(filePath, "utf8");
6235
6322
  return JSON.parse(content);
6236
6323
  } catch (error) {
6237
6324
  console.warn(`[CredentialManager] Failed to parse JSON at ${filePath}:`, error instanceof Error ? error.message : error);
@@ -6270,12 +6357,12 @@ var CredentialManager = class {
6270
6357
  */
6271
6358
  loadProviderTokens(provider, sessionDir, legacyDir) {
6272
6359
  if (provider === "codex") {
6273
- const sessionAuthPath = path9.join(sessionDir, "auth.json");
6274
- const legacyAuthPath = path9.join(legacyDir, "auth.json");
6360
+ const sessionAuthPath = path10.join(sessionDir, "auth.json");
6361
+ const legacyAuthPath = path10.join(legacyDir, "auth.json");
6275
6362
  return this.readCodexTokensFromPath(sessionAuthPath) || this.readCodexTokensFromPath(legacyAuthPath);
6276
6363
  }
6277
- const sessionCredentialsPath = path9.join(sessionDir, ".credentials.json");
6278
- const legacyCredentialsPath = path9.join(legacyDir, ".credentials.json");
6364
+ const sessionCredentialsPath = path10.join(sessionDir, ".credentials.json");
6365
+ const legacyCredentialsPath = path10.join(legacyDir, ".credentials.json");
6279
6366
  return this.readClaudeTokensFromPath(sessionCredentialsPath) || this.readClaudeTokensFromPath(legacyCredentialsPath);
6280
6367
  }
6281
6368
  // ---------------------------------------------------------------------------
@@ -6329,7 +6416,7 @@ var CredentialManager = class {
6329
6416
  return { apiUrl: envApiUrl || "https://episoda.dev", token: envToken };
6330
6417
  }
6331
6418
  try {
6332
- const config = await (0, import_core7.loadConfig)();
6419
+ const config = await (0, import_core9.loadConfig)();
6333
6420
  if (!config?.access_token) {
6334
6421
  return null;
6335
6422
  }
@@ -6390,10 +6477,10 @@ var CredentialManager = class {
6390
6477
  cleanupSessionCredentials(sessionId, provider) {
6391
6478
  const providers = provider ? [provider] : ["claude", "codex"];
6392
6479
  for (const p of providers) {
6393
- const sessionDir = p === "codex" ? path9.join(os3.homedir(), ".codex", "sessions", sessionId) : path9.join(os3.homedir(), ".claude", "sessions", sessionId);
6480
+ const sessionDir = p === "codex" ? path10.join(os3.homedir(), ".codex", "sessions", sessionId) : path10.join(os3.homedir(), ".claude", "sessions", sessionId);
6394
6481
  try {
6395
- if (fs8.existsSync(sessionDir)) {
6396
- fs8.rmSync(sessionDir, { recursive: true, force: true });
6482
+ if (fs9.existsSync(sessionDir)) {
6483
+ fs9.rmSync(sessionDir, { recursive: true, force: true });
6397
6484
  console.log(`[CredentialManager] EP1260: Cleaned up ${p} session credentials: ${sessionDir}`);
6398
6485
  }
6399
6486
  } catch (error) {
@@ -6404,10 +6491,10 @@ var CredentialManager = class {
6404
6491
  };
6405
6492
 
6406
6493
  // src/agent/mcp-server-configurator.ts
6407
- var path10 = __toESM(require("path"));
6408
- var fs9 = __toESM(require("fs"));
6494
+ var path11 = __toESM(require("path"));
6495
+ var fs10 = __toESM(require("fs"));
6409
6496
  var os4 = __toESM(require("os"));
6410
- var import_core8 = __toESM(require_dist());
6497
+ var import_core10 = __toESM(require_dist());
6411
6498
 
6412
6499
  // src/utils/github-token.ts
6413
6500
  var INVALID_GITHUB_TOKEN_LITERALS = /* @__PURE__ */ new Set([
@@ -6440,7 +6527,7 @@ var McpServerConfigurator = class {
6440
6527
  return { apiUrl: envApiUrl || "https://episoda.dev", token: envToken, source: "env" };
6441
6528
  }
6442
6529
  try {
6443
- const config = await (0, import_core8.loadConfig)();
6530
+ const config = await (0, import_core10.loadConfig)();
6444
6531
  if (config?.access_token) {
6445
6532
  return {
6446
6533
  apiUrl: config.api_url || envApiUrl || "https://episoda.dev",
@@ -6514,13 +6601,13 @@ var McpServerConfigurator = class {
6514
6601
  findCodexRequirementsToml(sessionProjectPath) {
6515
6602
  const candidates = [];
6516
6603
  if (sessionProjectPath) {
6517
- candidates.push(path10.join(sessionProjectPath, ".codex", "requirements.toml"));
6604
+ candidates.push(path11.join(sessionProjectPath, ".codex", "requirements.toml"));
6518
6605
  }
6519
- const codexHome = process.env.CODEX_HOME || path10.join(os4.homedir(), ".codex");
6520
- candidates.push(path10.join(codexHome, "requirements.toml"));
6606
+ const codexHome = process.env.CODEX_HOME || path11.join(os4.homedir(), ".codex");
6607
+ candidates.push(path11.join(codexHome, "requirements.toml"));
6521
6608
  candidates.push("/etc/codex/requirements.toml");
6522
6609
  for (const p of candidates) {
6523
- if (fs9.existsSync(p)) return p;
6610
+ if (fs10.existsSync(p)) return p;
6524
6611
  }
6525
6612
  return null;
6526
6613
  }
@@ -6558,9 +6645,9 @@ var McpServerConfigurator = class {
6558
6645
  };
6559
6646
 
6560
6647
  // src/agent/providers/claude-provider.ts
6561
- var fs10 = __toESM(require("fs"));
6648
+ var fs11 = __toESM(require("fs"));
6562
6649
  var os5 = __toESM(require("os"));
6563
- var path11 = __toESM(require("path"));
6650
+ var path12 = __toESM(require("path"));
6564
6651
 
6565
6652
  // src/agent/providers/claude-config.ts
6566
6653
  var import_crypto = require("crypto");
@@ -6880,8 +6967,8 @@ var ClaudeProvider = class _ClaudeProvider {
6880
6967
  }
6881
6968
  getSessionDirs(sessionId) {
6882
6969
  return {
6883
- sessionConfigDir: path11.join(os5.homedir(), ".claude", "sessions", sessionId),
6884
- legacyConfigDir: path11.join(os5.homedir(), ".claude")
6970
+ sessionConfigDir: path12.join(os5.homedir(), ".claude", "sessions", sessionId),
6971
+ legacyConfigDir: path12.join(os5.homedir(), ".claude")
6885
6972
  };
6886
6973
  }
6887
6974
  async buildSpawnArgs(options) {
@@ -6986,11 +7073,11 @@ ${message}`;
6986
7073
  }
6987
7074
  async writeSessionConfig(options) {
6988
7075
  const { session, dirs, useOAuth, useApiKey } = options;
6989
- const statsigDir = path11.join(dirs.sessionConfigDir, "statsig");
6990
- fs10.mkdirSync(statsigDir, { recursive: true });
7076
+ const statsigDir = path12.join(dirs.sessionConfigDir, "statsig");
7077
+ fs11.mkdirSync(statsigDir, { recursive: true });
6991
7078
  console.log(`[ClaudeProvider] EP1260: Created session-specific Claude dir: ${dirs.sessionConfigDir}`);
6992
7079
  if (useOAuth) {
6993
- const credentialsPath = path11.join(dirs.sessionConfigDir, ".credentials.json");
7080
+ const credentialsPath = path12.join(dirs.sessionConfigDir, ".credentials.json");
6994
7081
  const oauthCredentials = {
6995
7082
  accessToken: session.credentials.oauthToken
6996
7083
  };
@@ -7004,7 +7091,7 @@ ${message}`;
7004
7091
  oauthCredentials.scopes = session.credentials.scopes;
7005
7092
  }
7006
7093
  const credentialsContent = JSON.stringify({ claudeAiOauth: oauthCredentials }, null, 2);
7007
- fs10.writeFileSync(credentialsPath, credentialsContent, { mode: 384 });
7094
+ fs11.writeFileSync(credentialsPath, credentialsContent, { mode: 384 });
7008
7095
  console.log(`[ClaudeProvider] EP1260: Wrote OAuth credentials to ${credentialsPath}`);
7009
7096
  try {
7010
7097
  const claudeConfig = generateClaudeConfig({
@@ -7016,11 +7103,11 @@ ${message}`;
7016
7103
  if (!hasEvaluations || !hasStableId) {
7017
7104
  throw new Error("Invalid statsig config: missing required files");
7018
7105
  }
7019
- const settingsPath = path11.join(dirs.sessionConfigDir, "settings.json");
7020
- fs10.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
7106
+ const settingsPath = path12.join(dirs.sessionConfigDir, "settings.json");
7107
+ fs11.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
7021
7108
  for (const [filename, content] of Object.entries(claudeConfig.statsig)) {
7022
- const filePath = path11.join(statsigDir, filename);
7023
- fs10.writeFileSync(filePath, content, { mode: 420 });
7109
+ const filePath = path12.join(statsigDir, filename);
7110
+ fs11.writeFileSync(filePath, content, { mode: 420 });
7024
7111
  }
7025
7112
  console.log("[ClaudeProvider] EP1260: Wrote Claude config files to session directory");
7026
7113
  } catch (configError) {
@@ -7053,9 +7140,9 @@ If changes are needed, explain what needs to be done.`;
7053
7140
  };
7054
7141
 
7055
7142
  // src/agent/providers/codex-provider.ts
7056
- var fs11 = __toESM(require("fs"));
7143
+ var fs12 = __toESM(require("fs"));
7057
7144
  var os6 = __toESM(require("os"));
7058
- var path12 = __toESM(require("path"));
7145
+ var path13 = __toESM(require("path"));
7059
7146
  var CodexProvider = class {
7060
7147
  constructor(mcpConfigurator) {
7061
7148
  this.mcpConfigurator = mcpConfigurator;
@@ -7070,8 +7157,8 @@ var CodexProvider = class {
7070
7157
  }
7071
7158
  getSessionDirs(sessionId) {
7072
7159
  return {
7073
- sessionConfigDir: path12.join(os6.homedir(), ".codex", "sessions", sessionId),
7074
- legacyConfigDir: path12.join(os6.homedir(), ".codex")
7160
+ sessionConfigDir: path13.join(os6.homedir(), ".codex", "sessions", sessionId),
7161
+ legacyConfigDir: path13.join(os6.homedir(), ".codex")
7075
7162
  };
7076
7163
  }
7077
7164
  async buildSpawnArgs(options) {
@@ -7126,7 +7213,7 @@ ${message}`;
7126
7213
  }
7127
7214
  async writeSessionConfig(options) {
7128
7215
  const { session, dirs, useOAuth, useApiKey, mcpAuth, mcpServers } = options;
7129
- fs11.mkdirSync(dirs.sessionConfigDir, { recursive: true });
7216
+ fs12.mkdirSync(dirs.sessionConfigDir, { recursive: true });
7130
7217
  console.log(`[CodexProvider] EP1260: Created session-specific Codex dir: ${dirs.sessionConfigDir}`);
7131
7218
  if (useOAuth) {
7132
7219
  const codexConfig = generateCodexConfig({
@@ -7136,8 +7223,8 @@ ${message}`;
7136
7223
  accountId: session.credentials.accountId,
7137
7224
  expiresAt: session.credentials.expiresAt
7138
7225
  });
7139
- const authJsonPath = path12.join(dirs.sessionConfigDir, "auth.json");
7140
- fs11.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
7226
+ const authJsonPath = path13.join(dirs.sessionConfigDir, "auth.json");
7227
+ fs12.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
7141
7228
  console.log(`[CodexProvider] EP1260: Wrote Codex auth.json to ${authJsonPath}`);
7142
7229
  } else if (useApiKey) {
7143
7230
  console.log("[CodexProvider] EP1133: Using Codex with API key (OPENAI_API_KEY)");
@@ -7145,7 +7232,7 @@ ${message}`;
7145
7232
  if (mcpServers.length > 0) {
7146
7233
  const requirementsPath = this.mcpConfigurator.findCodexRequirementsToml(session.projectPath);
7147
7234
  if (requirementsPath) {
7148
- const requirementsContent = fs11.readFileSync(requirementsPath, "utf8");
7235
+ const requirementsContent = fs12.readFileSync(requirementsPath, "utf8");
7149
7236
  const allowlist = this.mcpConfigurator.parseRequirementsMcpAllowlist(requirementsContent);
7150
7237
  const allowedIds = Array.from(allowlist.keys());
7151
7238
  if (allowedIds.length > 0) {
@@ -7226,16 +7313,16 @@ ${message}`;
7226
7313
  env
7227
7314
  };
7228
7315
  });
7229
- const configTomlPath = path12.join(dirs.sessionConfigDir, "config.toml");
7316
+ const configTomlPath = path13.join(dirs.sessionConfigDir, "config.toml");
7230
7317
  const configTomlContent2 = generateCodexMcpConfigToml(codexMcpServers, session.projectPath);
7231
- fs11.writeFileSync(configTomlPath, configTomlContent2, { mode: 384 });
7318
+ fs12.writeFileSync(configTomlPath, configTomlContent2, { mode: 384 });
7232
7319
  console.log(`[CodexProvider] EP1287: Wrote Codex config.toml at ${configTomlPath} with ${codexMcpServers.length} MCP server(s): ${codexMcpServers.map((s) => s.name).join(", ")}`);
7233
7320
  return;
7234
7321
  }
7235
7322
  const configTomlContent = generateCodexMcpConfigToml([], session.projectPath);
7236
7323
  if (configTomlContent.trim()) {
7237
- const configTomlPath = path12.join(dirs.sessionConfigDir, "config.toml");
7238
- fs11.writeFileSync(configTomlPath, configTomlContent, { mode: 420 });
7324
+ const configTomlPath = path13.join(dirs.sessionConfigDir, "config.toml");
7325
+ fs12.writeFileSync(configTomlPath, configTomlContent, { mode: 420 });
7239
7326
  console.log(`[CodexProvider] EP1287: Wrote Codex config.toml (project trust only) at ${configTomlPath}`);
7240
7327
  }
7241
7328
  }
@@ -7534,18 +7621,18 @@ async function startPersistentRuntimeMessage(params) {
7534
7621
  }
7535
7622
 
7536
7623
  // src/agent/agent-process-metrics.ts
7537
- var import_child_process9 = require("child_process");
7624
+ var import_child_process10 = require("child_process");
7538
7625
  async function getChildProcessRssMb(pid) {
7539
7626
  if (!pid || pid <= 0) return null;
7540
7627
  try {
7541
- if (typeof import_child_process9.execFile !== "function") return null;
7542
- const stdout = await new Promise((resolve8, reject) => {
7543
- (0, import_child_process9.execFile)("ps", ["-o", "rss=", "-p", String(pid)], { timeout: 1e3 }, (err, out) => {
7628
+ if (typeof import_child_process10.execFile !== "function") return null;
7629
+ const stdout = await new Promise((resolve9, reject) => {
7630
+ (0, import_child_process10.execFile)("ps", ["-o", "rss=", "-p", String(pid)], { timeout: 1e3 }, (err, out) => {
7544
7631
  if (err) {
7545
7632
  reject(err);
7546
7633
  return;
7547
7634
  }
7548
- resolve8(String(out));
7635
+ resolve9(String(out));
7549
7636
  });
7550
7637
  });
7551
7638
  const kb = Number(String(stdout).trim());
@@ -7557,18 +7644,18 @@ async function getChildProcessRssMb(pid) {
7557
7644
  }
7558
7645
 
7559
7646
  // src/agent/agent-pid-utils.ts
7560
- var fs12 = __toESM(require("fs"));
7561
- var path13 = __toESM(require("path"));
7647
+ var fs13 = __toESM(require("fs"));
7648
+ var path14 = __toESM(require("path"));
7562
7649
  async function cleanupOrphanedAgentProcesses(pidDir) {
7563
7650
  let cleaned = 0;
7564
- if (!fs12.existsSync(pidDir)) {
7651
+ if (!fs13.existsSync(pidDir)) {
7565
7652
  return { cleaned };
7566
7653
  }
7567
- const pidFiles = fs12.readdirSync(pidDir).filter((f) => f.endsWith(".pid"));
7654
+ const pidFiles = fs13.readdirSync(pidDir).filter((f) => f.endsWith(".pid"));
7568
7655
  for (const pidFile of pidFiles) {
7569
- const pidPath = path13.join(pidDir, pidFile);
7656
+ const pidPath = path14.join(pidDir, pidFile);
7570
7657
  try {
7571
- const pidStr = fs12.readFileSync(pidPath, "utf-8").trim();
7658
+ const pidStr = fs13.readFileSync(pidPath, "utf-8").trim();
7572
7659
  const pid = parseInt(pidStr, 10);
7573
7660
  if (!isNaN(pid)) {
7574
7661
  try {
@@ -7579,7 +7666,7 @@ async function cleanupOrphanedAgentProcesses(pidDir) {
7579
7666
  } catch {
7580
7667
  }
7581
7668
  }
7582
- fs12.unlinkSync(pidPath);
7669
+ fs13.unlinkSync(pidPath);
7583
7670
  } catch (error) {
7584
7671
  console.warn(`[AgentManager] Error cleaning PID file ${pidFile}:`, error);
7585
7672
  }
@@ -7590,14 +7677,14 @@ async function cleanupOrphanedAgentProcesses(pidDir) {
7590
7677
  return { cleaned };
7591
7678
  }
7592
7679
  function writeAgentPidFile(pidDir, sessionId, pid) {
7593
- const pidPath = path13.join(pidDir, `${sessionId}.pid`);
7594
- fs12.writeFileSync(pidPath, pid.toString());
7680
+ const pidPath = path14.join(pidDir, `${sessionId}.pid`);
7681
+ fs13.writeFileSync(pidPath, pid.toString());
7595
7682
  }
7596
7683
  function removeAgentPidFile(pidDir, sessionId) {
7597
- const pidPath = path13.join(pidDir, `${sessionId}.pid`);
7684
+ const pidPath = path14.join(pidDir, `${sessionId}.pid`);
7598
7685
  try {
7599
- if (fs12.existsSync(pidPath)) {
7600
- fs12.unlinkSync(pidPath);
7686
+ if (fs13.existsSync(pidPath)) {
7687
+ fs13.unlinkSync(pidPath);
7601
7688
  }
7602
7689
  } catch {
7603
7690
  }
@@ -7617,7 +7704,7 @@ var AgentControlPlane = class {
7617
7704
  this.initialized = false;
7618
7705
  // EP1133: Lock for config file writes to prevent race conditions
7619
7706
  this.configWriteLock = Promise.resolve();
7620
- this.pidDir = path14.join(os7.homedir(), ".episoda", "agent-pids");
7707
+ this.pidDir = path15.join(os7.homedir(), ".episoda", "agent-pids");
7621
7708
  this.runtimeManager = new AgentSessionManager(this);
7622
7709
  this.credentialManager = new CredentialManager();
7623
7710
  this.mcpConfigurator = new McpServerConfigurator();
@@ -7648,8 +7735,8 @@ var AgentControlPlane = class {
7648
7735
  async withConfigLock(fn) {
7649
7736
  const previousLock = this.configWriteLock;
7650
7737
  let releaseLock;
7651
- this.configWriteLock = new Promise((resolve8) => {
7652
- releaseLock = resolve8;
7738
+ this.configWriteLock = new Promise((resolve9) => {
7739
+ releaseLock = resolve9;
7653
7740
  });
7654
7741
  try {
7655
7742
  await previousLock;
@@ -7685,8 +7772,8 @@ var AgentControlPlane = class {
7685
7772
  return;
7686
7773
  }
7687
7774
  console.log("[AgentManager] Initializing...");
7688
- if (!fs13.existsSync(this.pidDir)) {
7689
- fs13.mkdirSync(this.pidDir, { recursive: true });
7775
+ if (!fs14.existsSync(this.pidDir)) {
7776
+ fs14.mkdirSync(this.pidDir, { recursive: true });
7690
7777
  }
7691
7778
  await this.cleanupOrphanedProcesses();
7692
7779
  for (const providerImpl of Object.values(this.providers)) {
@@ -7761,7 +7848,7 @@ var AgentControlPlane = class {
7761
7848
  let effectiveWorkspaceId = requestedWorkspaceId;
7762
7849
  if (!effectiveWorkspaceId) {
7763
7850
  try {
7764
- const config = await (0, import_core9.loadConfig)();
7851
+ const config = await (0, import_core11.loadConfig)();
7765
7852
  effectiveWorkspaceId = normalizeWorkspaceId(config?.workspace_id);
7766
7853
  } catch (error) {
7767
7854
  console.warn("[AgentManager] EP1357: Unable to resolve workspaceId from local config:", error instanceof Error ? error.message : error);
@@ -8203,12 +8290,12 @@ var AgentCommandQueue = class {
8203
8290
  const now = Date.now();
8204
8291
  let cleaned = 0;
8205
8292
  for (const [sessionId, queue] of this.pendingCommands) {
8206
- const valid3 = queue.filter((pending) => now - pending.receivedAt <= this.commandTimeout);
8207
- cleaned += queue.length - valid3.length;
8208
- if (valid3.length === 0) {
8293
+ const valid2 = queue.filter((pending) => now - pending.receivedAt <= this.commandTimeout);
8294
+ cleaned += queue.length - valid2.length;
8295
+ if (valid2.length === 0) {
8209
8296
  this.pendingCommands.delete(sessionId);
8210
8297
  } else {
8211
- this.pendingCommands.set(sessionId, valid3);
8298
+ this.pendingCommands.set(sessionId, valid2);
8212
8299
  }
8213
8300
  }
8214
8301
  if (cleaned > 0) {
@@ -8270,10 +8357,10 @@ var AgentCommandQueue = class {
8270
8357
  };
8271
8358
 
8272
8359
  // src/daemon/worktree-manager.ts
8273
- var fs14 = __toESM(require("fs"));
8274
- var path15 = __toESM(require("path"));
8275
- var import_child_process10 = require("child_process");
8276
- var import_core10 = __toESM(require_dist());
8360
+ var fs15 = __toESM(require("fs"));
8361
+ var path16 = __toESM(require("path"));
8362
+ var import_child_process11 = require("child_process");
8363
+ var import_core12 = __toESM(require_dist());
8277
8364
  function validateModuleUid(moduleUid) {
8278
8365
  if (!moduleUid || typeof moduleUid !== "string" || !moduleUid.trim()) {
8279
8366
  return false;
@@ -8308,7 +8395,7 @@ function isValidDefaultBranchName(name) {
8308
8395
  return true;
8309
8396
  }
8310
8397
  function runGit(args, cwd, timeoutMs) {
8311
- const res = (0, import_child_process10.spawnSync)("git", args, {
8398
+ const res = (0, import_child_process11.spawnSync)("git", args, {
8312
8399
  cwd,
8313
8400
  encoding: "utf-8",
8314
8401
  timeout: timeoutMs,
@@ -8328,9 +8415,9 @@ var WorktreeManager = class _WorktreeManager {
8328
8415
  // ============================================================
8329
8416
  this.lockPath = "";
8330
8417
  this.projectRoot = projectRoot;
8331
- this.bareRepoPath = path15.join(projectRoot, ".bare");
8332
- this.configPath = path15.join(projectRoot, ".episoda", "config.json");
8333
- this.gitExecutor = new import_core10.GitExecutor();
8418
+ this.bareRepoPath = path16.join(projectRoot, ".bare");
8419
+ this.configPath = path16.join(projectRoot, ".episoda", "config.json");
8420
+ this.gitExecutor = new import_core12.GitExecutor();
8334
8421
  }
8335
8422
  /**
8336
8423
  * Initialize worktree manager from existing project root
@@ -8340,13 +8427,13 @@ var WorktreeManager = class _WorktreeManager {
8340
8427
  */
8341
8428
  async initialize() {
8342
8429
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
8343
- if (!fs14.existsSync(this.bareRepoPath)) {
8430
+ if (!fs15.existsSync(this.bareRepoPath)) {
8344
8431
  if (debug) {
8345
8432
  console.log(`[WorktreeManager] initialize: .bare not found at ${this.bareRepoPath}`);
8346
8433
  }
8347
8434
  return false;
8348
8435
  }
8349
- if (!fs14.existsSync(this.configPath)) {
8436
+ if (!fs15.existsSync(this.configPath)) {
8350
8437
  if (debug) {
8351
8438
  console.log(`[WorktreeManager] initialize: config not found at ${this.configPath}`);
8352
8439
  }
@@ -8407,8 +8494,8 @@ var WorktreeManager = class _WorktreeManager {
8407
8494
  */
8408
8495
  static async createProject(projectRoot, repoUrl, projectId, workspaceSlug, projectSlug) {
8409
8496
  const manager = new _WorktreeManager(projectRoot);
8410
- const episodaDir = path15.join(projectRoot, ".episoda");
8411
- fs14.mkdirSync(episodaDir, { recursive: true });
8497
+ const episodaDir = path16.join(projectRoot, ".episoda");
8498
+ fs15.mkdirSync(episodaDir, { recursive: true });
8412
8499
  const cloneResult = await manager.gitExecutor.execute({
8413
8500
  action: "clone_bare",
8414
8501
  url: repoUrl,
@@ -8472,8 +8559,8 @@ var WorktreeManager = class _WorktreeManager {
8472
8559
  error: `Invalid module UID: "${moduleUid}" - contains disallowed characters`
8473
8560
  };
8474
8561
  }
8475
- const worktreePath = path15.join(this.projectRoot, moduleUid);
8476
- const normalizedWorktreePath = path15.resolve(worktreePath);
8562
+ const worktreePath = path16.join(this.projectRoot, moduleUid);
8563
+ const normalizedWorktreePath = path16.resolve(worktreePath);
8477
8564
  const lockAcquired = await this.acquireLock();
8478
8565
  if (!lockAcquired) {
8479
8566
  return {
@@ -8490,12 +8577,12 @@ var WorktreeManager = class _WorktreeManager {
8490
8577
  worktreeInfo: existing
8491
8578
  };
8492
8579
  }
8493
- if (fs14.existsSync(worktreePath)) {
8580
+ if (fs15.existsSync(worktreePath)) {
8494
8581
  const listResult = await this.gitExecutor.execute({
8495
8582
  action: "worktree_list"
8496
8583
  }, { cwd: this.bareRepoPath });
8497
8584
  const worktrees = listResult.details?.worktrees || [];
8498
- const matching = worktrees.find((w) => path15.resolve(w.path) === normalizedWorktreePath);
8585
+ const matching = worktrees.find((w) => path16.resolve(w.path) === normalizedWorktreePath);
8499
8586
  if (matching) {
8500
8587
  const adoptedWorktree = {
8501
8588
  moduleUid,
@@ -8523,7 +8610,7 @@ var WorktreeManager = class _WorktreeManager {
8523
8610
  };
8524
8611
  }
8525
8612
  console.warn(`[WorktreeManager] EP1265: Removing stale worktree path at ${worktreePath}`);
8526
- fs14.rmSync(worktreePath, { recursive: true, force: true });
8613
+ fs15.rmSync(worktreePath, { recursive: true, force: true });
8527
8614
  const pruneResult = await this.gitExecutor.execute({
8528
8615
  action: "worktree_prune"
8529
8616
  }, { cwd: this.bareRepoPath });
@@ -8718,8 +8805,8 @@ var WorktreeManager = class _WorktreeManager {
8718
8805
  const allWorktrees = this.listWorktrees();
8719
8806
  const activeSet = new Set(activeModuleUids);
8720
8807
  const orphaned = allWorktrees.filter((w) => !activeSet.has(w.moduleUid));
8721
- const valid3 = allWorktrees.filter((w) => activeSet.has(w.moduleUid));
8722
- return { orphaned, valid: valid3 };
8808
+ const valid2 = allWorktrees.filter((w) => activeSet.has(w.moduleUid));
8809
+ return { orphaned, valid: valid2 };
8723
8810
  }
8724
8811
  /**
8725
8812
  * Update last accessed timestamp for a worktree
@@ -8743,7 +8830,7 @@ var WorktreeManager = class _WorktreeManager {
8743
8830
  let prunedCount = 0;
8744
8831
  await this.updateConfigSafe((config) => {
8745
8832
  const initialCount = config.worktrees.length;
8746
- config.worktrees = config.worktrees.filter((w) => fs14.existsSync(w.worktreePath));
8833
+ config.worktrees = config.worktrees.filter((w) => fs15.existsSync(w.worktreePath));
8747
8834
  prunedCount = initialCount - config.worktrees.length;
8748
8835
  return config;
8749
8836
  });
@@ -8754,7 +8841,7 @@ var WorktreeManager = class _WorktreeManager {
8754
8841
  */
8755
8842
  async validateWorktrees() {
8756
8843
  const config = this.readConfig();
8757
- const valid3 = [];
8844
+ const valid2 = [];
8758
8845
  const stale = [];
8759
8846
  const orphaned = [];
8760
8847
  const listResult = await this.gitExecutor.execute({
@@ -8765,7 +8852,7 @@ var WorktreeManager = class _WorktreeManager {
8765
8852
  );
8766
8853
  for (const worktree of config?.worktrees || []) {
8767
8854
  if (actualWorktrees.has(worktree.worktreePath)) {
8768
- valid3.push(worktree);
8855
+ valid2.push(worktree);
8769
8856
  actualWorktrees.delete(worktree.worktreePath);
8770
8857
  } else {
8771
8858
  stale.push(worktree);
@@ -8776,7 +8863,7 @@ var WorktreeManager = class _WorktreeManager {
8776
8863
  orphaned.push(wpath);
8777
8864
  }
8778
8865
  }
8779
- return { valid: valid3, stale, orphaned };
8866
+ return { valid: valid2, stale, orphaned };
8780
8867
  }
8781
8868
  /**
8782
8869
  * EP1190: Clean up non-module worktrees (like 'main')
@@ -8793,7 +8880,7 @@ var WorktreeManager = class _WorktreeManager {
8793
8880
  try {
8794
8881
  const validation = await this.validateWorktrees();
8795
8882
  for (const orphanPath of validation.orphaned) {
8796
- const dirName = path15.basename(orphanPath);
8883
+ const dirName = path16.basename(orphanPath);
8797
8884
  const isModuleWorktree = /^EP\d+$/.test(dirName);
8798
8885
  if (!isModuleWorktree) {
8799
8886
  console.log(`[WorktreeManager] EP1190: Found non-module worktree: ${dirName}`);
@@ -8866,25 +8953,25 @@ var WorktreeManager = class _WorktreeManager {
8866
8953
  const retryInterval = 50;
8867
8954
  while (Date.now() - startTime < timeoutMs) {
8868
8955
  try {
8869
- fs14.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
8956
+ fs15.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
8870
8957
  return true;
8871
8958
  } catch (err) {
8872
8959
  if (err.code === "EEXIST") {
8873
8960
  try {
8874
- const stats = fs14.statSync(lockPath);
8961
+ const stats = fs15.statSync(lockPath);
8875
8962
  const lockAge = Date.now() - stats.mtimeMs;
8876
8963
  if (lockAge > 3e4) {
8877
8964
  try {
8878
- const lockContent = fs14.readFileSync(lockPath, "utf-8").trim();
8965
+ const lockContent = fs15.readFileSync(lockPath, "utf-8").trim();
8879
8966
  const lockPid = parseInt(lockContent, 10);
8880
8967
  if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
8881
- await new Promise((resolve8) => setTimeout(resolve8, retryInterval));
8968
+ await new Promise((resolve9) => setTimeout(resolve9, retryInterval));
8882
8969
  continue;
8883
8970
  }
8884
8971
  } catch {
8885
8972
  }
8886
8973
  try {
8887
- fs14.unlinkSync(lockPath);
8974
+ fs15.unlinkSync(lockPath);
8888
8975
  } catch {
8889
8976
  }
8890
8977
  continue;
@@ -8892,7 +8979,7 @@ var WorktreeManager = class _WorktreeManager {
8892
8979
  } catch {
8893
8980
  continue;
8894
8981
  }
8895
- await new Promise((resolve8) => setTimeout(resolve8, retryInterval));
8982
+ await new Promise((resolve9) => setTimeout(resolve9, retryInterval));
8896
8983
  continue;
8897
8984
  }
8898
8985
  throw err;
@@ -8905,7 +8992,7 @@ var WorktreeManager = class _WorktreeManager {
8905
8992
  */
8906
8993
  releaseLock() {
8907
8994
  try {
8908
- fs14.unlinkSync(this.getLockPath());
8995
+ fs15.unlinkSync(this.getLockPath());
8909
8996
  } catch {
8910
8997
  }
8911
8998
  }
@@ -8929,11 +9016,11 @@ var WorktreeManager = class _WorktreeManager {
8929
9016
  // Turborepo cache
8930
9017
  ];
8931
9018
  for (const cacheDir of cacheDirs) {
8932
- const cachePath = path15.join(worktreePath, cacheDir);
9019
+ const cachePath = path16.join(worktreePath, cacheDir);
8933
9020
  try {
8934
- if (fs14.existsSync(cachePath)) {
9021
+ if (fs15.existsSync(cachePath)) {
8935
9022
  console.log(`[WorktreeManager] EP1070: Cleaning build cache: ${cacheDir}`);
8936
- fs14.rmSync(cachePath, { recursive: true, force: true });
9023
+ fs15.rmSync(cachePath, { recursive: true, force: true });
8937
9024
  }
8938
9025
  } catch (error) {
8939
9026
  console.warn(`[WorktreeManager] EP1070: Failed to clean ${cacheDir} (non-blocking):`, error.message);
@@ -8942,10 +9029,10 @@ var WorktreeManager = class _WorktreeManager {
8942
9029
  }
8943
9030
  readConfig() {
8944
9031
  try {
8945
- if (!fs14.existsSync(this.configPath)) {
9032
+ if (!fs15.existsSync(this.configPath)) {
8946
9033
  return null;
8947
9034
  }
8948
- const content = fs14.readFileSync(this.configPath, "utf-8");
9035
+ const content = fs15.readFileSync(this.configPath, "utf-8");
8949
9036
  return JSON.parse(content);
8950
9037
  } catch (error) {
8951
9038
  if (error instanceof SyntaxError) {
@@ -8959,11 +9046,11 @@ var WorktreeManager = class _WorktreeManager {
8959
9046
  }
8960
9047
  writeConfig(config) {
8961
9048
  try {
8962
- const dir = path15.dirname(this.configPath);
8963
- if (!fs14.existsSync(dir)) {
8964
- fs14.mkdirSync(dir, { recursive: true });
9049
+ const dir = path16.dirname(this.configPath);
9050
+ if (!fs15.existsSync(dir)) {
9051
+ fs15.mkdirSync(dir, { recursive: true });
8965
9052
  }
8966
- fs14.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
9053
+ fs15.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
8967
9054
  } catch (error) {
8968
9055
  console.error("[WorktreeManager] Failed to write config:", error);
8969
9056
  throw error;
@@ -9045,14 +9132,14 @@ var WorktreeManager = class _WorktreeManager {
9045
9132
  }
9046
9133
  try {
9047
9134
  for (const file of files) {
9048
- const srcPath = path15.join(mainWorktree.worktreePath, file);
9049
- const destPath = path15.join(worktree.worktreePath, file);
9050
- if (fs14.existsSync(srcPath)) {
9051
- const destDir = path15.dirname(destPath);
9052
- if (!fs14.existsSync(destDir)) {
9053
- fs14.mkdirSync(destDir, { recursive: true });
9135
+ const srcPath = path16.join(mainWorktree.worktreePath, file);
9136
+ const destPath = path16.join(worktree.worktreePath, file);
9137
+ if (fs15.existsSync(srcPath)) {
9138
+ const destDir = path16.dirname(destPath);
9139
+ if (!fs15.existsSync(destDir)) {
9140
+ fs15.mkdirSync(destDir, { recursive: true });
9054
9141
  }
9055
- fs14.copyFileSync(srcPath, destPath);
9142
+ fs15.copyFileSync(srcPath, destPath);
9056
9143
  console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
9057
9144
  } else {
9058
9145
  console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
@@ -9141,10 +9228,10 @@ function getEpisodaRoot() {
9141
9228
  if (process.env.EPISODA_MODE === "cloud") {
9142
9229
  return process.env.HOME || "/home/episoda";
9143
9230
  }
9144
- return path15.join(require("os").homedir(), "episoda");
9231
+ return path16.join(require("os").homedir(), "episoda");
9145
9232
  }
9146
9233
  function getProjectPath(workspaceSlug, projectSlug) {
9147
- return path15.join(getEpisodaRoot(), workspaceSlug, projectSlug);
9234
+ return path16.join(getEpisodaRoot(), workspaceSlug, projectSlug);
9148
9235
  }
9149
9236
  async function isWorktreeProject(projectRoot) {
9150
9237
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
@@ -9159,7 +9246,7 @@ async function isWorktreeProject(projectRoot) {
9159
9246
  return result;
9160
9247
  }
9161
9248
  async function findProjectRoot(startPath) {
9162
- let current = path15.resolve(startPath);
9249
+ let current = path16.resolve(startPath);
9163
9250
  const episodaRoot = getEpisodaRoot();
9164
9251
  const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
9165
9252
  if (debug) {
@@ -9172,14 +9259,14 @@ async function findProjectRoot(startPath) {
9172
9259
  return null;
9173
9260
  }
9174
9261
  for (let i = 0; i < 10; i++) {
9175
- const bareDir = path15.join(current, ".bare");
9176
- const episodaDir = path15.join(current, ".episoda");
9262
+ const bareDir = path16.join(current, ".bare");
9263
+ const episodaDir = path16.join(current, ".episoda");
9177
9264
  if (debug) {
9178
- const bareExists = fs14.existsSync(bareDir);
9179
- const episodaExists = fs14.existsSync(episodaDir);
9265
+ const bareExists = fs15.existsSync(bareDir);
9266
+ const episodaExists = fs15.existsSync(episodaDir);
9180
9267
  console.log(`[WorktreeManager] findProjectRoot: checking ${current} (.bare=${bareExists}, .episoda=${episodaExists})`);
9181
9268
  }
9182
- if (fs14.existsSync(bareDir) && fs14.existsSync(episodaDir)) {
9269
+ if (fs15.existsSync(bareDir) && fs15.existsSync(episodaDir)) {
9183
9270
  if (await isWorktreeProject(current)) {
9184
9271
  if (debug) {
9185
9272
  console.log(`[WorktreeManager] findProjectRoot: found valid project at ${current}`);
@@ -9187,7 +9274,7 @@ async function findProjectRoot(startPath) {
9187
9274
  return current;
9188
9275
  }
9189
9276
  }
9190
- const parent = path15.dirname(current);
9277
+ const parent = path16.dirname(current);
9191
9278
  if (parent === current) {
9192
9279
  break;
9193
9280
  }
@@ -9200,7 +9287,7 @@ async function findProjectRoot(startPath) {
9200
9287
  }
9201
9288
 
9202
9289
  // src/daemon/agent-command-routing.ts
9203
- var fs15 = __toESM(require("fs"));
9290
+ var fs16 = __toESM(require("fs"));
9204
9291
  function shouldQueueAgentCommandForRetry(action) {
9205
9292
  return action === "start" || action === "message";
9206
9293
  }
@@ -9214,7 +9301,7 @@ async function resolveAgentWorkingDirectory(options) {
9214
9301
  getWorktreeInfoForModule: getWorktreeInfoForModule2,
9215
9302
  pathExists = async (candidatePath) => {
9216
9303
  try {
9217
- await fs15.promises.access(candidatePath);
9304
+ await fs16.promises.access(candidatePath);
9218
9305
  return true;
9219
9306
  } catch {
9220
9307
  return false;
@@ -9248,8 +9335,8 @@ async function resolveAgentWorkingDirectory(options) {
9248
9335
  }
9249
9336
 
9250
9337
  // src/utils/env-setup.ts
9251
- var fs16 = __toESM(require("fs"));
9252
- var path16 = __toESM(require("path"));
9338
+ var fs17 = __toESM(require("fs"));
9339
+ var path17 = __toESM(require("path"));
9253
9340
  async function fetchEnvVars(apiUrl, accessToken) {
9254
9341
  try {
9255
9342
  const url = `${apiUrl}/api/cli/env-vars`;
@@ -9284,16 +9371,16 @@ function writeEnvFile(targetPath, envVars) {
9284
9371
  }
9285
9372
  return `${key}=${value}`;
9286
9373
  }).join("\n") + "\n";
9287
- const envPath = path16.join(targetPath, ".env");
9288
- fs16.writeFileSync(envPath, envContent, { mode: 384 });
9374
+ const envPath = path17.join(targetPath, ".env");
9375
+ fs17.writeFileSync(envPath, envContent, { mode: 384 });
9289
9376
  console.log(`[env-setup] Wrote ${Object.keys(envVars).length} env vars to ${envPath}`);
9290
9377
  }
9291
9378
 
9292
9379
  // src/utils/worktree.ts
9293
- var path17 = __toESM(require("path"));
9294
- var fs17 = __toESM(require("fs"));
9380
+ var path18 = __toESM(require("path"));
9381
+ var fs18 = __toESM(require("fs"));
9295
9382
  var os8 = __toESM(require("os"));
9296
- var import_core11 = __toESM(require_dist());
9383
+ var import_core13 = __toESM(require_dist());
9297
9384
  function getEpisodaRoot2() {
9298
9385
  if (process.env.EPISODA_ROOT) {
9299
9386
  return process.env.EPISODA_ROOT;
@@ -9301,19 +9388,19 @@ function getEpisodaRoot2() {
9301
9388
  if (process.env.EPISODA_MODE === "cloud") {
9302
9389
  return process.env.HOME || "/home/episoda";
9303
9390
  }
9304
- return path17.join(os8.homedir(), "episoda");
9391
+ return path18.join(os8.homedir(), "episoda");
9305
9392
  }
9306
9393
  function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
9307
9394
  const root = getEpisodaRoot2();
9308
- const worktreePath = path17.join(root, workspaceSlug, projectSlug, moduleUid);
9395
+ const worktreePath = path18.join(root, workspaceSlug, projectSlug, moduleUid);
9309
9396
  return {
9310
9397
  path: worktreePath,
9311
- exists: fs17.existsSync(worktreePath),
9398
+ exists: fs18.existsSync(worktreePath),
9312
9399
  moduleUid
9313
9400
  };
9314
9401
  }
9315
9402
  async function getWorktreeInfoForModule(moduleUid) {
9316
- const config = await (0, import_core11.loadConfig)();
9403
+ const config = await (0, import_core13.loadConfig)();
9317
9404
  if (config?.workspace_slug && config?.project_slug) {
9318
9405
  return getWorktreeInfo(moduleUid, config.workspace_slug, config.project_slug);
9319
9406
  }
@@ -9322,15 +9409,15 @@ async function getWorktreeInfoForModule(moduleUid) {
9322
9409
  return null;
9323
9410
  }
9324
9411
  const root = getEpisodaRoot2();
9325
- const workspaceRoot = path17.join(root, config.workspace_slug);
9412
+ const workspaceRoot = path18.join(root, config.workspace_slug);
9326
9413
  try {
9327
- const entries = fs17.readdirSync(workspaceRoot, { withFileTypes: true });
9414
+ const entries = fs18.readdirSync(workspaceRoot, { withFileTypes: true });
9328
9415
  for (const entry of entries) {
9329
9416
  if (!entry.isDirectory()) {
9330
9417
  continue;
9331
9418
  }
9332
- const worktreePath = path17.join(workspaceRoot, entry.name, moduleUid);
9333
- if (fs17.existsSync(worktreePath)) {
9419
+ const worktreePath = path18.join(workspaceRoot, entry.name, moduleUid);
9420
+ if (fs18.existsSync(worktreePath)) {
9334
9421
  return {
9335
9422
  path: worktreePath,
9336
9423
  exists: true,
@@ -9347,21 +9434,21 @@ async function getWorktreeInfoForModule(moduleUid) {
9347
9434
  }
9348
9435
 
9349
9436
  // src/utils/port-allocator.ts
9350
- var fs18 = __toESM(require("fs"));
9351
- var path18 = __toESM(require("path"));
9437
+ var fs19 = __toESM(require("fs"));
9438
+ var path19 = __toESM(require("path"));
9352
9439
  var os9 = __toESM(require("os"));
9353
9440
  var PORT_RANGE_START = 3100;
9354
9441
  var PORT_RANGE_END = 3199;
9355
9442
  var PORT_WARNING_THRESHOLD = 80;
9356
- var PORTS_FILE = path18.join(os9.homedir(), ".episoda", "ports.json");
9443
+ var PORTS_FILE = path19.join(os9.homedir(), ".episoda", "ports.json");
9357
9444
  var portAssignments = /* @__PURE__ */ new Map();
9358
9445
  var initialized = false;
9359
9446
  function loadFromDisk() {
9360
9447
  if (initialized) return;
9361
9448
  initialized = true;
9362
9449
  try {
9363
- if (fs18.existsSync(PORTS_FILE)) {
9364
- const content = fs18.readFileSync(PORTS_FILE, "utf8");
9450
+ if (fs19.existsSync(PORTS_FILE)) {
9451
+ const content = fs19.readFileSync(PORTS_FILE, "utf8");
9365
9452
  const data = JSON.parse(content);
9366
9453
  for (const [moduleUid, port] of Object.entries(data)) {
9367
9454
  if (typeof port === "number" && port >= PORT_RANGE_START && port <= PORT_RANGE_END) {
@@ -9378,15 +9465,15 @@ function loadFromDisk() {
9378
9465
  }
9379
9466
  function saveToDisk() {
9380
9467
  try {
9381
- const dir = path18.dirname(PORTS_FILE);
9382
- if (!fs18.existsSync(dir)) {
9383
- fs18.mkdirSync(dir, { recursive: true });
9468
+ const dir = path19.dirname(PORTS_FILE);
9469
+ if (!fs19.existsSync(dir)) {
9470
+ fs19.mkdirSync(dir, { recursive: true });
9384
9471
  }
9385
9472
  const data = {};
9386
9473
  for (const [moduleUid, port] of portAssignments) {
9387
9474
  data[moduleUid] = port;
9388
9475
  }
9389
- fs18.writeFileSync(PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
9476
+ fs19.writeFileSync(PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
9390
9477
  } catch (error) {
9391
9478
  console.warn(`[PortAllocator] EP1042: Failed to save ports.json:`, error);
9392
9479
  }
@@ -9456,21 +9543,21 @@ function reconcileWithRegistry(activeModules) {
9456
9543
  }
9457
9544
 
9458
9545
  // src/utils/ws-port-allocator.ts
9459
- var fs19 = __toESM(require("fs"));
9460
- var path19 = __toESM(require("path"));
9546
+ var fs20 = __toESM(require("fs"));
9547
+ var path20 = __toESM(require("path"));
9461
9548
  var os10 = __toESM(require("os"));
9462
9549
  var WS_PORT_RANGE_START = 3200;
9463
9550
  var WS_PORT_RANGE_END = 3299;
9464
9551
  var WS_PORT_WARNING_THRESHOLD = 80;
9465
- var WS_PORTS_FILE = path19.join(os10.homedir(), ".episoda", "ws-ports.json");
9552
+ var WS_PORTS_FILE = path20.join(os10.homedir(), ".episoda", "ws-ports.json");
9466
9553
  var wsPortAssignments = /* @__PURE__ */ new Map();
9467
9554
  var initialized2 = false;
9468
9555
  function loadFromDisk2() {
9469
9556
  if (initialized2) return;
9470
9557
  initialized2 = true;
9471
9558
  try {
9472
- if (!fs19.existsSync(WS_PORTS_FILE)) return;
9473
- const content = fs19.readFileSync(WS_PORTS_FILE, "utf8");
9559
+ if (!fs20.existsSync(WS_PORTS_FILE)) return;
9560
+ const content = fs20.readFileSync(WS_PORTS_FILE, "utf8");
9474
9561
  const data = JSON.parse(content);
9475
9562
  for (const [moduleUid, port] of Object.entries(data)) {
9476
9563
  if (typeof port === "number" && port >= WS_PORT_RANGE_START && port <= WS_PORT_RANGE_END) {
@@ -9486,15 +9573,15 @@ function loadFromDisk2() {
9486
9573
  }
9487
9574
  function saveToDisk2() {
9488
9575
  try {
9489
- const dir = path19.dirname(WS_PORTS_FILE);
9490
- if (!fs19.existsSync(dir)) {
9491
- fs19.mkdirSync(dir, { recursive: true });
9576
+ const dir = path20.dirname(WS_PORTS_FILE);
9577
+ if (!fs20.existsSync(dir)) {
9578
+ fs20.mkdirSync(dir, { recursive: true });
9492
9579
  }
9493
9580
  const data = {};
9494
9581
  for (const [moduleUid, port] of wsPortAssignments) {
9495
9582
  data[moduleUid] = port;
9496
9583
  }
9497
- fs19.writeFileSync(WS_PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
9584
+ fs20.writeFileSync(WS_PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
9498
9585
  } catch (error) {
9499
9586
  console.warn("[WsPortAllocator] EP1406: Failed to save ws-ports.json:", error);
9500
9587
  }
@@ -9584,61 +9671,61 @@ function clearAllWsPorts() {
9584
9671
  }
9585
9672
 
9586
9673
  // src/framework-detector.ts
9587
- var fs20 = __toESM(require("fs"));
9588
- var path20 = __toESM(require("path"));
9674
+ var fs21 = __toESM(require("fs"));
9675
+ var path21 = __toESM(require("path"));
9589
9676
  function getInstallCommand(cwd) {
9590
- if (fs20.existsSync(path20.join(cwd, "bun.lockb"))) {
9677
+ if (fs21.existsSync(path21.join(cwd, "bun.lockb"))) {
9591
9678
  return {
9592
9679
  command: ["bun", "install"],
9593
9680
  description: "Installing dependencies with bun",
9594
9681
  detectedFrom: "bun.lockb"
9595
9682
  };
9596
9683
  }
9597
- if (fs20.existsSync(path20.join(cwd, "pnpm-lock.yaml"))) {
9684
+ if (fs21.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
9598
9685
  return {
9599
9686
  command: ["pnpm", "install"],
9600
9687
  description: "Installing dependencies with pnpm",
9601
9688
  detectedFrom: "pnpm-lock.yaml"
9602
9689
  };
9603
9690
  }
9604
- if (fs20.existsSync(path20.join(cwd, "yarn.lock"))) {
9691
+ if (fs21.existsSync(path21.join(cwd, "yarn.lock"))) {
9605
9692
  return {
9606
9693
  command: ["yarn", "install"],
9607
9694
  description: "Installing dependencies with yarn",
9608
9695
  detectedFrom: "yarn.lock"
9609
9696
  };
9610
9697
  }
9611
- if (fs20.existsSync(path20.join(cwd, "package-lock.json"))) {
9698
+ if (fs21.existsSync(path21.join(cwd, "package-lock.json"))) {
9612
9699
  return {
9613
9700
  command: ["npm", "ci"],
9614
9701
  description: "Installing dependencies with npm ci",
9615
9702
  detectedFrom: "package-lock.json"
9616
9703
  };
9617
9704
  }
9618
- if (fs20.existsSync(path20.join(cwd, "package.json"))) {
9705
+ if (fs21.existsSync(path21.join(cwd, "package.json"))) {
9619
9706
  return {
9620
9707
  command: ["npm", "install"],
9621
9708
  description: "Installing dependencies with npm",
9622
9709
  detectedFrom: "package.json"
9623
9710
  };
9624
9711
  }
9625
- if (fs20.existsSync(path20.join(cwd, "Pipfile.lock")) || fs20.existsSync(path20.join(cwd, "Pipfile"))) {
9712
+ if (fs21.existsSync(path21.join(cwd, "Pipfile.lock")) || fs21.existsSync(path21.join(cwd, "Pipfile"))) {
9626
9713
  return {
9627
9714
  command: ["pipenv", "install"],
9628
9715
  description: "Installing dependencies with pipenv",
9629
- detectedFrom: fs20.existsSync(path20.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9716
+ detectedFrom: fs21.existsSync(path21.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
9630
9717
  };
9631
9718
  }
9632
- if (fs20.existsSync(path20.join(cwd, "poetry.lock"))) {
9719
+ if (fs21.existsSync(path21.join(cwd, "poetry.lock"))) {
9633
9720
  return {
9634
9721
  command: ["poetry", "install"],
9635
9722
  description: "Installing dependencies with poetry",
9636
9723
  detectedFrom: "poetry.lock"
9637
9724
  };
9638
9725
  }
9639
- if (fs20.existsSync(path20.join(cwd, "pyproject.toml"))) {
9640
- const pyprojectPath = path20.join(cwd, "pyproject.toml");
9641
- const content = fs20.readFileSync(pyprojectPath, "utf-8");
9726
+ if (fs21.existsSync(path21.join(cwd, "pyproject.toml"))) {
9727
+ const pyprojectPath = path21.join(cwd, "pyproject.toml");
9728
+ const content = fs21.readFileSync(pyprojectPath, "utf-8");
9642
9729
  if (content.includes("[tool.poetry]")) {
9643
9730
  return {
9644
9731
  command: ["poetry", "install"],
@@ -9647,32 +9734,32 @@ function getInstallCommand(cwd) {
9647
9734
  };
9648
9735
  }
9649
9736
  }
9650
- if (fs20.existsSync(path20.join(cwd, "requirements.txt"))) {
9737
+ if (fs21.existsSync(path21.join(cwd, "requirements.txt"))) {
9651
9738
  return {
9652
9739
  command: ["pip", "install", "-r", "requirements.txt"],
9653
9740
  description: "Installing dependencies with pip",
9654
9741
  detectedFrom: "requirements.txt"
9655
9742
  };
9656
9743
  }
9657
- if (fs20.existsSync(path20.join(cwd, "Gemfile.lock")) || fs20.existsSync(path20.join(cwd, "Gemfile"))) {
9744
+ if (fs21.existsSync(path21.join(cwd, "Gemfile.lock")) || fs21.existsSync(path21.join(cwd, "Gemfile"))) {
9658
9745
  return {
9659
9746
  command: ["bundle", "install"],
9660
9747
  description: "Installing dependencies with bundler",
9661
- detectedFrom: fs20.existsSync(path20.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9748
+ detectedFrom: fs21.existsSync(path21.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
9662
9749
  };
9663
9750
  }
9664
- if (fs20.existsSync(path20.join(cwd, "go.sum")) || fs20.existsSync(path20.join(cwd, "go.mod"))) {
9751
+ if (fs21.existsSync(path21.join(cwd, "go.sum")) || fs21.existsSync(path21.join(cwd, "go.mod"))) {
9665
9752
  return {
9666
9753
  command: ["go", "mod", "download"],
9667
9754
  description: "Downloading Go modules",
9668
- detectedFrom: fs20.existsSync(path20.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9755
+ detectedFrom: fs21.existsSync(path21.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
9669
9756
  };
9670
9757
  }
9671
- if (fs20.existsSync(path20.join(cwd, "Cargo.lock")) || fs20.existsSync(path20.join(cwd, "Cargo.toml"))) {
9758
+ if (fs21.existsSync(path21.join(cwd, "Cargo.lock")) || fs21.existsSync(path21.join(cwd, "Cargo.toml"))) {
9672
9759
  return {
9673
9760
  command: ["cargo", "build"],
9674
9761
  description: "Building Rust project (downloads dependencies)",
9675
- detectedFrom: fs20.existsSync(path20.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9762
+ detectedFrom: fs21.existsSync(path21.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
9676
9763
  };
9677
9764
  }
9678
9765
  return null;
@@ -9683,34 +9770,34 @@ var fs35 = __toESM(require("fs"));
9683
9770
  var http2 = __toESM(require("http"));
9684
9771
  var os16 = __toESM(require("os"));
9685
9772
  var path37 = __toESM(require("path"));
9686
- var import_child_process19 = require("child_process");
9773
+ var import_child_process18 = require("child_process");
9687
9774
 
9688
9775
  // src/daemon/ipc-router.ts
9689
9776
  var os15 = __toESM(require("os"));
9690
- var import_core15 = __toESM(require_dist());
9777
+ var import_core17 = __toESM(require_dist());
9691
9778
 
9692
9779
  // src/daemon/handlers/file-handlers.ts
9693
- var fs22 = __toESM(require("fs"));
9694
- var path22 = __toESM(require("path"));
9780
+ var fs23 = __toESM(require("fs"));
9781
+ var path23 = __toESM(require("path"));
9695
9782
  var readline = __toESM(require("readline"));
9696
9783
 
9697
9784
  // src/daemon/permissions/path-classifier.ts
9698
- var path21 = __toESM(require("path"));
9699
- var fs21 = __toESM(require("fs"));
9785
+ var path22 = __toESM(require("path"));
9786
+ var fs22 = __toESM(require("fs"));
9700
9787
  function classifyPath(targetPath, options) {
9701
9788
  const { workspaceRoot, projectRoot } = options;
9702
- const normalizedWorkspace = path21.resolve(workspaceRoot);
9703
- const normalizedProject = path21.resolve(projectRoot);
9704
- const resolvedPath = path21.isAbsolute(targetPath) ? path21.resolve(targetPath) : path21.resolve(projectRoot, targetPath);
9705
- const artifactsDir = path21.join(normalizedWorkspace, "artifacts");
9706
- if (resolvedPath.startsWith(artifactsDir + path21.sep) || resolvedPath === artifactsDir) {
9789
+ const normalizedWorkspace = path22.resolve(workspaceRoot);
9790
+ const normalizedProject = path22.resolve(projectRoot);
9791
+ const resolvedPath = path22.isAbsolute(targetPath) ? path22.resolve(targetPath) : path22.resolve(projectRoot, targetPath);
9792
+ const artifactsDir = path22.join(normalizedWorkspace, "artifacts");
9793
+ if (resolvedPath.startsWith(artifactsDir + path22.sep) || resolvedPath === artifactsDir) {
9707
9794
  return {
9708
9795
  classification: "artifacts",
9709
9796
  resolvedPath
9710
9797
  };
9711
9798
  }
9712
- if (!resolvedPath.startsWith(normalizedProject + path21.sep) && resolvedPath !== normalizedProject) {
9713
- if (resolvedPath.startsWith(normalizedWorkspace + path21.sep)) {
9799
+ if (!resolvedPath.startsWith(normalizedProject + path22.sep) && resolvedPath !== normalizedProject) {
9800
+ if (resolvedPath.startsWith(normalizedWorkspace + path22.sep)) {
9714
9801
  return {
9715
9802
  classification: "unknown",
9716
9803
  resolvedPath
@@ -9721,8 +9808,8 @@ function classifyPath(targetPath, options) {
9721
9808
  resolvedPath
9722
9809
  };
9723
9810
  }
9724
- const relativePath = path21.relative(normalizedProject, resolvedPath);
9725
- const pathParts = relativePath.split(path21.sep);
9811
+ const relativePath = path22.relative(normalizedProject, resolvedPath);
9812
+ const pathParts = relativePath.split(path22.sep);
9726
9813
  if (pathParts[0] === ".bare") {
9727
9814
  return {
9728
9815
  classification: "bare_repo",
@@ -9737,9 +9824,9 @@ function classifyPath(targetPath, options) {
9737
9824
  }
9738
9825
  const firstPart = pathParts[0];
9739
9826
  if (firstPart && isModuleUidPattern(firstPart)) {
9740
- const worktreeDir = path21.join(normalizedProject, firstPart);
9741
- const gitFile = path21.join(worktreeDir, ".git");
9742
- const worktreeExists = fs21.existsSync(gitFile) && fs21.statSync(gitFile).isFile();
9827
+ const worktreeDir = path22.join(normalizedProject, firstPart);
9828
+ const gitFile = path22.join(worktreeDir, ".git");
9829
+ const worktreeExists = fs22.existsSync(gitFile) && fs22.statSync(gitFile).isFile();
9743
9830
  return {
9744
9831
  classification: "worktree",
9745
9832
  moduleUid: firstPart,
@@ -9756,19 +9843,19 @@ function isModuleUidPattern(str) {
9756
9843
  return /^EP\d+$/.test(str);
9757
9844
  }
9758
9845
  function deriveWorkspaceRoot(projectPath) {
9759
- return path21.dirname(path21.resolve(projectPath));
9846
+ return path22.dirname(path22.resolve(projectPath));
9760
9847
  }
9761
9848
  function validateWorkspacePath(filePath, projectPath) {
9762
- const normalizedProjectPath = path21.resolve(projectPath);
9849
+ const normalizedProjectPath = path22.resolve(projectPath);
9763
9850
  const workspaceRoot = deriveWorkspaceRoot(projectPath);
9764
- const normalizedWorkspace = path21.resolve(workspaceRoot);
9765
- const artifactsDir = path21.join(normalizedWorkspace, "artifacts");
9766
- const absolutePath = path21.isAbsolute(filePath) ? path21.resolve(filePath) : path21.resolve(projectPath, filePath);
9767
- const normalizedPath = path21.normalize(absolutePath);
9768
- if (normalizedPath.startsWith(normalizedProjectPath + path21.sep) || normalizedPath === normalizedProjectPath) {
9851
+ const normalizedWorkspace = path22.resolve(workspaceRoot);
9852
+ const artifactsDir = path22.join(normalizedWorkspace, "artifacts");
9853
+ const absolutePath = path22.isAbsolute(filePath) ? path22.resolve(filePath) : path22.resolve(projectPath, filePath);
9854
+ const normalizedPath = path22.normalize(absolutePath);
9855
+ if (normalizedPath.startsWith(normalizedProjectPath + path22.sep) || normalizedPath === normalizedProjectPath) {
9769
9856
  return normalizedPath;
9770
9857
  }
9771
- if (normalizedPath.startsWith(artifactsDir + path21.sep) || normalizedPath === artifactsDir) {
9858
+ if (normalizedPath.startsWith(artifactsDir + path22.sep) || normalizedPath === artifactsDir) {
9772
9859
  return normalizedPath;
9773
9860
  }
9774
9861
  return null;
@@ -9866,13 +9953,13 @@ async function handleFileRead(command, projectPath) {
9866
9953
  };
9867
9954
  }
9868
9955
  try {
9869
- if (!fs22.existsSync(validPath)) {
9956
+ if (!fs23.existsSync(validPath)) {
9870
9957
  return {
9871
9958
  success: false,
9872
9959
  error: "File not found"
9873
9960
  };
9874
9961
  }
9875
- const stats = fs22.statSync(validPath);
9962
+ const stats = fs23.statSync(validPath);
9876
9963
  if (stats.isDirectory()) {
9877
9964
  return {
9878
9965
  success: false,
@@ -9885,7 +9972,7 @@ async function handleFileRead(command, projectPath) {
9885
9972
  error: `File too large: ${stats.size} bytes exceeds limit of ${maxSize} bytes`
9886
9973
  };
9887
9974
  }
9888
- const buffer = fs22.readFileSync(validPath);
9975
+ const buffer = fs23.readFileSync(validPath);
9889
9976
  let content;
9890
9977
  if (encoding === "base64") {
9891
9978
  content = buffer.toString("base64");
@@ -9925,9 +10012,9 @@ async function handleFileWrite(command, projectPath) {
9925
10012
  }
9926
10013
  try {
9927
10014
  if (createDirs) {
9928
- const dirPath = path22.dirname(validPath);
9929
- if (!fs22.existsSync(dirPath)) {
9930
- fs22.mkdirSync(dirPath, { recursive: true });
10015
+ const dirPath = path23.dirname(validPath);
10016
+ if (!fs23.existsSync(dirPath)) {
10017
+ fs23.mkdirSync(dirPath, { recursive: true });
9931
10018
  }
9932
10019
  }
9933
10020
  let buffer;
@@ -9937,8 +10024,8 @@ async function handleFileWrite(command, projectPath) {
9937
10024
  buffer = Buffer.from(content, "utf8");
9938
10025
  }
9939
10026
  const tempPath = `${validPath}.tmp.${Date.now()}`;
9940
- fs22.writeFileSync(tempPath, buffer);
9941
- fs22.renameSync(tempPath, validPath);
10027
+ fs23.writeFileSync(tempPath, buffer);
10028
+ fs23.renameSync(tempPath, validPath);
9942
10029
  return {
9943
10030
  success: true,
9944
10031
  bytesWritten: buffer.length
@@ -9963,13 +10050,13 @@ async function handleFileList(command, projectPath) {
9963
10050
  };
9964
10051
  }
9965
10052
  try {
9966
- if (!fs22.existsSync(validPath)) {
10053
+ if (!fs23.existsSync(validPath)) {
9967
10054
  return {
9968
10055
  success: false,
9969
10056
  error: "Directory not found"
9970
10057
  };
9971
10058
  }
9972
- const stats = fs22.statSync(validPath);
10059
+ const stats = fs23.statSync(validPath);
9973
10060
  if (!stats.isDirectory()) {
9974
10061
  return {
9975
10062
  success: false,
@@ -9980,11 +10067,11 @@ async function handleFileList(command, projectPath) {
9980
10067
  if (recursive) {
9981
10068
  await listDirectoryRecursive(validPath, validPath, entries, includeHidden);
9982
10069
  } else {
9983
- const dirEntries = await fs22.promises.readdir(validPath, { withFileTypes: true });
10070
+ const dirEntries = await fs23.promises.readdir(validPath, { withFileTypes: true });
9984
10071
  for (const entry of dirEntries) {
9985
10072
  if (!includeHidden && entry.name.startsWith(".")) continue;
9986
- const entryPath = path22.join(validPath, entry.name);
9987
- const entryStats = await fs22.promises.stat(entryPath);
10073
+ const entryPath = path23.join(validPath, entry.name);
10074
+ const entryStats = await fs23.promises.stat(entryPath);
9988
10075
  entries.push({
9989
10076
  name: entry.name,
9990
10077
  type: entry.isDirectory() ? "directory" : "file",
@@ -10006,13 +10093,13 @@ async function handleFileList(command, projectPath) {
10006
10093
  }
10007
10094
  }
10008
10095
  async function listDirectoryRecursive(basePath, currentPath, entries, includeHidden) {
10009
- const dirEntries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
10096
+ const dirEntries = await fs23.promises.readdir(currentPath, { withFileTypes: true });
10010
10097
  for (const entry of dirEntries) {
10011
10098
  if (!includeHidden && entry.name.startsWith(".")) continue;
10012
- const entryPath = path22.join(currentPath, entry.name);
10013
- const relativePath = path22.relative(basePath, entryPath);
10099
+ const entryPath = path23.join(currentPath, entry.name);
10100
+ const relativePath = path23.relative(basePath, entryPath);
10014
10101
  try {
10015
- const entryStats = await fs22.promises.stat(entryPath);
10102
+ const entryStats = await fs23.promises.stat(entryPath);
10016
10103
  entries.push({
10017
10104
  name: relativePath,
10018
10105
  type: entry.isDirectory() ? "directory" : "file",
@@ -10036,7 +10123,7 @@ async function handleFileSearch(command, projectPath) {
10036
10123
  };
10037
10124
  }
10038
10125
  try {
10039
- if (!fs22.existsSync(validPath)) {
10126
+ if (!fs23.existsSync(validPath)) {
10040
10127
  return {
10041
10128
  success: false,
10042
10129
  error: "Directory not found"
@@ -10126,12 +10213,12 @@ function findClosingBracket(pattern, start) {
10126
10213
  }
10127
10214
  async function searchFilesRecursive(basePath, currentPath, pattern, files, maxResults) {
10128
10215
  if (files.length >= maxResults) return;
10129
- const entries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
10216
+ const entries = await fs23.promises.readdir(currentPath, { withFileTypes: true });
10130
10217
  for (const entry of entries) {
10131
10218
  if (files.length >= maxResults) return;
10132
10219
  if (entry.name.startsWith(".")) continue;
10133
- const entryPath = path22.join(currentPath, entry.name);
10134
- const relativePath = path22.relative(basePath, entryPath);
10220
+ const entryPath = path23.join(currentPath, entry.name);
10221
+ const relativePath = path23.relative(basePath, entryPath);
10135
10222
  try {
10136
10223
  if (entry.isDirectory()) {
10137
10224
  await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults);
@@ -10159,7 +10246,7 @@ async function handleFileGrep(command, projectPath) {
10159
10246
  };
10160
10247
  }
10161
10248
  try {
10162
- if (!fs22.existsSync(validPath)) {
10249
+ if (!fs23.existsSync(validPath)) {
10163
10250
  return {
10164
10251
  success: false,
10165
10252
  error: "Path not found"
@@ -10168,7 +10255,7 @@ async function handleFileGrep(command, projectPath) {
10168
10255
  const matches = [];
10169
10256
  const searchRegex = new RegExp(pattern, caseSensitive ? "" : "i");
10170
10257
  const fileGlobRegex = globToRegex(filePattern);
10171
- const stats = fs22.statSync(validPath);
10258
+ const stats = fs23.statSync(validPath);
10172
10259
  if (stats.isFile()) {
10173
10260
  await grepFile(validPath, validPath, searchRegex, matches, effectiveMaxResults);
10174
10261
  } else {
@@ -10189,8 +10276,8 @@ async function handleFileGrep(command, projectPath) {
10189
10276
  }
10190
10277
  async function grepFile(basePath, filePath, pattern, matches, maxResults) {
10191
10278
  if (matches.length >= maxResults) return;
10192
- const relativePath = path22.relative(basePath, filePath);
10193
- const fileStream = fs22.createReadStream(filePath, { encoding: "utf8" });
10279
+ const relativePath = path23.relative(basePath, filePath);
10280
+ const fileStream = fs23.createReadStream(filePath, { encoding: "utf8" });
10194
10281
  const rl = readline.createInterface({
10195
10282
  input: fileStream,
10196
10283
  crlfDelay: Infinity
@@ -10204,7 +10291,7 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
10204
10291
  }
10205
10292
  if (pattern.test(line)) {
10206
10293
  matches.push({
10207
- file: relativePath || path22.basename(filePath),
10294
+ file: relativePath || path23.basename(filePath),
10208
10295
  line: lineNumber,
10209
10296
  content: line.slice(0, 500)
10210
10297
  // Truncate long lines
@@ -10214,11 +10301,11 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
10214
10301
  }
10215
10302
  async function grepDirectoryRecursive(basePath, currentPath, searchPattern, filePattern, matches, maxResults) {
10216
10303
  if (matches.length >= maxResults) return;
10217
- const entries = await fs22.promises.readdir(currentPath, { withFileTypes: true });
10304
+ const entries = await fs23.promises.readdir(currentPath, { withFileTypes: true });
10218
10305
  for (const entry of entries) {
10219
10306
  if (matches.length >= maxResults) return;
10220
10307
  if (entry.name.startsWith(".")) continue;
10221
- const entryPath = path22.join(currentPath, entry.name);
10308
+ const entryPath = path23.join(currentPath, entry.name);
10222
10309
  try {
10223
10310
  if (entry.isDirectory()) {
10224
10311
  await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults);
@@ -10246,13 +10333,13 @@ async function handleFileEdit(command, projectPath) {
10246
10333
  };
10247
10334
  }
10248
10335
  try {
10249
- if (!fs22.existsSync(validPath)) {
10336
+ if (!fs23.existsSync(validPath)) {
10250
10337
  return {
10251
10338
  success: false,
10252
10339
  error: "File not found"
10253
10340
  };
10254
10341
  }
10255
- const stats = fs22.statSync(validPath);
10342
+ const stats = fs23.statSync(validPath);
10256
10343
  if (stats.isDirectory()) {
10257
10344
  return {
10258
10345
  success: false,
@@ -10266,7 +10353,7 @@ async function handleFileEdit(command, projectPath) {
10266
10353
  error: `File too large for edit operation: ${stats.size} bytes exceeds limit of ${MAX_EDIT_SIZE} bytes (10MB). Use write-file for large files.`
10267
10354
  };
10268
10355
  }
10269
- const content = fs22.readFileSync(validPath, "utf8");
10356
+ const content = fs23.readFileSync(validPath, "utf8");
10270
10357
  const occurrences = content.split(oldString).length - 1;
10271
10358
  if (occurrences === 0) {
10272
10359
  return {
@@ -10283,8 +10370,8 @@ async function handleFileEdit(command, projectPath) {
10283
10370
  const newContent = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
10284
10371
  const replacements = replaceAll ? occurrences : 1;
10285
10372
  const tempPath = `${validPath}.tmp.${Date.now()}`;
10286
- fs22.writeFileSync(tempPath, newContent, "utf8");
10287
- fs22.renameSync(tempPath, validPath);
10373
+ fs23.writeFileSync(tempPath, newContent, "utf8");
10374
+ fs23.renameSync(tempPath, validPath);
10288
10375
  const newSize = Buffer.byteLength(newContent, "utf8");
10289
10376
  return {
10290
10377
  success: true,
@@ -10309,7 +10396,7 @@ async function handleFileDelete(command, projectPath) {
10309
10396
  error: "Invalid path: directory traversal not allowed"
10310
10397
  };
10311
10398
  }
10312
- const normalizedProjectPath = path22.resolve(projectPath);
10399
+ const normalizedProjectPath = path23.resolve(projectPath);
10313
10400
  if (validPath === normalizedProjectPath) {
10314
10401
  return {
10315
10402
  success: false,
@@ -10324,13 +10411,13 @@ async function handleFileDelete(command, projectPath) {
10324
10411
  };
10325
10412
  }
10326
10413
  try {
10327
- if (!fs22.existsSync(validPath)) {
10414
+ if (!fs23.existsSync(validPath)) {
10328
10415
  return {
10329
10416
  success: false,
10330
10417
  error: "Path not found"
10331
10418
  };
10332
10419
  }
10333
- const stats = fs22.statSync(validPath);
10420
+ const stats = fs23.statSync(validPath);
10334
10421
  const isDirectory = stats.isDirectory();
10335
10422
  if (isDirectory && !recursive) {
10336
10423
  return {
@@ -10339,9 +10426,9 @@ async function handleFileDelete(command, projectPath) {
10339
10426
  };
10340
10427
  }
10341
10428
  if (isDirectory) {
10342
- fs22.rmSync(validPath, { recursive: true, force: true });
10429
+ fs23.rmSync(validPath, { recursive: true, force: true });
10343
10430
  } else {
10344
- fs22.unlinkSync(validPath);
10431
+ fs23.unlinkSync(validPath);
10345
10432
  }
10346
10433
  return {
10347
10434
  success: true,
@@ -10374,8 +10461,8 @@ async function handleFileMkdir(command, projectPath) {
10374
10461
  };
10375
10462
  }
10376
10463
  try {
10377
- if (fs22.existsSync(validPath)) {
10378
- const stats = fs22.statSync(validPath);
10464
+ if (fs23.existsSync(validPath)) {
10465
+ const stats = fs23.statSync(validPath);
10379
10466
  if (stats.isDirectory()) {
10380
10467
  return {
10381
10468
  success: true,
@@ -10390,7 +10477,7 @@ async function handleFileMkdir(command, projectPath) {
10390
10477
  }
10391
10478
  }
10392
10479
  const modeNum = parseInt(mode, 8);
10393
- fs22.mkdirSync(validPath, { recursive: true, mode: modeNum });
10480
+ fs23.mkdirSync(validPath, { recursive: true, mode: modeNum });
10394
10481
  return {
10395
10482
  success: true,
10396
10483
  created: true
@@ -10406,7 +10493,7 @@ async function handleFileMkdir(command, projectPath) {
10406
10493
  }
10407
10494
 
10408
10495
  // src/daemon/handlers/exec-handler.ts
10409
- var import_child_process11 = require("child_process");
10496
+ var import_child_process12 = require("child_process");
10410
10497
  var DEFAULT_TIMEOUT = 3e4;
10411
10498
  var MAX_TIMEOUT = 3e5;
10412
10499
  async function handleExec(command, projectPath) {
@@ -10417,7 +10504,7 @@ async function handleExec(command, projectPath) {
10417
10504
  env = {}
10418
10505
  } = command;
10419
10506
  const effectiveTimeout = Math.min(Math.max(timeout, 1e3), MAX_TIMEOUT);
10420
- return new Promise((resolve8) => {
10507
+ return new Promise((resolve9) => {
10421
10508
  let stdout = "";
10422
10509
  let stderr = "";
10423
10510
  let timedOut = false;
@@ -10425,10 +10512,10 @@ async function handleExec(command, projectPath) {
10425
10512
  const done = (result) => {
10426
10513
  if (resolved) return;
10427
10514
  resolved = true;
10428
- resolve8(result);
10515
+ resolve9(result);
10429
10516
  };
10430
10517
  try {
10431
- const proc = (0, import_child_process11.spawn)(cmd, {
10518
+ const proc = (0, import_child_process12.spawn)(cmd, {
10432
10519
  shell: true,
10433
10520
  cwd,
10434
10521
  env: { ...process.env, ...env },
@@ -10489,10 +10576,10 @@ async function handleExec(command, projectPath) {
10489
10576
  }
10490
10577
 
10491
10578
  // src/daemon/handlers/worktree-handlers.ts
10492
- var path28 = __toESM(require("path"));
10493
- var fs28 = __toESM(require("fs"));
10579
+ var path29 = __toESM(require("path"));
10580
+ var fs29 = __toESM(require("fs"));
10494
10581
  var os13 = __toESM(require("os"));
10495
- var import_child_process14 = require("child_process");
10582
+ var import_child_process15 = require("child_process");
10496
10583
  var import_util2 = require("util");
10497
10584
 
10498
10585
  // src/preview/types.ts
@@ -10519,54 +10606,54 @@ var import_events3 = require("events");
10519
10606
  var import_fs = require("fs");
10520
10607
 
10521
10608
  // src/preview/dev-server-runner.ts
10522
- var import_child_process13 = require("child_process");
10609
+ var import_child_process14 = require("child_process");
10523
10610
  var http = __toESM(require("http"));
10524
- var fs25 = __toESM(require("fs"));
10525
- var path25 = __toESM(require("path"));
10611
+ var fs26 = __toESM(require("fs"));
10612
+ var path26 = __toESM(require("path"));
10526
10613
  var import_events2 = require("events");
10527
- var import_core12 = __toESM(require_dist());
10614
+ var import_core14 = __toESM(require_dist());
10528
10615
 
10529
10616
  // src/utils/port-check.ts
10530
10617
  var net2 = __toESM(require("net"));
10531
10618
  async function isPortInUse(port) {
10532
- return new Promise((resolve8) => {
10619
+ return new Promise((resolve9) => {
10533
10620
  const server = net2.createServer();
10534
10621
  server.once("error", (err) => {
10535
10622
  if (err.code === "EADDRINUSE") {
10536
- resolve8(true);
10623
+ resolve9(true);
10537
10624
  } else {
10538
- resolve8(false);
10625
+ resolve9(false);
10539
10626
  }
10540
10627
  });
10541
10628
  server.once("listening", () => {
10542
10629
  server.close();
10543
- resolve8(false);
10630
+ resolve9(false);
10544
10631
  });
10545
10632
  server.listen(port);
10546
10633
  });
10547
10634
  }
10548
10635
 
10549
10636
  // src/utils/env-cache.ts
10550
- var fs23 = __toESM(require("fs"));
10551
- var path23 = __toESM(require("path"));
10637
+ var fs24 = __toESM(require("fs"));
10638
+ var path24 = __toESM(require("path"));
10552
10639
  var os11 = __toESM(require("os"));
10553
10640
  var DEFAULT_CACHE_TTL = 60;
10554
- var CACHE_DIR = path23.join(os11.homedir(), ".episoda", "cache");
10641
+ var CACHE_DIR = path24.join(os11.homedir(), ".episoda", "cache");
10555
10642
  function getCacheFilePath(projectId) {
10556
- return path23.join(CACHE_DIR, `env-vars-${projectId}.json`);
10643
+ return path24.join(CACHE_DIR, `env-vars-${projectId}.json`);
10557
10644
  }
10558
10645
  function ensureCacheDir() {
10559
- if (!fs23.existsSync(CACHE_DIR)) {
10560
- fs23.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
10646
+ if (!fs24.existsSync(CACHE_DIR)) {
10647
+ fs24.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
10561
10648
  }
10562
10649
  }
10563
10650
  function readCache(projectId) {
10564
10651
  try {
10565
10652
  const cacheFile = getCacheFilePath(projectId);
10566
- if (!fs23.existsSync(cacheFile)) {
10653
+ if (!fs24.existsSync(cacheFile)) {
10567
10654
  return null;
10568
10655
  }
10569
- const content = fs23.readFileSync(cacheFile, "utf-8");
10656
+ const content = fs24.readFileSync(cacheFile, "utf-8");
10570
10657
  const data = JSON.parse(content);
10571
10658
  if (!data.vars || typeof data.vars !== "object" || !data.fetchedAt) {
10572
10659
  return null;
@@ -10585,7 +10672,7 @@ function writeCache(projectId, vars) {
10585
10672
  fetchedAt: Date.now(),
10586
10673
  projectId
10587
10674
  };
10588
- fs23.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
10675
+ fs24.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
10589
10676
  } catch (error) {
10590
10677
  console.warn("[env-cache] Failed to write cache:", error instanceof Error ? error.message : error);
10591
10678
  }
@@ -10654,11 +10741,11 @@ No cached values available as fallback.`
10654
10741
  }
10655
10742
 
10656
10743
  // src/preview/dev-server-registry.ts
10657
- var fs24 = __toESM(require("fs"));
10658
- var path24 = __toESM(require("path"));
10744
+ var fs25 = __toESM(require("fs"));
10745
+ var path25 = __toESM(require("path"));
10659
10746
  var os12 = __toESM(require("os"));
10660
- var import_child_process12 = require("child_process");
10661
- var DEV_SERVER_REGISTRY_DIR = path24.join(os12.homedir(), ".episoda", "dev-servers");
10747
+ var import_child_process13 = require("child_process");
10748
+ var DEV_SERVER_REGISTRY_DIR = path25.join(os12.homedir(), ".episoda", "dev-servers");
10662
10749
  var DevServerRegistry = class {
10663
10750
  constructor() {
10664
10751
  this.ensureRegistryDir();
@@ -10668,9 +10755,9 @@ var DevServerRegistry = class {
10668
10755
  */
10669
10756
  ensureRegistryDir() {
10670
10757
  try {
10671
- if (!fs24.existsSync(DEV_SERVER_REGISTRY_DIR)) {
10758
+ if (!fs25.existsSync(DEV_SERVER_REGISTRY_DIR)) {
10672
10759
  console.log(`[DevServerRegistry] EP1042: Creating registry directory: ${DEV_SERVER_REGISTRY_DIR}`);
10673
- fs24.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
10760
+ fs25.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
10674
10761
  }
10675
10762
  } catch (error) {
10676
10763
  console.error(`[DevServerRegistry] EP1042: Failed to create registry directory:`, error);
@@ -10681,7 +10768,7 @@ var DevServerRegistry = class {
10681
10768
  * Get the registry file path for a module
10682
10769
  */
10683
10770
  getEntryPath(moduleUid) {
10684
- return path24.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
10771
+ return path25.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
10685
10772
  }
10686
10773
  /**
10687
10774
  * Register a dev server
@@ -10692,7 +10779,7 @@ var DevServerRegistry = class {
10692
10779
  try {
10693
10780
  this.ensureRegistryDir();
10694
10781
  const entryPath = this.getEntryPath(entry.moduleUid);
10695
- fs24.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
10782
+ fs25.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
10696
10783
  console.log(
10697
10784
  `[DevServerRegistry] EP1042: Registered ${entry.moduleUid} (PID ${entry.pid}, port ${entry.port}${entry.wsPort ? `, wsPort ${entry.wsPort}` : ""})`
10698
10785
  );
@@ -10708,8 +10795,8 @@ var DevServerRegistry = class {
10708
10795
  unregister(moduleUid) {
10709
10796
  try {
10710
10797
  const entryPath = this.getEntryPath(moduleUid);
10711
- if (fs24.existsSync(entryPath)) {
10712
- fs24.unlinkSync(entryPath);
10798
+ if (fs25.existsSync(entryPath)) {
10799
+ fs25.unlinkSync(entryPath);
10713
10800
  console.log(`[DevServerRegistry] EP1042: Unregistered ${moduleUid}`);
10714
10801
  }
10715
10802
  } catch (error) {
@@ -10725,10 +10812,10 @@ var DevServerRegistry = class {
10725
10812
  getByModule(moduleUid) {
10726
10813
  try {
10727
10814
  const entryPath = this.getEntryPath(moduleUid);
10728
- if (!fs24.existsSync(entryPath)) {
10815
+ if (!fs25.existsSync(entryPath)) {
10729
10816
  return null;
10730
10817
  }
10731
- const content = fs24.readFileSync(entryPath, "utf8");
10818
+ const content = fs25.readFileSync(entryPath, "utf8");
10732
10819
  const entry = JSON.parse(content);
10733
10820
  if (!entry.pid || !entry.port || !entry.worktreePath) {
10734
10821
  console.warn(`[DevServerRegistry] EP1042: Invalid entry for ${moduleUid}, removing`);
@@ -10771,7 +10858,7 @@ var DevServerRegistry = class {
10771
10858
  const entries = [];
10772
10859
  try {
10773
10860
  this.ensureRegistryDir();
10774
- const files = fs24.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
10861
+ const files = fs25.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
10775
10862
  for (const file of files) {
10776
10863
  const moduleUid = file.replace(".json", "");
10777
10864
  const entry = this.getByModule(moduleUid);
@@ -10819,7 +10906,7 @@ var DevServerRegistry = class {
10819
10906
  */
10820
10907
  getProcessCwd(pid) {
10821
10908
  try {
10822
- const output = (0, import_child_process12.execSync)(`lsof -p ${pid} -Fn | grep ^n | grep cwd | head -1`, {
10909
+ const output = (0, import_child_process13.execSync)(`lsof -p ${pid} -Fn | grep ^n | grep cwd | head -1`, {
10823
10910
  encoding: "utf8",
10824
10911
  timeout: 5e3
10825
10912
  }).trim();
@@ -10829,7 +10916,7 @@ var DevServerRegistry = class {
10829
10916
  return null;
10830
10917
  } catch {
10831
10918
  try {
10832
- return fs24.readlinkSync(`/proc/${pid}/cwd`);
10919
+ return fs25.readlinkSync(`/proc/${pid}/cwd`);
10833
10920
  } catch {
10834
10921
  return null;
10835
10922
  }
@@ -10844,7 +10931,7 @@ var DevServerRegistry = class {
10844
10931
  findProcessesInWorktree(worktreePath) {
10845
10932
  const pids = [];
10846
10933
  try {
10847
- const output = (0, import_child_process12.execSync)(
10934
+ const output = (0, import_child_process13.execSync)(
10848
10935
  `lsof -c node -c next | grep "${worktreePath}" | awk '{print $2}' | sort -u`,
10849
10936
  { encoding: "utf8", timeout: 5e3 }
10850
10937
  ).trim();
@@ -10909,7 +10996,7 @@ var DevServerRegistry = class {
10909
10996
  */
10910
10997
  findProcessesOnPort(port) {
10911
10998
  try {
10912
- const output = (0, import_child_process12.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
10999
+ const output = (0, import_child_process13.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
10913
11000
  if (!output) {
10914
11001
  return [];
10915
11002
  }
@@ -10943,24 +11030,24 @@ var DevServerRegistry = class {
10943
11030
  return killed;
10944
11031
  }
10945
11032
  wait(ms) {
10946
- return new Promise((resolve8) => setTimeout(resolve8, ms));
11033
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
10947
11034
  }
10948
11035
  killWsServerOnPort(wsPort, worktreePath) {
10949
11036
  const killed = [];
10950
11037
  try {
10951
- const output = (0, import_child_process12.execSync)(`lsof -ti:${wsPort} 2>/dev/null || true`, {
11038
+ const output = (0, import_child_process13.execSync)(`lsof -ti:${wsPort} 2>/dev/null || true`, {
10952
11039
  encoding: "utf8",
10953
11040
  timeout: 5e3
10954
11041
  }).trim();
10955
11042
  if (!output) {
10956
11043
  return killed;
10957
11044
  }
10958
- const expectedPath = path24.resolve(worktreePath);
11045
+ const expectedPath = path25.resolve(worktreePath);
10959
11046
  const pids = output.split("\n").map((line) => parseInt(line, 10)).filter((pid) => !isNaN(pid) && pid > 0);
10960
11047
  for (const pid of pids) {
10961
- const command = (0, import_child_process12.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8", timeout: 5e3 }).trim();
11048
+ const command = (0, import_child_process13.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8", timeout: 5e3 }).trim();
10962
11049
  const cwd = this.getProcessCwd(pid);
10963
- const cwdMatches = cwd ? path24.resolve(cwd) === expectedPath : false;
11050
+ const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
10964
11051
  const isWsServer = command.includes("ws-server.js") || command.includes("build:ws-server");
10965
11052
  if (!isWsServer || !cwdMatches) {
10966
11053
  continue;
@@ -11240,7 +11327,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11240
11327
  */
11241
11328
  async killProcessOnPort(port) {
11242
11329
  try {
11243
- const result = (0, import_child_process13.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
11330
+ const result = (0, import_child_process14.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
11244
11331
  if (!result) {
11245
11332
  return true;
11246
11333
  }
@@ -11248,15 +11335,15 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11248
11335
  console.log(`[DevServerRunner] Found ${pids.length} process(es) on port ${port}`);
11249
11336
  for (const pid of pids) {
11250
11337
  try {
11251
- (0, import_child_process13.execSync)(`kill -15 ${pid} 2>/dev/null || true`);
11338
+ (0, import_child_process14.execSync)(`kill -15 ${pid} 2>/dev/null || true`);
11252
11339
  } catch {
11253
11340
  }
11254
11341
  }
11255
11342
  await this.wait(1e3);
11256
11343
  for (const pid of pids) {
11257
11344
  try {
11258
- (0, import_child_process13.execSync)(`kill -0 ${pid} 2>/dev/null`);
11259
- (0, import_child_process13.execSync)(`kill -9 ${pid} 2>/dev/null || true`);
11345
+ (0, import_child_process14.execSync)(`kill -0 ${pid} 2>/dev/null`);
11346
+ (0, import_child_process14.execSync)(`kill -9 ${pid} 2>/dev/null || true`);
11260
11347
  } catch {
11261
11348
  }
11262
11349
  }
@@ -11276,13 +11363,13 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11276
11363
  return { success: false, error: `WS_BIND_CONFLICT: WS port ${wsPort} is already in use.` };
11277
11364
  }
11278
11365
  const registry = getDevServerRegistry();
11279
- const expectedPath = path25.resolve(projectPath);
11366
+ const expectedPath = path26.resolve(projectPath);
11280
11367
  const unsafe = [];
11281
11368
  const killable = [];
11282
11369
  for (const pid of pids) {
11283
11370
  const command = this.getProcessCommand(pid);
11284
11371
  const cwd = registry.getProcessCwd(pid);
11285
- const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11372
+ const cwdMatches = cwd ? path26.resolve(cwd) === expectedPath : false;
11286
11373
  if (this.isLikelyWsServerProcess(command) && cwdMatches) {
11287
11374
  killable.push(pid);
11288
11375
  } else {
@@ -11322,12 +11409,12 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11322
11409
  async cleanupWsServerOnPort(wsPort, worktreePath) {
11323
11410
  if (!wsPort || !await isPortInUse(wsPort)) return;
11324
11411
  const registry = getDevServerRegistry();
11325
- const expectedPath = path25.resolve(worktreePath);
11412
+ const expectedPath = path26.resolve(worktreePath);
11326
11413
  const pids = this.getPidsOnPort(wsPort);
11327
11414
  for (const pid of pids) {
11328
11415
  const command = this.getProcessCommand(pid);
11329
11416
  const cwd = registry.getProcessCwd(pid);
11330
- const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11417
+ const cwdMatches = cwd ? path26.resolve(cwd) === expectedPath : false;
11331
11418
  if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
11332
11419
  continue;
11333
11420
  }
@@ -11340,7 +11427,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11340
11427
  for (const pid of pids) {
11341
11428
  const command = this.getProcessCommand(pid);
11342
11429
  const cwd = registry.getProcessCwd(pid);
11343
- const cwdMatches = cwd ? path25.resolve(cwd) === expectedPath : false;
11430
+ const cwdMatches = cwd ? path26.resolve(cwd) === expectedPath : false;
11344
11431
  if (!this.isLikelyWsServerProcess(command) || !cwdMatches) {
11345
11432
  continue;
11346
11433
  }
@@ -11353,7 +11440,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11353
11440
  }
11354
11441
  getPidsOnPort(port) {
11355
11442
  try {
11356
- const output = (0, import_child_process13.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
11443
+ const output = (0, import_child_process14.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
11357
11444
  if (!output) return [];
11358
11445
  return output.split("\n").map((value) => parseInt(value, 10)).filter((value) => !isNaN(value) && value > 0);
11359
11446
  } catch {
@@ -11362,7 +11449,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11362
11449
  }
11363
11450
  getProcessCommand(pid) {
11364
11451
  try {
11365
- return (0, import_child_process13.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim() || "unknown";
11452
+ return (0, import_child_process14.execSync)(`ps -p ${pid} -o command=`, { encoding: "utf8" }).trim() || "unknown";
11366
11453
  } catch {
11367
11454
  return "unknown";
11368
11455
  }
@@ -11374,8 +11461,8 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11374
11461
  const fallback = `WS_BIND_CONFLICT: ws-server failed to become healthy on WS port ${wsPort}`;
11375
11462
  if (!logPath) return fallback;
11376
11463
  try {
11377
- if (!fs25.existsSync(logPath)) return fallback;
11378
- const content = fs25.readFileSync(logPath, "utf8");
11464
+ if (!fs26.existsSync(logPath)) return fallback;
11465
+ const content = fs26.readFileSync(logPath, "utf8");
11379
11466
  if (content.includes("EADDRINUSE") && content.includes(`${wsPort}`)) {
11380
11467
  return `WS_BIND_CONFLICT: ws-server could not bind to WS port ${wsPort} (EADDRINUSE)`;
11381
11468
  }
@@ -11389,7 +11476,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11389
11476
  // ============ Private Methods ============
11390
11477
  async fetchEnvVars(projectPath) {
11391
11478
  try {
11392
- const config = await (0, import_core12.loadConfig)();
11479
+ const config = await (0, import_core14.loadConfig)();
11393
11480
  if (!config?.access_token || !config?.project_id) {
11394
11481
  return {};
11395
11482
  }
@@ -11399,8 +11486,8 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11399
11486
  cacheTtl: 300
11400
11487
  });
11401
11488
  console.log(`[DevServerRunner] Loaded ${Object.keys(result.envVars).length} env vars`);
11402
- const envFilePath = path25.join(projectPath, ".env");
11403
- if (!fs25.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
11489
+ const envFilePath = path26.join(projectPath, ".env");
11490
+ if (!fs26.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
11404
11491
  console.log(`[DevServerRunner] Writing .env file`);
11405
11492
  writeEnvFile(projectPath, result.envVars);
11406
11493
  }
@@ -11425,7 +11512,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11425
11512
  WS_PORT: String(wsPort),
11426
11513
  NODE_OPTIONS: enhancedNodeOptions
11427
11514
  };
11428
- const proc = (0, import_child_process13.spawn)(cmd, args, {
11515
+ const proc = (0, import_child_process14.spawn)(cmd, args, {
11429
11516
  cwd: projectPath,
11430
11517
  env: mergedEnv,
11431
11518
  stdio: ["ignore", "pipe", "pipe"],
@@ -11517,7 +11604,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11517
11604
  return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
11518
11605
  }
11519
11606
  async checkHealth(port) {
11520
- return new Promise((resolve8) => {
11607
+ return new Promise((resolve9) => {
11521
11608
  const req = http.request(
11522
11609
  {
11523
11610
  hostname: "localhost",
@@ -11526,18 +11613,18 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11526
11613
  method: "HEAD",
11527
11614
  timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
11528
11615
  },
11529
- () => resolve8(true)
11616
+ () => resolve9(true)
11530
11617
  );
11531
- req.on("error", () => resolve8(false));
11618
+ req.on("error", () => resolve9(false));
11532
11619
  req.on("timeout", () => {
11533
11620
  req.destroy();
11534
- resolve8(false);
11621
+ resolve9(false);
11535
11622
  });
11536
11623
  req.end();
11537
11624
  });
11538
11625
  }
11539
11626
  async checkWsHealth(wsPort) {
11540
- return new Promise((resolve8) => {
11627
+ return new Promise((resolve9) => {
11541
11628
  const req = http.request(
11542
11629
  {
11543
11630
  hostname: "127.0.0.1",
@@ -11547,14 +11634,14 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11547
11634
  timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
11548
11635
  },
11549
11636
  (res) => {
11550
- resolve8(res.statusCode === 200);
11637
+ resolve9(res.statusCode === 200);
11551
11638
  res.resume();
11552
11639
  }
11553
11640
  );
11554
- req.on("error", () => resolve8(false));
11641
+ req.on("error", () => resolve9(false));
11555
11642
  req.on("timeout", () => {
11556
11643
  req.destroy();
11557
- resolve8(false);
11644
+ resolve9(false);
11558
11645
  });
11559
11646
  req.end();
11560
11647
  });
@@ -11582,28 +11669,28 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11582
11669
  return false;
11583
11670
  }
11584
11671
  wait(ms) {
11585
- return new Promise((resolve8) => setTimeout(resolve8, ms));
11672
+ return new Promise((resolve9) => setTimeout(resolve9, ms));
11586
11673
  }
11587
11674
  getLogsDir() {
11588
- const logsDir = path25.join((0, import_core12.getConfigDir)(), "logs");
11589
- if (!fs25.existsSync(logsDir)) {
11590
- fs25.mkdirSync(logsDir, { recursive: true });
11675
+ const logsDir = path26.join((0, import_core14.getConfigDir)(), "logs");
11676
+ if (!fs26.existsSync(logsDir)) {
11677
+ fs26.mkdirSync(logsDir, { recursive: true });
11591
11678
  }
11592
11679
  return logsDir;
11593
11680
  }
11594
11681
  getLogFilePath(moduleUid) {
11595
- return path25.join(this.getLogsDir(), `dev-${moduleUid}.log`);
11682
+ return path26.join(this.getLogsDir(), `dev-${moduleUid}.log`);
11596
11683
  }
11597
11684
  rotateLogIfNeeded(logPath) {
11598
11685
  try {
11599
- if (fs25.existsSync(logPath)) {
11600
- const stats = fs25.statSync(logPath);
11686
+ if (fs26.existsSync(logPath)) {
11687
+ const stats = fs26.statSync(logPath);
11601
11688
  if (stats.size > DEV_SERVER_CONSTANTS.MAX_LOG_SIZE_BYTES) {
11602
11689
  const backupPath = `${logPath}.1`;
11603
- if (fs25.existsSync(backupPath)) {
11604
- fs25.unlinkSync(backupPath);
11690
+ if (fs26.existsSync(backupPath)) {
11691
+ fs26.unlinkSync(backupPath);
11605
11692
  }
11606
- fs25.renameSync(logPath, backupPath);
11693
+ fs26.renameSync(logPath, backupPath);
11607
11694
  }
11608
11695
  }
11609
11696
  } catch {
@@ -11614,7 +11701,7 @@ var DevServerRunner = class extends import_events2.EventEmitter {
11614
11701
  try {
11615
11702
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11616
11703
  const prefix = isError ? "ERR" : "OUT";
11617
- fs25.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
11704
+ fs26.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
11618
11705
  `);
11619
11706
  } catch {
11620
11707
  }
@@ -11781,7 +11868,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11781
11868
  for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
11782
11869
  if (attempt > 1) {
11783
11870
  console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
11784
- await new Promise((resolve8) => setTimeout(resolve8, 2e3));
11871
+ await new Promise((resolve9) => setTimeout(resolve9, 2e3));
11785
11872
  }
11786
11873
  tunnelResult = await this.tunnel.startTunnel({
11787
11874
  moduleUid,
@@ -11913,7 +12000,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
11913
12000
  }
11914
12001
  console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
11915
12002
  await this.stopPreview(moduleUid);
11916
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
12003
+ await new Promise((resolve9) => setTimeout(resolve9, 1e3));
11917
12004
  return this.startPreview({
11918
12005
  moduleUid,
11919
12006
  worktreePath: state.worktreePath,
@@ -12070,7 +12157,7 @@ function getPreviewManager() {
12070
12157
  }
12071
12158
 
12072
12159
  // src/daemon/handlers/worktree-handlers.ts
12073
- var import_core13 = __toESM(require_dist());
12160
+ var import_core15 = __toESM(require_dist());
12074
12161
 
12075
12162
  // src/api/worktree-api.ts
12076
12163
  async function deleteWorktree(config, moduleUid) {
@@ -12106,8 +12193,8 @@ async function deleteWorktree(config, moduleUid) {
12106
12193
  }
12107
12194
 
12108
12195
  // src/daemon/package-manager.ts
12109
- var fs26 = __toESM(require("fs"));
12110
- var path26 = __toESM(require("path"));
12196
+ var fs27 = __toESM(require("fs"));
12197
+ var path27 = __toESM(require("path"));
12111
12198
  function pnpmCommand(args) {
12112
12199
  return [
12113
12200
  "if command -v pnpm >/dev/null 2>&1; then",
@@ -12125,13 +12212,13 @@ var PACKAGE_MANAGERS = {
12125
12212
  {
12126
12213
  name: "pnpm",
12127
12214
  detector: (p) => {
12128
- if (fs26.existsSync(path26.join(p, "pnpm-lock.yaml"))) {
12215
+ if (fs27.existsSync(path27.join(p, "pnpm-lock.yaml"))) {
12129
12216
  return "pnpm-lock.yaml";
12130
12217
  }
12131
- const pkgJsonPath = path26.join(p, "package.json");
12132
- if (fs26.existsSync(pkgJsonPath)) {
12218
+ const pkgJsonPath = path27.join(p, "package.json");
12219
+ if (fs27.existsSync(pkgJsonPath)) {
12133
12220
  try {
12134
- const pkg = JSON.parse(fs26.readFileSync(pkgJsonPath, "utf-8"));
12221
+ const pkg = JSON.parse(fs27.readFileSync(pkgJsonPath, "utf-8"));
12135
12222
  if (pkg.packageManager?.startsWith("pnpm")) {
12136
12223
  return "package.json (packageManager field)";
12137
12224
  }
@@ -12147,7 +12234,7 @@ var PACKAGE_MANAGERS = {
12147
12234
  },
12148
12235
  {
12149
12236
  name: "yarn",
12150
- detector: (p) => fs26.existsSync(path26.join(p, "yarn.lock")) ? "yarn.lock" : null,
12237
+ detector: (p) => fs27.existsSync(path27.join(p, "yarn.lock")) ? "yarn.lock" : null,
12151
12238
  config: {
12152
12239
  installCmd: "yarn install --frozen-lockfile",
12153
12240
  cacheEnvVar: "YARN_CACHE_FOLDER"
@@ -12155,14 +12242,14 @@ var PACKAGE_MANAGERS = {
12155
12242
  },
12156
12243
  {
12157
12244
  name: "bun",
12158
- detector: (p) => fs26.existsSync(path26.join(p, "bun.lockb")) ? "bun.lockb" : null,
12245
+ detector: (p) => fs27.existsSync(path27.join(p, "bun.lockb")) ? "bun.lockb" : null,
12159
12246
  config: {
12160
12247
  installCmd: "bun install --frozen-lockfile"
12161
12248
  }
12162
12249
  },
12163
12250
  {
12164
12251
  name: "npm",
12165
- detector: (p) => fs26.existsSync(path26.join(p, "package-lock.json")) ? "package-lock.json" : null,
12252
+ detector: (p) => fs27.existsSync(path27.join(p, "package-lock.json")) ? "package-lock.json" : null,
12166
12253
  config: {
12167
12254
  installCmd: "npm ci"
12168
12255
  }
@@ -12171,7 +12258,7 @@ var PACKAGE_MANAGERS = {
12171
12258
  // EP1222: Default to pnpm for new projects without lockfile
12172
12259
  // This encourages standardization on pnpm across Episoda projects
12173
12260
  name: "pnpm",
12174
- detector: (p) => fs26.existsSync(path26.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
12261
+ detector: (p) => fs27.existsSync(path27.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
12175
12262
  config: {
12176
12263
  installCmd: pnpmCommand("install"),
12177
12264
  cacheEnvVar: "PNPM_HOME"
@@ -12182,13 +12269,13 @@ var PACKAGE_MANAGERS = {
12182
12269
  {
12183
12270
  name: "uv",
12184
12271
  detector: (p) => {
12185
- const pyprojectPath = path26.join(p, "pyproject.toml");
12186
- if (fs26.existsSync(path26.join(p, "uv.lock"))) {
12272
+ const pyprojectPath = path27.join(p, "pyproject.toml");
12273
+ if (fs27.existsSync(path27.join(p, "uv.lock"))) {
12187
12274
  return "uv.lock";
12188
12275
  }
12189
- if (fs26.existsSync(pyprojectPath)) {
12276
+ if (fs27.existsSync(pyprojectPath)) {
12190
12277
  try {
12191
- const content = fs26.readFileSync(pyprojectPath, "utf-8");
12278
+ const content = fs27.readFileSync(pyprojectPath, "utf-8");
12192
12279
  if (content.includes("[tool.uv]") || content.includes("[project]")) {
12193
12280
  return "pyproject.toml";
12194
12281
  }
@@ -12204,7 +12291,7 @@ var PACKAGE_MANAGERS = {
12204
12291
  },
12205
12292
  {
12206
12293
  name: "pip",
12207
- detector: (p) => fs26.existsSync(path26.join(p, "requirements.txt")) ? "requirements.txt" : null,
12294
+ detector: (p) => fs27.existsSync(path27.join(p, "requirements.txt")) ? "requirements.txt" : null,
12208
12295
  config: {
12209
12296
  installCmd: "pip install -r requirements.txt"
12210
12297
  }
@@ -12256,15 +12343,15 @@ function detectPackageManager(worktreePath, preferredLanguages) {
12256
12343
  }
12257
12344
 
12258
12345
  // src/daemon/build-packages.ts
12259
- var fs27 = __toESM(require("fs"));
12260
- var path27 = __toESM(require("path"));
12346
+ var fs28 = __toESM(require("fs"));
12347
+ var path28 = __toESM(require("path"));
12261
12348
  function hasPackageScript(worktreePath, scriptName) {
12262
12349
  try {
12263
- const packageJsonPath = path27.join(worktreePath, "package.json");
12264
- if (!fs27.existsSync(packageJsonPath)) {
12350
+ const packageJsonPath = path28.join(worktreePath, "package.json");
12351
+ if (!fs28.existsSync(packageJsonPath)) {
12265
12352
  return false;
12266
12353
  }
12267
- const packageJson2 = JSON.parse(fs27.readFileSync(packageJsonPath, "utf-8"));
12354
+ const packageJson2 = JSON.parse(fs28.readFileSync(packageJsonPath, "utf-8"));
12268
12355
  return typeof packageJson2.scripts?.[scriptName] === "string";
12269
12356
  } catch {
12270
12357
  return false;
@@ -12315,7 +12402,7 @@ async function getConfigForApi() {
12315
12402
  machine_uuid: process.env.EPISODA_CONTAINER_ID
12316
12403
  };
12317
12404
  }
12318
- return (0, import_core13.loadConfig)();
12405
+ return (0, import_core15.loadConfig)();
12319
12406
  }
12320
12407
  function stripGitCredentials(repoUrl) {
12321
12408
  try {
@@ -12402,24 +12489,24 @@ async function readOriginUrl(bareRepoPath) {
12402
12489
  return null;
12403
12490
  }
12404
12491
  }
12405
- var execAsync2 = (0, import_util2.promisify)(import_child_process14.exec);
12492
+ var execAsync2 = (0, import_util2.promisify)(import_child_process15.exec);
12406
12493
  function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
12407
12494
  if (process.env.EPISODA_MODE !== "cloud") {
12408
12495
  return;
12409
12496
  }
12410
12497
  const homeDir = process.env.HOME || os13.homedir();
12411
- const workspaceConfigPath = path28.join(
12498
+ const workspaceConfigPath = path29.join(
12412
12499
  homeDir,
12413
12500
  "episoda",
12414
12501
  workspaceSlug,
12415
12502
  ".episoda",
12416
12503
  "config.json"
12417
12504
  );
12418
- if (!fs28.existsSync(workspaceConfigPath)) {
12505
+ if (!fs29.existsSync(workspaceConfigPath)) {
12419
12506
  return;
12420
12507
  }
12421
12508
  try {
12422
- const content = fs28.readFileSync(workspaceConfigPath, "utf8");
12509
+ const content = fs29.readFileSync(workspaceConfigPath, "utf8");
12423
12510
  const workspaceConfig = JSON.parse(content);
12424
12511
  let changed = false;
12425
12512
  if (projectId && workspaceConfig.projectId !== projectId && workspaceConfig.project_id !== projectId) {
@@ -12431,7 +12518,7 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
12431
12518
  changed = true;
12432
12519
  }
12433
12520
  if (changed) {
12434
- fs28.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
12521
+ fs29.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
12435
12522
  console.log("[Worktree] Updated workspace config with project context");
12436
12523
  }
12437
12524
  } catch (error) {
@@ -12478,20 +12565,20 @@ async function handleWorktreeCreate(request2) {
12478
12565
  }
12479
12566
  try {
12480
12567
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12481
- const bareRepoPath = path28.join(projectPath, ".bare");
12568
+ const bareRepoPath = path29.join(projectPath, ".bare");
12482
12569
  const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
12483
12570
  const refreshedRepoUrl = await getFreshGithubRepoUrl(repoUrl, projectId);
12484
- if (!fs28.existsSync(bareRepoPath)) {
12571
+ if (!fs29.existsSync(bareRepoPath)) {
12485
12572
  console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
12486
12573
  console.log(`[Worktree] Repo URL: ${stripGitCredentials(refreshedRepoUrl)}`);
12487
- const episodaDir = path28.join(projectPath, ".episoda");
12488
- fs28.mkdirSync(episodaDir, { recursive: true });
12574
+ const episodaDir = path29.join(projectPath, ".episoda");
12575
+ fs29.mkdirSync(episodaDir, { recursive: true });
12489
12576
  try {
12490
12577
  console.log(`[Worktree] K1273: Starting git clone...`);
12491
12578
  await execAsync2(`git clone --bare "${refreshedRepoUrl}" "${bareRepoPath}"`, { env: gitEnv });
12492
12579
  console.log(`[Worktree] K1273: Clone successful`);
12493
12580
  await execAsync2(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
12494
- const configPath = path28.join(episodaDir, "config.json");
12581
+ const configPath = path29.join(episodaDir, "config.json");
12495
12582
  const config = {
12496
12583
  projectId,
12497
12584
  workspaceSlug,
@@ -12500,7 +12587,7 @@ async function handleWorktreeCreate(request2) {
12500
12587
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
12501
12588
  worktrees: []
12502
12589
  };
12503
- fs28.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
12590
+ fs29.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
12504
12591
  console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
12505
12592
  } catch (cloneError) {
12506
12593
  console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
@@ -12544,8 +12631,8 @@ async function handleWorktreeCreate(request2) {
12544
12631
  console.log(`[Worktree] EP1143: Worktree created at ${worktreePath}`);
12545
12632
  if (envVars && Object.keys(envVars).length > 0) {
12546
12633
  const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
12547
- const envPath = path28.join(worktreePath, ".env");
12548
- fs28.writeFileSync(envPath, envContent + "\n", "utf-8");
12634
+ const envPath = path29.join(worktreePath, ".env");
12635
+ fs29.writeFileSync(envPath, envContent + "\n", "utf-8");
12549
12636
  console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
12550
12637
  }
12551
12638
  const isCloud = process.env.EPISODA_MODE === "cloud";
@@ -12697,12 +12784,12 @@ async function handleProjectEject(request2) {
12697
12784
  console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
12698
12785
  try {
12699
12786
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12700
- const bareRepoPath = path28.join(projectPath, ".bare");
12701
- if (!fs28.existsSync(projectPath)) {
12787
+ const bareRepoPath = path29.join(projectPath, ".bare");
12788
+ if (!fs29.existsSync(projectPath)) {
12702
12789
  console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
12703
12790
  return { success: true };
12704
12791
  }
12705
- if (!fs28.existsSync(bareRepoPath)) {
12792
+ if (!fs29.existsSync(bareRepoPath)) {
12706
12793
  console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
12707
12794
  return { success: true };
12708
12795
  }
@@ -12717,12 +12804,12 @@ async function handleProjectEject(request2) {
12717
12804
  };
12718
12805
  }
12719
12806
  }
12720
- const artifactsPath = path28.join(projectPath, "artifacts");
12721
- if (fs28.existsSync(artifactsPath)) {
12807
+ const artifactsPath = path29.join(projectPath, "artifacts");
12808
+ if (fs29.existsSync(artifactsPath)) {
12722
12809
  await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
12723
12810
  }
12724
12811
  console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
12725
- await fs28.promises.rm(projectPath, { recursive: true, force: true });
12812
+ await fs29.promises.rm(projectPath, { recursive: true, force: true });
12726
12813
  console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
12727
12814
  return { success: true };
12728
12815
  } catch (error) {
@@ -12736,7 +12823,7 @@ async function handleProjectEject(request2) {
12736
12823
  async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
12737
12824
  const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
12738
12825
  try {
12739
- const files = await fs28.promises.readdir(artifactsPath);
12826
+ const files = await fs29.promises.readdir(artifactsPath);
12740
12827
  if (files.length === 0) {
12741
12828
  return;
12742
12829
  }
@@ -12750,8 +12837,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12750
12837
  }
12751
12838
  const artifacts = [];
12752
12839
  for (const fileName of files) {
12753
- const filePath = path28.join(artifactsPath, fileName);
12754
- const stat = await fs28.promises.stat(filePath);
12840
+ const filePath = path29.join(artifactsPath, fileName);
12841
+ const stat = await fs29.promises.stat(filePath);
12755
12842
  if (stat.isDirectory()) {
12756
12843
  continue;
12757
12844
  }
@@ -12760,9 +12847,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12760
12847
  continue;
12761
12848
  }
12762
12849
  try {
12763
- const content = await fs28.promises.readFile(filePath);
12850
+ const content = await fs29.promises.readFile(filePath);
12764
12851
  const base64Content = content.toString("base64");
12765
- const ext = path28.extname(fileName).toLowerCase();
12852
+ const ext = path29.extname(fileName).toLowerCase();
12766
12853
  const mimeTypes = {
12767
12854
  ".json": "application/json",
12768
12855
  ".txt": "text/plain",
@@ -12818,8 +12905,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12818
12905
  }
12819
12906
 
12820
12907
  // src/daemon/handlers/project-handlers.ts
12821
- var path29 = __toESM(require("path"));
12822
- var fs29 = __toESM(require("fs"));
12908
+ var path30 = __toESM(require("path"));
12909
+ var fs30 = __toESM(require("fs"));
12823
12910
  function validateSlug(slug, fieldName) {
12824
12911
  if (!slug || typeof slug !== "string") {
12825
12912
  return `${fieldName} is required`;
@@ -12859,14 +12946,14 @@ async function handleProjectSetup(params) {
12859
12946
  console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
12860
12947
  try {
12861
12948
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12862
- const artifactsPath = path29.join(projectPath, "artifacts");
12863
- const configDir = path29.join(projectPath, ".episoda");
12864
- const configPath = path29.join(configDir, "config.json");
12865
- await fs29.promises.mkdir(artifactsPath, { recursive: true });
12866
- await fs29.promises.mkdir(configDir, { recursive: true });
12949
+ const artifactsPath = path30.join(projectPath, "artifacts");
12950
+ const configDir = path30.join(projectPath, ".episoda");
12951
+ const configPath = path30.join(configDir, "config.json");
12952
+ await fs30.promises.mkdir(artifactsPath, { recursive: true });
12953
+ await fs30.promises.mkdir(configDir, { recursive: true });
12867
12954
  let existingConfig = {};
12868
12955
  try {
12869
- const existing = await fs29.promises.readFile(configPath, "utf-8");
12956
+ const existing = await fs30.promises.readFile(configPath, "utf-8");
12870
12957
  existingConfig = JSON.parse(existing);
12871
12958
  } catch {
12872
12959
  }
@@ -12879,7 +12966,7 @@ async function handleProjectSetup(params) {
12879
12966
  // Only set created_at if not already present
12880
12967
  created_at: existingConfig.created_at || (/* @__PURE__ */ new Date()).toISOString()
12881
12968
  };
12882
- await fs29.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12969
+ await fs30.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12883
12970
  console.log(`[ProjectSetup] EP1199: Project setup complete at ${projectPath}`);
12884
12971
  return {
12885
12972
  success: true,
@@ -12898,9 +12985,9 @@ async function handleProjectSetup(params) {
12898
12985
 
12899
12986
  // src/daemon/handlers/pty-handler.ts
12900
12987
  var pty = __toESM(require("@lydell/node-pty"));
12901
- var fs30 = __toESM(require("fs"));
12988
+ var fs31 = __toESM(require("fs"));
12902
12989
  var os14 = __toESM(require("os"));
12903
- var path30 = __toESM(require("path"));
12990
+ var path31 = __toESM(require("path"));
12904
12991
  var INACTIVITY_TIMEOUT_MS3 = 30 * 60 * 1e3;
12905
12992
  var sessions = /* @__PURE__ */ new Map();
12906
12993
  async function handlePtySpawn(payload, client) {
@@ -13070,27 +13157,27 @@ function createCredentialBootstrap(payload, agentRunId) {
13070
13157
  if (bootstrap.provider === "codex" && !bootstrap.oauth.refresh_token) {
13071
13158
  throw new Error("Codex PTY spawn aborted: refresh_token is missing from credential bundle. Please reconnect Codex in Settings.");
13072
13159
  }
13073
- const baseDir = fs30.mkdtempSync(path30.join(os14.tmpdir(), `episoda-pty-${agentRunId}-`));
13160
+ const baseDir = fs31.mkdtempSync(path31.join(os14.tmpdir(), `episoda-pty-${agentRunId}-`));
13074
13161
  const cleanupDirs = [baseDir];
13075
13162
  if (bootstrap.provider === "claude") {
13076
- const claudeConfigDir = path30.join(baseDir, ".claude");
13077
- fs30.mkdirSync(claudeConfigDir, { recursive: true });
13078
- const credentialsPath = path30.join(claudeConfigDir, ".credentials.json");
13163
+ const claudeConfigDir = path31.join(baseDir, ".claude");
13164
+ fs31.mkdirSync(claudeConfigDir, { recursive: true });
13165
+ const credentialsPath = path31.join(claudeConfigDir, ".credentials.json");
13079
13166
  const claudeAiOauth = {
13080
13167
  accessToken: bootstrap.oauth.access_token
13081
13168
  };
13082
13169
  if (bootstrap.oauth.refresh_token) claudeAiOauth.refreshToken = bootstrap.oauth.refresh_token;
13083
13170
  if (bootstrap.oauth.expires_at) claudeAiOauth.expiresAt = new Date(bootstrap.oauth.expires_at).getTime();
13084
13171
  if (bootstrap.oauth.scopes) claudeAiOauth.scopes = bootstrap.oauth.scopes;
13085
- fs30.writeFileSync(credentialsPath, JSON.stringify({ claudeAiOauth }, null, 2), { mode: 384 });
13172
+ fs31.writeFileSync(credentialsPath, JSON.stringify({ claudeAiOauth }, null, 2), { mode: 384 });
13086
13173
  return {
13087
13174
  env: { CLAUDE_CONFIG_DIR: claudeConfigDir },
13088
13175
  cleanupDirs
13089
13176
  };
13090
13177
  }
13091
- const codexHome = path30.join(baseDir, ".codex");
13092
- fs30.mkdirSync(codexHome, { recursive: true });
13093
- const authPath = path30.join(codexHome, "auth.json");
13178
+ const codexHome = path31.join(baseDir, ".codex");
13179
+ fs31.mkdirSync(codexHome, { recursive: true });
13180
+ const authPath = path31.join(codexHome, "auth.json");
13094
13181
  const authPayload = {
13095
13182
  auth_mode: "chatgpt",
13096
13183
  OPENAI_API_KEY: null,
@@ -13104,7 +13191,7 @@ function createCredentialBootstrap(payload, agentRunId) {
13104
13191
  account_id: bootstrap.oauth.account_id
13105
13192
  }
13106
13193
  };
13107
- fs30.writeFileSync(authPath, JSON.stringify(authPayload, null, 2), { mode: 384 });
13194
+ fs31.writeFileSync(authPath, JSON.stringify(authPayload, null, 2), { mode: 384 });
13108
13195
  return {
13109
13196
  env: { CODEX_HOME: codexHome },
13110
13197
  cleanupDirs,
@@ -13117,7 +13204,7 @@ function syncCredentialUpdateAfterExit(session, agent_run_id, client) {
13117
13204
  cleanupCredentialDirs(session.credentialDirs);
13118
13205
  return;
13119
13206
  }
13120
- void fs30.promises.readFile(authPath, "utf8").then((raw) => {
13207
+ void fs31.promises.readFile(authPath, "utf8").then((raw) => {
13121
13208
  const tokens = extractCredentialTokens(raw);
13122
13209
  if (!tokens.access_token) {
13123
13210
  console.warn(`[PTY] EP1472: No access_token in auth.json for ${agent_run_id}, skipping credential update`);
@@ -13169,8 +13256,8 @@ function getErrorMessage(error) {
13169
13256
  function cleanupCredentialDirs(dirs) {
13170
13257
  for (const dirPath of dirs) {
13171
13258
  try {
13172
- if (fs30.existsSync(dirPath)) {
13173
- fs30.rmSync(dirPath, { recursive: true, force: true });
13259
+ if (fs31.existsSync(dirPath)) {
13260
+ fs31.rmSync(dirPath, { recursive: true, force: true });
13174
13261
  }
13175
13262
  } catch (error) {
13176
13263
  console.warn(`[PTY] Failed to cleanup credential dir ${dirPath}:`, error);
@@ -13179,10 +13266,10 @@ function cleanupCredentialDirs(dirs) {
13179
13266
  }
13180
13267
 
13181
13268
  // src/utils/dev-server.ts
13182
- var import_child_process15 = require("child_process");
13183
- var import_core14 = __toESM(require_dist());
13184
- var fs31 = __toESM(require("fs"));
13185
- var path31 = __toESM(require("path"));
13269
+ var import_child_process16 = require("child_process");
13270
+ var import_core16 = __toESM(require_dist());
13271
+ var fs32 = __toESM(require("fs"));
13272
+ var path32 = __toESM(require("path"));
13186
13273
  var MAX_RESTART_ATTEMPTS = 5;
13187
13274
  var INITIAL_RESTART_DELAY_MS = 2e3;
13188
13275
  var MAX_RESTART_DELAY_MS = 3e4;
@@ -13190,26 +13277,26 @@ var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
13190
13277
  var NODE_MEMORY_LIMIT_MB = 2048;
13191
13278
  var activeServers = /* @__PURE__ */ new Map();
13192
13279
  function getLogsDir() {
13193
- const logsDir = path31.join((0, import_core14.getConfigDir)(), "logs");
13194
- if (!fs31.existsSync(logsDir)) {
13195
- fs31.mkdirSync(logsDir, { recursive: true });
13280
+ const logsDir = path32.join((0, import_core16.getConfigDir)(), "logs");
13281
+ if (!fs32.existsSync(logsDir)) {
13282
+ fs32.mkdirSync(logsDir, { recursive: true });
13196
13283
  }
13197
13284
  return logsDir;
13198
13285
  }
13199
13286
  function getLogFilePath(moduleUid) {
13200
- return path31.join(getLogsDir(), `dev-${moduleUid}.log`);
13287
+ return path32.join(getLogsDir(), `dev-${moduleUid}.log`);
13201
13288
  }
13202
13289
  function rotateLogIfNeeded(logPath) {
13203
13290
  try {
13204
- if (fs31.existsSync(logPath)) {
13205
- const stats = fs31.statSync(logPath);
13291
+ if (fs32.existsSync(logPath)) {
13292
+ const stats = fs32.statSync(logPath);
13206
13293
  if (stats.size > MAX_LOG_SIZE_BYTES2) {
13207
13294
  const backupPath = `${logPath}.1`;
13208
- if (fs31.existsSync(backupPath)) {
13209
- fs31.unlinkSync(backupPath);
13295
+ if (fs32.existsSync(backupPath)) {
13296
+ fs32.unlinkSync(backupPath);
13210
13297
  }
13211
- fs31.renameSync(logPath, backupPath);
13212
- console.log(`[DevServer] EP932: Rotated log file for ${path31.basename(logPath)}`);
13298
+ fs32.renameSync(logPath, backupPath);
13299
+ console.log(`[DevServer] EP932: Rotated log file for ${path32.basename(logPath)}`);
13213
13300
  }
13214
13301
  }
13215
13302
  } catch (error) {
@@ -13222,13 +13309,13 @@ function writeToLog(logPath, line, isError = false) {
13222
13309
  const prefix = isError ? "ERR" : "OUT";
13223
13310
  const logLine = `[${timestamp}] [${prefix}] ${line}
13224
13311
  `;
13225
- fs31.appendFileSync(logPath, logLine);
13312
+ fs32.appendFileSync(logPath, logLine);
13226
13313
  } catch {
13227
13314
  }
13228
13315
  }
13229
13316
  async function killProcessOnPort(port) {
13230
13317
  try {
13231
- const result = (0, import_child_process15.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
13318
+ const result = (0, import_child_process16.execSync)(`lsof -ti:${port} 2>/dev/null || true`, { encoding: "utf8" }).trim();
13232
13319
  if (!result) {
13233
13320
  console.log(`[DevServer] EP929: No process found on port ${port}`);
13234
13321
  return true;
@@ -13237,21 +13324,21 @@ async function killProcessOnPort(port) {
13237
13324
  console.log(`[DevServer] EP929: Found ${pids.length} process(es) on port ${port}: ${pids.join(", ")}`);
13238
13325
  for (const pid of pids) {
13239
13326
  try {
13240
- (0, import_child_process15.execSync)(`kill -15 ${pid} 2>/dev/null || true`, { encoding: "utf8" });
13327
+ (0, import_child_process16.execSync)(`kill -15 ${pid} 2>/dev/null || true`, { encoding: "utf8" });
13241
13328
  console.log(`[DevServer] EP929: Sent SIGTERM to PID ${pid}`);
13242
13329
  } catch {
13243
13330
  }
13244
13331
  }
13245
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
13332
+ await new Promise((resolve9) => setTimeout(resolve9, 1e3));
13246
13333
  for (const pid of pids) {
13247
13334
  try {
13248
- (0, import_child_process15.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
13249
- (0, import_child_process15.execSync)(`kill -9 ${pid} 2>/dev/null || true`, { encoding: "utf8" });
13335
+ (0, import_child_process16.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
13336
+ (0, import_child_process16.execSync)(`kill -9 ${pid} 2>/dev/null || true`, { encoding: "utf8" });
13250
13337
  console.log(`[DevServer] EP929: Force killed PID ${pid}`);
13251
13338
  } catch {
13252
13339
  }
13253
13340
  }
13254
- await new Promise((resolve8) => setTimeout(resolve8, 500));
13341
+ await new Promise((resolve9) => setTimeout(resolve9, 500));
13255
13342
  const stillInUse = await isPortInUse(port);
13256
13343
  if (stillInUse) {
13257
13344
  console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
@@ -13271,7 +13358,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
13271
13358
  if (await isPortInUse(port)) {
13272
13359
  return true;
13273
13360
  }
13274
- await new Promise((resolve8) => setTimeout(resolve8, checkInterval));
13361
+ await new Promise((resolve9) => setTimeout(resolve9, checkInterval));
13275
13362
  }
13276
13363
  return false;
13277
13364
  }
@@ -13297,7 +13384,7 @@ function spawnDevServerProcess(projectPath, port, moduleUid, logPath, customComm
13297
13384
  if (injectedCount > 0) {
13298
13385
  console.log(`[DevServer] EP998: Injecting ${injectedCount} env vars from database`);
13299
13386
  }
13300
- const devProcess = (0, import_child_process15.spawn)(cmd, args, {
13387
+ const devProcess = (0, import_child_process16.spawn)(cmd, args, {
13301
13388
  cwd: projectPath,
13302
13389
  env: mergedEnv,
13303
13390
  stdio: ["ignore", "pipe", "pipe"],
@@ -13343,7 +13430,7 @@ async function handleProcessExit(moduleUid, code, signal) {
13343
13430
  const delay = calculateRestartDelay(serverInfo.restartCount);
13344
13431
  console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
13345
13432
  writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
13346
- await new Promise((resolve8) => setTimeout(resolve8, delay));
13433
+ await new Promise((resolve9) => setTimeout(resolve9, delay));
13347
13434
  if (!activeServers.has(moduleUid)) {
13348
13435
  console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
13349
13436
  return;
@@ -13391,7 +13478,7 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
13391
13478
  console.log(`[DevServer] EP932: Starting dev server for ${moduleUid} on port ${port} (auto-restart: ${autoRestart})...`);
13392
13479
  let injectedEnvVars = {};
13393
13480
  try {
13394
- const config = await (0, import_core14.loadConfig)();
13481
+ const config = await (0, import_core16.loadConfig)();
13395
13482
  if (config?.access_token && config?.project_id) {
13396
13483
  const apiUrl = config.api_url || "https://episoda.dev";
13397
13484
  const result = await fetchEnvVarsWithCache(apiUrl, config.access_token, {
@@ -13401,8 +13488,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
13401
13488
  });
13402
13489
  injectedEnvVars = result.envVars;
13403
13490
  console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
13404
- const envFilePath = path31.join(projectPath, ".env");
13405
- if (!fs31.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
13491
+ const envFilePath = path32.join(projectPath, ".env");
13492
+ if (!fs32.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
13406
13493
  console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
13407
13494
  writeEnvFile(projectPath, injectedEnvVars);
13408
13495
  }
@@ -13468,7 +13555,7 @@ async function stopDevServer(moduleUid) {
13468
13555
  writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
13469
13556
  }
13470
13557
  serverInfo.process.kill("SIGTERM");
13471
- await new Promise((resolve8) => setTimeout(resolve8, 2e3));
13558
+ await new Promise((resolve9) => setTimeout(resolve9, 2e3));
13472
13559
  if (!serverInfo.process.killed) {
13473
13560
  serverInfo.process.kill("SIGKILL");
13474
13561
  }
@@ -13486,7 +13573,7 @@ async function restartDevServer(moduleUid) {
13486
13573
  writeToLog(logFile, `Manual restart requested`, false);
13487
13574
  }
13488
13575
  await stopDevServer(moduleUid);
13489
- await new Promise((resolve8) => setTimeout(resolve8, 1e3));
13576
+ await new Promise((resolve9) => setTimeout(resolve9, 1e3));
13490
13577
  if (await isPortInUse(port)) {
13491
13578
  await killProcessOnPort(port);
13492
13579
  }
@@ -13507,6 +13594,32 @@ function getDevServerStatus() {
13507
13594
  }));
13508
13595
  }
13509
13596
 
13597
+ // src/ipc/protocol-version.ts
13598
+ var PROTOCOL_VERSION = "1";
13599
+
13600
+ // src/cli-version.ts
13601
+ var import_fs2 = require("fs");
13602
+ var import_path = require("path");
13603
+ var FALLBACK_CLI_VERSION = "0.2.206";
13604
+ function getCliVersion() {
13605
+ const candidatePaths = [
13606
+ (0, import_path.resolve)(__dirname, "..", "package.json"),
13607
+ (0, import_path.resolve)(__dirname, "../../package.json")
13608
+ ];
13609
+ for (const candidatePath of candidatePaths) {
13610
+ try {
13611
+ if (!(0, import_fs2.existsSync)(candidatePath)) continue;
13612
+ const parsed = JSON.parse((0, import_fs2.readFileSync)(candidatePath, "utf8"));
13613
+ if (typeof parsed.version === "string" && parsed.version.length > 0) {
13614
+ return parsed.version;
13615
+ }
13616
+ } catch {
13617
+ }
13618
+ }
13619
+ return FALLBACK_CLI_VERSION;
13620
+ }
13621
+ var CLI_VERSION = getCliVersion();
13622
+
13510
13623
  // src/daemon/ipc-router.ts
13511
13624
  var IPCRouter = class {
13512
13625
  constructor(host, ipcServer) {
@@ -13521,6 +13634,9 @@ var IPCRouter = class {
13521
13634
  return { status: "ok" };
13522
13635
  });
13523
13636
  this.ipcServer.on("status", async () => {
13637
+ const lifecycleMetadata = readDaemonLifecycleMetadata();
13638
+ const pinnedVersion = process.env.EPISODA_CLI_PIN_VERSION || null;
13639
+ const installChannel = process.env.EPISODA_CLI_INSTALL_CHANNEL || "embedded";
13524
13640
  const projects = getAllProjects().map((p) => ({
13525
13641
  id: p.id,
13526
13642
  path: p.path,
@@ -13543,6 +13659,16 @@ var IPCRouter = class {
13543
13659
  hostname: os15.hostname(),
13544
13660
  platform: os15.platform(),
13545
13661
  arch: os15.arch(),
13662
+ daemonVersion: CLI_VERSION,
13663
+ protocolVersion: PROTOCOL_VERSION,
13664
+ installChannel,
13665
+ serviceMode: lifecycleMetadata.serviceMode,
13666
+ updatePolicy: "manual",
13667
+ updateState: pinnedVersion ? "pinned" : installChannel === "linked" ? "linked" : "manual",
13668
+ pinnedVersion,
13669
+ lastRestartReason: lifecycleMetadata.lastRestartReason,
13670
+ lastStartedAt: lifecycleMetadata.lastStartedAt,
13671
+ attachedProjects: projects,
13546
13672
  projects
13547
13673
  };
13548
13674
  });
@@ -13568,7 +13694,7 @@ var IPCRouter = class {
13568
13694
  if (attempt < MAX_RETRIES) {
13569
13695
  const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
13570
13696
  console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
13571
- await new Promise((resolve8) => setTimeout(resolve8, delay));
13697
+ await new Promise((resolve9) => setTimeout(resolve9, delay));
13572
13698
  await this.host.disconnectProject(projectPath);
13573
13699
  }
13574
13700
  }
@@ -13621,7 +13747,7 @@ var IPCRouter = class {
13621
13747
  };
13622
13748
  });
13623
13749
  this.ipcServer.on("verify-server-connection", async () => {
13624
- const config = await (0, import_core15.loadConfig)();
13750
+ const config = await (0, import_core17.loadConfig)();
13625
13751
  if (!config?.access_token || !config?.api_url) {
13626
13752
  return {
13627
13753
  verified: false,
@@ -13744,331 +13870,19 @@ var IPCRouter = class {
13744
13870
  };
13745
13871
 
13746
13872
  // src/daemon/update-manager.ts
13747
- var fs32 = __toESM(require("fs"));
13748
- var path32 = __toESM(require("path"));
13749
- var import_child_process17 = require("child_process");
13750
- var import_core17 = __toESM(require_dist());
13751
- var semver2 = __toESM(require("semver"));
13752
-
13753
- // src/utils/update-checker.ts
13754
- var import_child_process16 = require("child_process");
13755
- var semver = __toESM(require("semver"));
13756
-
13757
- // src/ipc/ipc-client.ts
13758
- var import_core16 = __toESM(require_dist());
13759
-
13760
- // src/utils/update-checker.ts
13761
- var PACKAGE_NAME = "@episoda/cli";
13762
- var LEGACY_PACKAGE_NAME = "episoda";
13763
- var NPM_REGISTRY = "https://registry.npmjs.org";
13764
- function isFileLinkedInstall() {
13765
- try {
13766
- const output = (0, import_child_process16.execSync)(`npm list -g ${PACKAGE_NAME} --json`, {
13767
- stdio: ["pipe", "pipe", "pipe"],
13768
- timeout: 1e4
13769
- }).toString();
13770
- const data = JSON.parse(output);
13771
- const resolved = data?.dependencies?.[PACKAGE_NAME]?.resolved;
13772
- return typeof resolved === "string" && resolved.startsWith("file:");
13773
- } catch {
13774
- return false;
13775
- }
13776
- }
13777
- async function checkForUpdates(currentVersion) {
13778
- const isLinked = isFileLinkedInstall();
13779
- if (isLinked) {
13780
- return {
13781
- currentVersion,
13782
- latestVersion: currentVersion,
13783
- updateAvailable: false,
13784
- isLinked: true
13785
- };
13786
- }
13787
- try {
13788
- const controller = new AbortController();
13789
- const timeoutId = setTimeout(() => controller.abort(), 5e3);
13790
- const response = await fetch(`${NPM_REGISTRY}/${PACKAGE_NAME}/latest`, {
13791
- signal: controller.signal
13792
- });
13793
- clearTimeout(timeoutId);
13794
- if (!response.ok) {
13795
- return { currentVersion, latestVersion: currentVersion, updateAvailable: false };
13796
- }
13797
- const data = await response.json();
13798
- const latestVersion = data.version;
13799
- return {
13800
- currentVersion,
13801
- latestVersion,
13802
- updateAvailable: semver.gt(latestVersion, currentVersion)
13803
- };
13804
- } catch (error) {
13805
- return { currentVersion, latestVersion: currentVersion, updateAvailable: false, offline: true };
13806
- }
13807
- }
13808
- function performSyncUpdate(targetVersion) {
13809
- try {
13810
- (0, import_child_process16.execSync)(`npm install -g ${PACKAGE_NAME}@${targetVersion}`, {
13811
- stdio: ["pipe", "pipe", "pipe"],
13812
- timeout: 12e4
13813
- // 2 minute timeout
13814
- });
13815
- return { success: true };
13816
- } catch (error) {
13817
- return {
13818
- success: false,
13819
- error: error instanceof Error ? error.message : String(error)
13820
- };
13873
+ var UpdateManager = class {
13874
+ constructor(_host, _currentVersion, _daemonEntryFile) {
13875
+ this._host = _host;
13876
+ this._currentVersion = _currentVersion;
13877
+ this._daemonEntryFile = _daemonEntryFile;
13821
13878
  }
13822
- }
13823
- function getInstalledVersion() {
13824
- try {
13825
- const output = (0, import_child_process16.execSync)(`npm list -g ${PACKAGE_NAME} --json`, {
13826
- stdio: ["pipe", "pipe", "pipe"],
13827
- timeout: 1e4
13828
- }).toString();
13829
- const data = JSON.parse(output);
13830
- return data?.dependencies?.[PACKAGE_NAME]?.version || null;
13831
- } catch {
13832
- return null;
13833
- }
13834
- }
13835
- function getLegacyInstalledVersion() {
13836
- try {
13837
- const output = (0, import_child_process16.execSync)(`npm list -g ${LEGACY_PACKAGE_NAME} --json`, {
13838
- stdio: ["pipe", "pipe", "pipe"],
13839
- timeout: 1e4
13840
- }).toString();
13841
- const data = JSON.parse(output);
13842
- return data?.dependencies?.[LEGACY_PACKAGE_NAME]?.version || null;
13843
- } catch {
13844
- return null;
13845
- }
13846
- }
13847
- function detectCliInstallChannel(embeddedVersion) {
13848
- const scopedVersion = getInstalledVersion();
13849
- const legacyVersion = getLegacyInstalledVersion();
13850
- const effectiveVersion = scopedVersion || legacyVersion || embeddedVersion || null;
13851
- return {
13852
- scopedVersion,
13853
- legacyVersion,
13854
- legacyOnly: !scopedVersion && !!legacyVersion,
13855
- effectiveVersion
13856
- };
13857
- }
13858
- function resolveEffectiveCliVersion(embeddedVersion) {
13859
- const installedVersion = detectCliInstallChannel(embeddedVersion).effectiveVersion;
13860
- if (!installedVersion) {
13861
- return embeddedVersion;
13862
- }
13863
- if (semver.valid(installedVersion) && semver.valid(embeddedVersion)) {
13864
- return semver.gt(installedVersion, embeddedVersion) ? installedVersion : embeddedVersion;
13865
- }
13866
- return installedVersion === embeddedVersion ? embeddedVersion : installedVersion;
13867
- }
13868
-
13869
- // src/daemon/update-manager.ts
13870
- var UpdateManager = class _UpdateManager {
13871
- constructor(host, currentVersion, daemonEntryFile) {
13872
- this.host = host;
13873
- this.currentVersion = currentVersion;
13874
- this.daemonEntryFile = daemonEntryFile;
13875
- this.pendingUpdateVersion = null;
13876
- this.updateInProgress = false;
13877
- // EP1324: Retry limiting for failed update attempts
13878
- this.lastFailedUpdateVersion = null;
13879
- this.updateFailedAttempts = 0;
13880
- // EP1319: Periodic CLI auto-update for long-lived containers
13881
- this.updateCheckInterval = null;
13882
- }
13883
- static {
13884
- this.UPDATE_CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
13885
- }
13886
- static {
13887
- // 4 hours
13888
- this.MAX_UPDATE_ATTEMPTS = 3;
13889
- }
13890
- /**
13891
- * Prefer installed CLI version when it's newer than embedded bundle metadata.
13892
- * This avoids update churn when daemon bundle version lags package install version.
13893
- */
13894
- getEffectiveCurrentVersion() {
13895
- return resolveEffectiveCliVersion(this.currentVersion);
13896
- }
13897
- isTargetVersionNewer(targetVersion, currentVersion) {
13898
- if (semver2.valid(targetVersion) && semver2.valid(currentVersion)) {
13899
- return semver2.gt(targetVersion, currentVersion);
13900
- }
13901
- return targetVersion !== currentVersion;
13902
- }
13903
- /**
13904
- * Start periodic update checks (every 4 hours).
13905
- * Does nothing if EPISODA_CLI_PIN_VERSION is set.
13906
- */
13907
13879
  startPeriodicChecks() {
13908
- if (process.env.EPISODA_CLI_PIN_VERSION) {
13909
- console.log(`[Daemon] EP1319: CLI version pinned to ${process.env.EPISODA_CLI_PIN_VERSION}, periodic updates disabled`);
13910
- return;
13911
- }
13912
- this.updateCheckInterval = setInterval(() => {
13913
- this.periodicUpdateCheck();
13914
- }, _UpdateManager.UPDATE_CHECK_INTERVAL_MS);
13915
- this.updateCheckInterval.unref();
13916
- console.log("[Daemon] EP1319: Periodic update check started (every 4 hours)");
13917
13880
  }
13918
- /**
13919
- * Stop periodic update checks. Call during shutdown.
13920
- */
13921
13881
  stopPeriodicChecks() {
13922
- if (this.updateCheckInterval) {
13923
- clearInterval(this.updateCheckInterval);
13924
- this.updateCheckInterval = null;
13925
- }
13926
13882
  }
13927
- /**
13928
- * EP1319: Apply deferred update when agent sessions become idle.
13929
- * Called from WS disconnect handler when pending update exists.
13930
- */
13931
13883
  applyPendingUpdateIfIdle() {
13932
- if (this.pendingUpdateVersion && this.getActiveAgentSessionCount() === 0) {
13933
- const pending = this.pendingUpdateVersion;
13934
- this.pendingUpdateVersion = null;
13935
- void this.applyUpdateIfIdle(pending, "idle");
13936
- }
13937
13884
  }
13938
- /**
13939
- * EP783: Check for CLI updates on startup (non-blocking).
13940
- * Fails silently on errors.
13941
- */
13942
13885
  async checkOnStartup() {
13943
- if (process.env.EPISODA_CLI_PIN_VERSION) {
13944
- return;
13945
- }
13946
- try {
13947
- const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
13948
- const result = await checkForUpdates(effectiveCurrentVersion);
13949
- if (result.updateAvailable) {
13950
- await this.applyUpdateIfIdle(result.latestVersion, "startup");
13951
- }
13952
- } catch (error) {
13953
- }
13954
- }
13955
- /**
13956
- * EP1319: Determine if any agent sessions are actively running.
13957
- * More accurate than liveConnections.size, which only reflects WebSocket connectivity.
13958
- */
13959
- getActiveAgentSessionCount() {
13960
- try {
13961
- const agentManager = getAgentControlPlane();
13962
- const sessions2 = agentManager.getAllSessions();
13963
- return sessions2.filter((session) => ["starting", "running", "stopping"].includes(session.status)).length;
13964
- } catch {
13965
- return 0;
13966
- }
13967
- }
13968
- async applyUpdateIfIdle(targetVersion, source) {
13969
- if (this.updateInProgress) return;
13970
- const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
13971
- if (!this.isTargetVersionNewer(targetVersion, effectiveCurrentVersion)) {
13972
- console.log(`[Daemon] EP1390: Skipping update target=${targetVersion}, current=${effectiveCurrentVersion} (source=${source})`);
13973
- return;
13974
- }
13975
- if (this.lastFailedUpdateVersion === targetVersion && this.updateFailedAttempts >= _UpdateManager.MAX_UPDATE_ATTEMPTS) {
13976
- console.log(`[Daemon] EP1324: Skipping update to ${targetVersion} after ${this.updateFailedAttempts} failed attempts`);
13977
- return;
13978
- }
13979
- const activeAgentSessions = this.getActiveAgentSessionCount();
13980
- if (activeAgentSessions > 0) {
13981
- this.pendingUpdateVersion = targetVersion;
13982
- console.log(`[Daemon] EP1319: Update available (${effectiveCurrentVersion} \u2192 ${targetVersion}) but ${activeAgentSessions} active agent session(s) \u2014 deferring`);
13983
- return;
13984
- }
13985
- const latestEffectiveCurrent = this.getEffectiveCurrentVersion();
13986
- if (!this.isTargetVersionNewer(targetVersion, latestEffectiveCurrent)) {
13987
- console.log(`[Daemon] EP1390: Update no longer needed target=${targetVersion}, current=${latestEffectiveCurrent}`);
13988
- return;
13989
- }
13990
- this.updateInProgress = true;
13991
- console.log(`[Daemon] EP1319: Applying CLI update (${source}) to ${targetVersion} (idle)`);
13992
- try {
13993
- const installResult = performSyncUpdate(targetVersion);
13994
- if (!installResult.success) {
13995
- this.recordUpdateFailure(targetVersion, `Install failed: ${installResult.error}`);
13996
- return;
13997
- }
13998
- const installedVersion = getInstalledVersion();
13999
- if (installedVersion !== targetVersion) {
14000
- this.recordUpdateFailure(targetVersion, `Version mismatch: expected ${targetVersion}, got ${installedVersion || "unknown"}`);
14001
- return;
14002
- }
14003
- console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
14004
- await this.host.shutdown();
14005
- const configDir = (0, import_core17.getConfigDir)();
14006
- const logPath = path32.join(configDir, "daemon.log");
14007
- const logFd = fs32.openSync(logPath, "a");
14008
- const child = (0, import_child_process17.spawn)("node", [this.daemonEntryFile], {
14009
- detached: true,
14010
- stdio: ["ignore", logFd, logFd],
14011
- cwd: configDir,
14012
- // Keep daemon process context stable across restarts.
14013
- env: { ...process.env, EPISODA_DAEMON_MODE: "1" }
14014
- });
14015
- if (!child.pid) {
14016
- try {
14017
- fs32.closeSync(logFd);
14018
- } catch {
14019
- }
14020
- this.recordUpdateFailure(targetVersion, "Failed to spawn replacement daemon (missing pid)");
14021
- return;
14022
- }
14023
- child.unref();
14024
- const pidPath = getPidFilePath();
14025
- fs32.writeFileSync(pidPath, child.pid.toString(), "utf-8");
14026
- console.log(`[Daemon] EP1324: New daemon spawned (PID: ${child.pid}), exiting old process`);
14027
- process.exit(0);
14028
- } catch (error) {
14029
- this.recordUpdateFailure(targetVersion, error instanceof Error ? error.message : String(error));
14030
- } finally {
14031
- this.updateInProgress = false;
14032
- }
14033
- }
14034
- /**
14035
- * EP1324: Record a failed update attempt and defer for retry.
14036
- * After MAX_UPDATE_ATTEMPTS for the same version, further attempts are blocked.
14037
- */
14038
- recordUpdateFailure(version, reason) {
14039
- if (this.lastFailedUpdateVersion === version) {
14040
- this.updateFailedAttempts++;
14041
- } else {
14042
- this.lastFailedUpdateVersion = version;
14043
- this.updateFailedAttempts = 1;
14044
- }
14045
- this.pendingUpdateVersion = version;
14046
- console.warn(`[Daemon] EP1324: Update to ${version} failed (attempt ${this.updateFailedAttempts}/${_UpdateManager.MAX_UPDATE_ATTEMPTS}): ${reason}`);
14047
- }
14048
- /**
14049
- * EP1319: Periodic update check for long-lived containers
14050
- * Checks npm registry for newer version. If found:
14051
- * - No active connections → install immediately via background update
14052
- * - Active connections → defer update, store version, retry next interval
14053
- */
14054
- async periodicUpdateCheck() {
14055
- if (process.env.EPISODA_CLI_PIN_VERSION) {
14056
- return;
14057
- }
14058
- try {
14059
- const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
14060
- const result = await checkForUpdates(effectiveCurrentVersion);
14061
- if (!result.updateAvailable) {
14062
- if (this.pendingUpdateVersion) {
14063
- console.log(`[Daemon] EP1319: Pending update to ${this.pendingUpdateVersion} is no longer needed (already current)`);
14064
- this.pendingUpdateVersion = null;
14065
- }
14066
- return;
14067
- }
14068
- const targetVersion = result.latestVersion;
14069
- await this.applyUpdateIfIdle(targetVersion, "periodic");
14070
- } catch (error) {
14071
- }
14072
13886
  }
14073
13887
  };
14074
13888
 
@@ -14679,7 +14493,7 @@ var HealthOrchestrator = class _HealthOrchestrator {
14679
14493
  };
14680
14494
 
14681
14495
  // src/daemon/daemon-core.ts
14682
- var import_child_process18 = require("child_process");
14496
+ var import_child_process17 = require("child_process");
14683
14497
  var DaemonCore = class {
14684
14498
  constructor(host) {
14685
14499
  this.host = host;
@@ -14688,7 +14502,7 @@ var DaemonCore = class {
14688
14502
  const ppid = process.ppid;
14689
14503
  if (!ppid || ppid <= 0) return void 0;
14690
14504
  try {
14691
- return (0, import_child_process18.execSync)(`ps -p ${ppid} -o command=`, {
14505
+ return (0, import_child_process17.execSync)(`ps -p ${ppid} -o command=`, {
14692
14506
  encoding: "utf-8",
14693
14507
  stdio: ["pipe", "pipe", "pipe"],
14694
14508
  timeout: 3e3
@@ -14844,7 +14658,7 @@ var ConnectionManager = class {
14844
14658
  * handlers are removed deterministically on every exit path.
14845
14659
  */
14846
14660
  async waitForAuthentication(client, timeoutMs = 3e4) {
14847
- await new Promise((resolve8, reject) => {
14661
+ await new Promise((resolve9, reject) => {
14848
14662
  let settled = false;
14849
14663
  const cleanup = () => {
14850
14664
  clearTimeout(timeout);
@@ -14863,7 +14677,7 @@ var ConnectionManager = class {
14863
14677
  if (settled) return;
14864
14678
  settled = true;
14865
14679
  cleanup();
14866
- resolve8();
14680
+ resolve9();
14867
14681
  };
14868
14682
  const authErrorHandler = (message) => {
14869
14683
  if (settled) return;
@@ -15895,8 +15709,8 @@ var Daemon = class _Daemon {
15895
15709
  }
15896
15710
  }
15897
15711
  let releaseLock;
15898
- const lockPromise = new Promise((resolve8) => {
15899
- releaseLock = resolve8;
15712
+ const lockPromise = new Promise((resolve9) => {
15713
+ releaseLock = resolve9;
15900
15714
  });
15901
15715
  this.tunnelOperationLocks.set(moduleUid, lockPromise);
15902
15716
  try {
@@ -15942,7 +15756,7 @@ var Daemon = class _Daemon {
15942
15756
  const maxWait = 35e3;
15943
15757
  const startTime = Date.now();
15944
15758
  while (this.connectionManager.hasPendingConnection(projectPath) && Date.now() - startTime < maxWait) {
15945
- await new Promise((resolve8) => setTimeout(resolve8, 500));
15759
+ await new Promise((resolve9) => setTimeout(resolve9, 500));
15946
15760
  }
15947
15761
  if (this.connectionManager.hasLiveConnection(projectPath)) {
15948
15762
  console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
@@ -17068,9 +16882,9 @@ var Daemon = class _Daemon {
17068
16882
  console.log(`[Daemon] EP1002: Worktree setup complete for ${moduleUid}`);
17069
16883
  }
17070
16884
  async runForegroundCommand(command, args, cwd, env, timeoutMs) {
17071
- await new Promise((resolve8, reject) => {
16885
+ await new Promise((resolve9, reject) => {
17072
16886
  const commandLabel = `${command} ${args.join(" ")}`.trim();
17073
- const child = (0, import_child_process19.spawn)(command, args, {
16887
+ const child = (0, import_child_process18.spawn)(command, args, {
17074
16888
  cwd,
17075
16889
  env,
17076
16890
  stdio: "inherit",
@@ -17091,7 +16905,7 @@ var Daemon = class _Daemon {
17091
16905
  reject(error);
17092
16906
  return;
17093
16907
  }
17094
- resolve8();
16908
+ resolve9();
17095
16909
  };
17096
16910
  termTimer = setTimeout(() => {
17097
16911
  timedOut = true;