@episoda/cli 0.2.163 → 0.2.165

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.
@@ -343,6 +343,8 @@ var require_git_executor = __commonJS({
343
343
  case "delete_branch":
344
344
  return await this.executeDeleteBranch(command, cwd, options);
345
345
  // EP597: Read operations for production local dev mode
346
+ case "list_branches":
347
+ return await this.executeListBranches(cwd, options);
346
348
  case "branch_exists":
347
349
  return await this.executeBranchExists(command, cwd, options);
348
350
  case "branch_has_commits":
@@ -632,6 +634,65 @@ var require_git_executor = __commonJS({
632
634
  args.push(command.branch);
633
635
  return await this.runGitCommand(args, cwd, options);
634
636
  }
637
+ /**
638
+ * List local branches with remote-tracking signal and current branch marker.
639
+ */
640
+ async executeListBranches(cwd, options) {
641
+ try {
642
+ const timeout = options?.timeout || 1e4;
643
+ const branchMap = /* @__PURE__ */ new Map();
644
+ const addBranch = (name, attrs) => {
645
+ const normalized = name.trim();
646
+ if (!normalized)
647
+ return;
648
+ const existing = branchMap.get(normalized);
649
+ if (existing) {
650
+ existing.current = existing.current || !!attrs?.current;
651
+ existing.remote = existing.remote || !!attrs?.remote;
652
+ return;
653
+ }
654
+ branchMap.set(normalized, {
655
+ name: normalized,
656
+ current: !!attrs?.current,
657
+ remote: !!attrs?.remote
658
+ });
659
+ };
660
+ const statusResult = await this.executeStatus(cwd, options);
661
+ const currentBranch = statusResult.success ? statusResult.details?.branchName : void 0;
662
+ try {
663
+ const { stdout } = await execAsync3(`git for-each-ref --format='%(refname:short)' refs/heads`, { cwd, timeout });
664
+ stdout.split("\n").map((line) => line.trim()).filter(Boolean).forEach((name) => addBranch(name, { current: name === currentBranch }));
665
+ } catch {
666
+ }
667
+ try {
668
+ const { stdout } = await execAsync3(`git for-each-ref --format='%(refname:short)' refs/remotes/origin`, { cwd, timeout });
669
+ stdout.split("\n").map((line) => line.trim()).filter(Boolean).forEach((refName) => {
670
+ if (refName === "origin/HEAD")
671
+ return;
672
+ const normalized = refName.startsWith("origin/") ? refName.slice("origin/".length) : refName;
673
+ addBranch(normalized, { remote: true, current: normalized === currentBranch });
674
+ });
675
+ } catch {
676
+ }
677
+ if (currentBranch && currentBranch !== "HEAD") {
678
+ addBranch(currentBranch, { current: true });
679
+ }
680
+ const branches = Array.from(branchMap.values()).sort((a, b) => a.name.localeCompare(b.name));
681
+ return {
682
+ success: true,
683
+ details: {
684
+ branchName: currentBranch,
685
+ branches
686
+ }
687
+ };
688
+ } catch (error) {
689
+ return {
690
+ success: false,
691
+ error: "UNKNOWN_ERROR",
692
+ output: error.message || "Failed to list branches"
693
+ };
694
+ }
695
+ }
635
696
  /**
636
697
  * EP597: Execute branch_exists command
637
698
  * Checks if a branch exists locally and/or remotely
@@ -1579,15 +1640,15 @@ var require_git_executor = __commonJS({
1579
1640
  try {
1580
1641
  const { stdout: gitDir } = await execAsync3("git rev-parse --git-dir", { cwd, timeout: 5e3 });
1581
1642
  const gitDirPath = gitDir.trim();
1582
- const fs29 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1643
+ const fs30 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1583
1644
  const rebaseMergePath = `${gitDirPath}/rebase-merge`;
1584
1645
  const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
1585
1646
  try {
1586
- await fs29.access(rebaseMergePath);
1647
+ await fs30.access(rebaseMergePath);
1587
1648
  inRebase = true;
1588
1649
  } catch {
1589
1650
  try {
1590
- await fs29.access(rebaseApplyPath);
1651
+ await fs30.access(rebaseApplyPath);
1591
1652
  inRebase = true;
1592
1653
  } catch {
1593
1654
  inRebase = false;
@@ -1643,9 +1704,9 @@ var require_git_executor = __commonJS({
1643
1704
  };
1644
1705
  }
1645
1706
  }
1646
- const fs29 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1707
+ const fs30 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1647
1708
  try {
1648
- await fs29.access(command.path);
1709
+ await fs30.access(command.path);
1649
1710
  return {
1650
1711
  success: false,
1651
1712
  error: "WORKTREE_EXISTS",
@@ -1704,9 +1765,9 @@ var require_git_executor = __commonJS({
1704
1765
  */
1705
1766
  async executeWorktreeRemove(command, cwd, options) {
1706
1767
  try {
1707
- const fs29 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1768
+ const fs30 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1708
1769
  try {
1709
- await fs29.access(command.path);
1770
+ await fs30.access(command.path);
1710
1771
  } catch {
1711
1772
  return {
1712
1773
  success: false,
@@ -1741,7 +1802,7 @@ var require_git_executor = __commonJS({
1741
1802
  const result = await this.runGitCommand(args, cwd, options);
1742
1803
  if (result.success) {
1743
1804
  try {
1744
- await fs29.rm(command.path, { recursive: true, force: true });
1805
+ await fs30.rm(command.path, { recursive: true, force: true });
1745
1806
  } catch {
1746
1807
  }
1747
1808
  return {
@@ -1875,10 +1936,10 @@ var require_git_executor = __commonJS({
1875
1936
  */
1876
1937
  async executeCloneBare(command, options) {
1877
1938
  try {
1878
- const fs29 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1879
- const path30 = await Promise.resolve().then(() => __importStar(require("path")));
1939
+ const fs30 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1940
+ const path31 = await Promise.resolve().then(() => __importStar(require("path")));
1880
1941
  try {
1881
- await fs29.access(command.path);
1942
+ await fs30.access(command.path);
1882
1943
  return {
1883
1944
  success: false,
1884
1945
  error: "BRANCH_ALREADY_EXISTS",
@@ -1887,9 +1948,9 @@ var require_git_executor = __commonJS({
1887
1948
  };
1888
1949
  } catch {
1889
1950
  }
1890
- const parentDir = path30.dirname(command.path);
1951
+ const parentDir = path31.dirname(command.path);
1891
1952
  try {
1892
- await fs29.mkdir(parentDir, { recursive: true });
1953
+ await fs30.mkdir(parentDir, { recursive: true });
1893
1954
  } catch {
1894
1955
  }
1895
1956
  const { stdout, stderr } = await execAsync3(
@@ -1937,22 +1998,22 @@ var require_git_executor = __commonJS({
1937
1998
  */
1938
1999
  async executeProjectInfo(cwd, options) {
1939
2000
  try {
1940
- const fs29 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
1941
- const path30 = await Promise.resolve().then(() => __importStar(require("path")));
2001
+ const fs30 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
2002
+ const path31 = await Promise.resolve().then(() => __importStar(require("path")));
1942
2003
  let currentPath = cwd;
1943
2004
  let projectPath = cwd;
1944
2005
  let bareRepoPath;
1945
2006
  for (let i = 0; i < 10; i++) {
1946
- const bareDir = path30.join(currentPath, ".bare");
1947
- const episodaDir = path30.join(currentPath, ".episoda");
2007
+ const bareDir = path31.join(currentPath, ".bare");
2008
+ const episodaDir = path31.join(currentPath, ".episoda");
1948
2009
  try {
1949
- await fs29.access(bareDir);
1950
- await fs29.access(episodaDir);
2010
+ await fs30.access(bareDir);
2011
+ await fs30.access(episodaDir);
1951
2012
  projectPath = currentPath;
1952
2013
  bareRepoPath = bareDir;
1953
2014
  break;
1954
2015
  } catch {
1955
- const parentPath = path30.dirname(currentPath);
2016
+ const parentPath = path31.dirname(currentPath);
1956
2017
  if (parentPath === currentPath) {
1957
2018
  break;
1958
2019
  }
@@ -2153,6 +2214,8 @@ var require_websocket_client = __commonJS({
2153
2214
  this.osArch = deviceInfo?.osArch;
2154
2215
  this.daemonPid = deviceInfo?.daemonPid;
2155
2216
  this.cliVersion = deviceInfo?.cliVersion;
2217
+ this.cliPackageName = deviceInfo?.cliPackageName;
2218
+ this.capabilities = deviceInfo?.capabilities;
2156
2219
  this.environment = deviceInfo?.environment;
2157
2220
  this.containerId = deviceInfo?.containerId;
2158
2221
  this.isDisconnecting = false;
@@ -2192,6 +2255,8 @@ var require_websocket_client = __commonJS({
2192
2255
  type: "auth",
2193
2256
  token,
2194
2257
  version: this.cliVersion || version_1.VERSION,
2258
+ cliPackageName: this.cliPackageName,
2259
+ capabilities: this.capabilities,
2195
2260
  environment: this.environment,
2196
2261
  machineId,
2197
2262
  containerId: this.containerId,
@@ -2511,6 +2576,8 @@ var require_websocket_client = __commonJS({
2511
2576
  osArch: this.osArch,
2512
2577
  daemonPid: this.daemonPid,
2513
2578
  cliVersion: this.cliVersion,
2579
+ cliPackageName: this.cliPackageName,
2580
+ capabilities: this.capabilities,
2514
2581
  environment: this.environment,
2515
2582
  containerId: this.containerId
2516
2583
  }).then(() => {
@@ -2607,33 +2674,33 @@ var require_auth = __commonJS({
2607
2674
  exports2.loadConfig = loadConfig16;
2608
2675
  exports2.saveConfig = saveConfig4;
2609
2676
  exports2.validateToken = validateToken;
2610
- var fs29 = __importStar(require("fs"));
2611
- var path30 = __importStar(require("path"));
2677
+ var fs30 = __importStar(require("fs"));
2678
+ var path31 = __importStar(require("path"));
2612
2679
  var os13 = __importStar(require("os"));
2613
2680
  var child_process_1 = require("child_process");
2614
2681
  var DEFAULT_CONFIG_FILE = "config.json";
2615
2682
  var hasWarnedMissingProjectId = false;
2616
2683
  var hasWarnedMissingRequiredFields = false;
2617
2684
  function getConfigDir10() {
2618
- return process.env.EPISODA_CONFIG_DIR || path30.join(os13.homedir(), ".episoda");
2685
+ return process.env.EPISODA_CONFIG_DIR || path31.join(os13.homedir(), ".episoda");
2619
2686
  }
2620
2687
  function getConfigPath(configPath) {
2621
2688
  if (configPath) {
2622
2689
  return configPath;
2623
2690
  }
2624
- return path30.join(getConfigDir10(), DEFAULT_CONFIG_FILE);
2691
+ return path31.join(getConfigDir10(), DEFAULT_CONFIG_FILE);
2625
2692
  }
2626
2693
  function ensureConfigDir(configPath) {
2627
- const dir = path30.dirname(configPath);
2628
- const isNew = !fs29.existsSync(dir);
2694
+ const dir = path31.dirname(configPath);
2695
+ const isNew = !fs30.existsSync(dir);
2629
2696
  if (isNew) {
2630
- fs29.mkdirSync(dir, { recursive: true, mode: 448 });
2697
+ fs30.mkdirSync(dir, { recursive: true, mode: 448 });
2631
2698
  }
2632
2699
  if (process.platform === "darwin") {
2633
- const nosyncPath = path30.join(dir, ".nosync");
2634
- if (isNew || !fs29.existsSync(nosyncPath)) {
2700
+ const nosyncPath = path31.join(dir, ".nosync");
2701
+ if (isNew || !fs30.existsSync(nosyncPath)) {
2635
2702
  try {
2636
- fs29.writeFileSync(nosyncPath, "", { mode: 384 });
2703
+ fs30.writeFileSync(nosyncPath, "", { mode: 384 });
2637
2704
  (0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
2638
2705
  stdio: "ignore",
2639
2706
  timeout: 5e3
@@ -2647,11 +2714,11 @@ var require_auth = __commonJS({
2647
2714
  const fullPath = getConfigPath(configPath);
2648
2715
  const isCloudMode = process.env.EPISODA_MODE === "cloud";
2649
2716
  const readConfigFile = (pathToFile) => {
2650
- if (!fs29.existsSync(pathToFile)) {
2717
+ if (!fs30.existsSync(pathToFile)) {
2651
2718
  return null;
2652
2719
  }
2653
2720
  try {
2654
- const content = fs29.readFileSync(pathToFile, "utf8");
2721
+ const content = fs30.readFileSync(pathToFile, "utf8");
2655
2722
  return JSON.parse(content);
2656
2723
  } catch (error) {
2657
2724
  console.error("Error loading config:", error);
@@ -2690,11 +2757,11 @@ var require_auth = __commonJS({
2690
2757
  }
2691
2758
  const homeDir = process.env.HOME || require("os").homedir();
2692
2759
  const workspaceConfigPath = require("path").join(homeDir, "episoda", process.env.EPISODA_WORKSPACE, ".episoda", "config.json");
2693
- if (!fs29.existsSync(workspaceConfigPath)) {
2760
+ if (!fs30.existsSync(workspaceConfigPath)) {
2694
2761
  return null;
2695
2762
  }
2696
2763
  try {
2697
- const content = fs29.readFileSync(workspaceConfigPath, "utf8");
2764
+ const content = fs30.readFileSync(workspaceConfigPath, "utf8");
2698
2765
  const workspaceConfig2 = JSON.parse(content);
2699
2766
  const expiresAtEnv = envValue(process.env.EPISODA_ACCESS_TOKEN_EXPIRES_AT);
2700
2767
  return {
@@ -2722,11 +2789,11 @@ var require_auth = __commonJS({
2722
2789
  }
2723
2790
  const homeDir = process.env.HOME || require("os").homedir();
2724
2791
  const projectConfigPath = require("path").join(homeDir, "episoda", workspaceSlug, projectSlug, ".episoda", "config.json");
2725
- if (!fs29.existsSync(projectConfigPath)) {
2792
+ if (!fs30.existsSync(projectConfigPath)) {
2726
2793
  return null;
2727
2794
  }
2728
2795
  try {
2729
- const content = fs29.readFileSync(projectConfigPath, "utf8");
2796
+ const content = fs30.readFileSync(projectConfigPath, "utf8");
2730
2797
  const projectConfig2 = JSON.parse(content);
2731
2798
  return {
2732
2799
  project_id: projectConfig2.projectId || projectConfig2.project_id,
@@ -2794,7 +2861,7 @@ var require_auth = __commonJS({
2794
2861
  ensureConfigDir(fullPath);
2795
2862
  try {
2796
2863
  const content = JSON.stringify(config, null, 2);
2797
- fs29.writeFileSync(fullPath, content, { mode: 384 });
2864
+ fs30.writeFileSync(fullPath, content, { mode: 384 });
2798
2865
  } catch (error) {
2799
2866
  throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
2800
2867
  }
@@ -2911,7 +2978,7 @@ var require_package = __commonJS({
2911
2978
  "package.json"(exports2, module2) {
2912
2979
  module2.exports = {
2913
2980
  name: "@episoda/cli",
2914
- version: "0.2.163",
2981
+ version: "0.2.165",
2915
2982
  description: "CLI tool for Episoda local development workflow orchestration",
2916
2983
  main: "dist/index.js",
2917
2984
  types: "dist/index.d.ts",
@@ -4912,7 +4979,9 @@ var import_core9 = __toESM(require_dist());
4912
4979
 
4913
4980
  // src/agent/claude-persistent-runtime.ts
4914
4981
  var import_child_process7 = require("child_process");
4915
- var ECHO_TIMEOUT_MS = parseInt(process.env.AGENT_ECHO_TIMEOUT_MS || "10000", 10);
4982
+ var DEFAULT_STARTUP_GRACE_MS = 3e4;
4983
+ var HEARTBEAT_ACK_MARKER = "__EPISODA_HEARTBEAT_ACK__";
4984
+ var ECHO_TIMEOUT_MS = parseInt(process.env.AGENT_ECHO_TIMEOUT_MS || `${DEFAULT_STARTUP_GRACE_MS}`, 10);
4916
4985
  var INACTIVITY_TIMEOUT_MS = parseInt(process.env.AGENT_STREAM_INACTIVITY_TIMEOUT_MS || "180000", 10);
4917
4986
  var SHUTDOWN_SIGTERM_WAIT_MS = 2e3;
4918
4987
  var SHUTDOWN_SIGKILL_WAIT_MS = 2e3;
@@ -4948,7 +5017,6 @@ var ClaudePersistentRuntime = class {
4948
5017
  this.echoTimer = null;
4949
5018
  this.inactivityTimer = null;
4950
5019
  this.turnStartTime = 0;
4951
- this.sawAnyStdoutThisTurn = false;
4952
5020
  // EP1360: Instrumentation
4953
5021
  this.spawnTimestamp = 0;
4954
5022
  this.sessionId = options.sessionId;
@@ -4988,6 +5056,11 @@ var ClaudePersistentRuntime = class {
4988
5056
  this.alive = true;
4989
5057
  this.process.stderr?.on("data", (data) => {
4990
5058
  const chunk = data.toString();
5059
+ this.resetInactivityTimer();
5060
+ if (this._turnState === "waiting_for_echo") {
5061
+ const source = chunk.includes(HEARTBEAT_ACK_MARKER) ? "stderr-marker" : "stderr";
5062
+ this.recordStartupHeartbeatAck(source);
5063
+ }
4991
5064
  this.stderrTail = (this.stderrTail + chunk).slice(-this.maxStderrTailBytes);
4992
5065
  if (RUNTIME_DEBUG) {
4993
5066
  console.error(`[ClaudePersistentRuntime] stderr (session=${this.sessionId}): ${chunk.trimEnd()}`);
@@ -4995,6 +5068,7 @@ var ClaudePersistentRuntime = class {
4995
5068
  });
4996
5069
  this.process.stdout?.on("data", (data) => {
4997
5070
  this.resetInactivityTimer();
5071
+ this.recordStartupHeartbeatAck("stdout");
4998
5072
  this.stdoutBuffer += data.toString();
4999
5073
  const lines = this.stdoutBuffer.split("\n");
5000
5074
  this.stdoutBuffer = lines.pop() || "";
@@ -5045,7 +5119,6 @@ var ClaudePersistentRuntime = class {
5045
5119
  this.turnStartTime = Date.now();
5046
5120
  this.turnTtftLogged = false;
5047
5121
  this.seenToolUseIds.clear();
5048
- this.sawAnyStdoutThisTurn = false;
5049
5122
  this._turnState = "waiting_for_echo";
5050
5123
  const payload = JSON.stringify({ type: "user", message: { role: "user", content: message } }) + "\n";
5051
5124
  try {
@@ -5058,9 +5131,9 @@ var ClaudePersistentRuntime = class {
5058
5131
  }
5059
5132
  this.echoTimer = setTimeout(() => {
5060
5133
  if (this._turnState === "waiting_for_echo") {
5061
- console.warn(`[ClaudePersistentRuntime] Echo timeout after ${ECHO_TIMEOUT_MS}ms \u2014 auto-degrading. session=${this.sessionId}`);
5134
+ console.warn(`[ClaudePersistentRuntime] Startup heartbeat timeout after ${ECHO_TIMEOUT_MS}ms \u2014 auto-degrading. session=${this.sessionId}`);
5062
5135
  if (this.callbacks) {
5063
- this.callbacks.onError(`ECHO_TIMEOUT: No stdout event within ${ECHO_TIMEOUT_MS}ms after send`);
5136
+ this.callbacks.onError(`ECHO_TIMEOUT: No startup heartbeat within ${ECHO_TIMEOUT_MS}ms after send`);
5064
5137
  this.endTurn();
5065
5138
  }
5066
5139
  }
@@ -5143,14 +5216,7 @@ var ClaudePersistentRuntime = class {
5143
5216
  }
5144
5217
  handleParsedEvent(parsed) {
5145
5218
  const type = parsed.type;
5146
- if (this._turnState === "waiting_for_echo") {
5147
- this.sawAnyStdoutThisTurn = true;
5148
- this.clearEchoTimer();
5149
- this._turnState = "streaming";
5150
- if (RUNTIME_DEBUG) {
5151
- console.log(`[ClaudePersistentRuntime] First stdout for turn \u2014 streaming started. session=${this.sessionId}`);
5152
- }
5153
- }
5219
+ this.recordStartupHeartbeatAck("stdout");
5154
5220
  if (type === "user") {
5155
5221
  return;
5156
5222
  }
@@ -5336,6 +5402,14 @@ var ClaudePersistentRuntime = class {
5336
5402
  this.endTurn();
5337
5403
  cb.onComplete(this._agentSessionId, resultMeta);
5338
5404
  }
5405
+ recordStartupHeartbeatAck(source) {
5406
+ if (this._turnState !== "waiting_for_echo") return;
5407
+ this.clearEchoTimer();
5408
+ this._turnState = "streaming";
5409
+ if (RUNTIME_DEBUG) {
5410
+ console.log(`[ClaudePersistentRuntime] Startup heartbeat ack via ${source}; streaming started. session=${this.sessionId}`);
5411
+ }
5412
+ }
5339
5413
  endTurn() {
5340
5414
  this.clearTimers();
5341
5415
  this.callbacks = null;
@@ -7527,7 +7601,7 @@ ${message}`;
7527
7601
  const args2 = parts.slice(1);
7528
7602
  let env;
7529
7603
  if (server.name === "github") {
7530
- env = { ...baseEnv };
7604
+ env = void 0;
7531
7605
  } else {
7532
7606
  env = {
7533
7607
  ...baseEnv,
@@ -8620,8 +8694,8 @@ var WorktreeManager = class _WorktreeManager {
8620
8694
  const allWorktrees = this.listWorktrees();
8621
8695
  const activeSet = new Set(activeModuleUids);
8622
8696
  const orphaned = allWorktrees.filter((w) => !activeSet.has(w.moduleUid));
8623
- const valid = allWorktrees.filter((w) => activeSet.has(w.moduleUid));
8624
- return { orphaned, valid };
8697
+ const valid3 = allWorktrees.filter((w) => activeSet.has(w.moduleUid));
8698
+ return { orphaned, valid: valid3 };
8625
8699
  }
8626
8700
  /**
8627
8701
  * Update last accessed timestamp for a worktree
@@ -8656,7 +8730,7 @@ var WorktreeManager = class _WorktreeManager {
8656
8730
  */
8657
8731
  async validateWorktrees() {
8658
8732
  const config = this.readConfig();
8659
- const valid = [];
8733
+ const valid3 = [];
8660
8734
  const stale = [];
8661
8735
  const orphaned = [];
8662
8736
  const listResult = await this.gitExecutor.execute({
@@ -8667,7 +8741,7 @@ var WorktreeManager = class _WorktreeManager {
8667
8741
  );
8668
8742
  for (const worktree of config?.worktrees || []) {
8669
8743
  if (actualWorktrees.has(worktree.worktreePath)) {
8670
- valid.push(worktree);
8744
+ valid3.push(worktree);
8671
8745
  actualWorktrees.delete(worktree.worktreePath);
8672
8746
  } else {
8673
8747
  stale.push(worktree);
@@ -8678,7 +8752,7 @@ var WorktreeManager = class _WorktreeManager {
8678
8752
  orphaned.push(wpath);
8679
8753
  }
8680
8754
  }
8681
- return { valid, stale, orphaned };
8755
+ return { valid: valid3, stale, orphaned };
8682
8756
  }
8683
8757
  /**
8684
8758
  * EP1190: Clean up non-module worktrees (like 'main')
@@ -9405,7 +9479,7 @@ function getInstallCommand(cwd) {
9405
9479
  }
9406
9480
 
9407
9481
  // src/daemon/daemon-process.ts
9408
- var fs28 = __toESM(require("fs"));
9482
+ var fs29 = __toESM(require("fs"));
9409
9483
  var http2 = __toESM(require("http"));
9410
9484
  var os12 = __toESM(require("os"));
9411
9485
 
@@ -10213,8 +10287,8 @@ async function handleExec(command, projectPath) {
10213
10287
  }
10214
10288
 
10215
10289
  // src/daemon/handlers/worktree-handlers.ts
10216
- var path23 = __toESM(require("path"));
10217
- var fs22 = __toESM(require("fs"));
10290
+ var path24 = __toESM(require("path"));
10291
+ var fs23 = __toESM(require("fs"));
10218
10292
  var os10 = __toESM(require("os"));
10219
10293
  var import_child_process15 = require("child_process");
10220
10294
  var import_util2 = require("util");
@@ -11551,6 +11625,18 @@ async function deleteWorktree(config, moduleUid) {
11551
11625
  // src/daemon/package-manager.ts
11552
11626
  var fs21 = __toESM(require("fs"));
11553
11627
  var path22 = __toESM(require("path"));
11628
+ function pnpmCommand(args) {
11629
+ return [
11630
+ "if command -v pnpm >/dev/null 2>&1; then",
11631
+ ` pnpm ${args}`,
11632
+ "elif command -v corepack >/dev/null 2>&1; then",
11633
+ ` corepack pnpm ${args}`,
11634
+ "else",
11635
+ ' echo "[setup] ERROR: pnpm is not installed and corepack is unavailable" >&2',
11636
+ " exit 127",
11637
+ "fi"
11638
+ ].join(" ");
11639
+ }
11554
11640
  var PACKAGE_MANAGERS = {
11555
11641
  javascript: [
11556
11642
  {
@@ -11572,7 +11658,7 @@ var PACKAGE_MANAGERS = {
11572
11658
  return null;
11573
11659
  },
11574
11660
  config: {
11575
- installCmd: "pnpm install --frozen-lockfile",
11661
+ installCmd: pnpmCommand("install --frozen-lockfile"),
11576
11662
  cacheEnvVar: "PNPM_HOME"
11577
11663
  }
11578
11664
  },
@@ -11604,7 +11690,7 @@ var PACKAGE_MANAGERS = {
11604
11690
  name: "pnpm",
11605
11691
  detector: (p) => fs21.existsSync(path22.join(p, "package.json")) ? "package.json (no lockfile - defaulting to pnpm)" : null,
11606
11692
  config: {
11607
- installCmd: "pnpm install",
11693
+ installCmd: pnpmCommand("install"),
11608
11694
  cacheEnvVar: "PNPM_HOME"
11609
11695
  }
11610
11696
  }
@@ -11686,6 +11772,42 @@ function detectPackageManager(worktreePath, preferredLanguages) {
11686
11772
  };
11687
11773
  }
11688
11774
 
11775
+ // src/daemon/build-packages.ts
11776
+ var fs22 = __toESM(require("fs"));
11777
+ var path23 = __toESM(require("path"));
11778
+ function hasPackageScript(worktreePath, scriptName) {
11779
+ try {
11780
+ const packageJsonPath = path23.join(worktreePath, "package.json");
11781
+ if (!fs22.existsSync(packageJsonPath)) {
11782
+ return false;
11783
+ }
11784
+ const packageJson2 = JSON.parse(fs22.readFileSync(packageJsonPath, "utf-8"));
11785
+ return typeof packageJson2.scripts?.[scriptName] === "string";
11786
+ } catch {
11787
+ return false;
11788
+ }
11789
+ }
11790
+ function getBuildPackagesCommand(packageManagerName, hasBuildPackagesScript) {
11791
+ if (!hasBuildPackagesScript) {
11792
+ return null;
11793
+ }
11794
+ switch (packageManagerName) {
11795
+ case "pnpm":
11796
+ return 'if command -v pnpm >/dev/null 2>&1; then pnpm run build:packages; elif command -v corepack >/dev/null 2>&1; then corepack pnpm run build:packages; else echo "[setup] ERROR: pnpm is not installed and corepack is unavailable" >&2; exit 127; fi';
11797
+ case "yarn":
11798
+ return "yarn build:packages";
11799
+ case "npm":
11800
+ return "npm run build:packages";
11801
+ case "bun":
11802
+ return "bun run build:packages";
11803
+ default:
11804
+ return null;
11805
+ }
11806
+ }
11807
+ function shouldRunBuildPackagesBootstrap(installSucceeded, buildPackagesCommand) {
11808
+ return installSucceeded && !!buildPackagesCommand;
11809
+ }
11810
+
11689
11811
  // src/daemon/handlers/worktree-handlers.ts
11690
11812
  async function getConfigForApi() {
11691
11813
  if (process.env.EPISODA_MODE === "cloud" && process.env.EPISODA_ACCESS_TOKEN) {
@@ -11709,18 +11831,18 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
11709
11831
  return;
11710
11832
  }
11711
11833
  const homeDir = process.env.HOME || os10.homedir();
11712
- const workspaceConfigPath = path23.join(
11834
+ const workspaceConfigPath = path24.join(
11713
11835
  homeDir,
11714
11836
  "episoda",
11715
11837
  workspaceSlug,
11716
11838
  ".episoda",
11717
11839
  "config.json"
11718
11840
  );
11719
- if (!fs22.existsSync(workspaceConfigPath)) {
11841
+ if (!fs23.existsSync(workspaceConfigPath)) {
11720
11842
  return;
11721
11843
  }
11722
11844
  try {
11723
- const content = fs22.readFileSync(workspaceConfigPath, "utf8");
11845
+ const content = fs23.readFileSync(workspaceConfigPath, "utf8");
11724
11846
  const workspaceConfig = JSON.parse(content);
11725
11847
  let changed = false;
11726
11848
  if (projectId && workspaceConfig.projectId !== projectId && workspaceConfig.project_id !== projectId) {
@@ -11732,7 +11854,7 @@ function persistWorkspaceProjectContext(workspaceSlug, projectSlug, projectId) {
11732
11854
  changed = true;
11733
11855
  }
11734
11856
  if (changed) {
11735
- fs22.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
11857
+ fs23.writeFileSync(workspaceConfigPath, JSON.stringify(workspaceConfig, null, 2), "utf8");
11736
11858
  console.log("[Worktree] Updated workspace config with project context");
11737
11859
  }
11738
11860
  } catch (error) {
@@ -11749,32 +11871,6 @@ async function autoDetectSetupScript(worktreePath) {
11749
11871
  console.log(`[Worktree] EP1222: Detected ${name} (${language}) via ${detection.matchedFile}`);
11750
11872
  return installCmd;
11751
11873
  }
11752
- function getBuildPackagesCommand(packageManagerName) {
11753
- switch (packageManagerName) {
11754
- case "pnpm":
11755
- return "pnpm run build:packages";
11756
- case "yarn":
11757
- return "yarn build:packages";
11758
- case "npm":
11759
- return "npm run build:packages";
11760
- case "bun":
11761
- return "bun run build:packages";
11762
- default:
11763
- return null;
11764
- }
11765
- }
11766
- function hasPackageScript(worktreePath, scriptName) {
11767
- try {
11768
- const packageJsonPath = path23.join(worktreePath, "package.json");
11769
- if (!fs22.existsSync(packageJsonPath)) {
11770
- return false;
11771
- }
11772
- const packageJson2 = JSON.parse(fs22.readFileSync(packageJsonPath, "utf-8"));
11773
- return typeof packageJson2.scripts?.[scriptName] === "string";
11774
- } catch {
11775
- return false;
11776
- }
11777
- }
11778
11874
  async function handleWorktreeCreate(request2) {
11779
11875
  const {
11780
11876
  workspaceSlug,
@@ -11805,19 +11901,19 @@ async function handleWorktreeCreate(request2) {
11805
11901
  }
11806
11902
  try {
11807
11903
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
11808
- const bareRepoPath = path23.join(projectPath, ".bare");
11904
+ const bareRepoPath = path24.join(projectPath, ".bare");
11809
11905
  const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
11810
- if (!fs22.existsSync(bareRepoPath)) {
11906
+ if (!fs23.existsSync(bareRepoPath)) {
11811
11907
  console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
11812
11908
  console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
11813
- const episodaDir = path23.join(projectPath, ".episoda");
11814
- fs22.mkdirSync(episodaDir, { recursive: true });
11909
+ const episodaDir = path24.join(projectPath, ".episoda");
11910
+ fs23.mkdirSync(episodaDir, { recursive: true });
11815
11911
  try {
11816
11912
  console.log(`[Worktree] K1273: Starting git clone...`);
11817
11913
  await execAsync2(`git clone --bare "${repoUrl}" "${bareRepoPath}"`, { env: gitEnv });
11818
11914
  console.log(`[Worktree] K1273: Clone successful`);
11819
11915
  await execAsync2(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`, { env: gitEnv });
11820
- const configPath = path23.join(episodaDir, "config.json");
11916
+ const configPath = path24.join(episodaDir, "config.json");
11821
11917
  const config = {
11822
11918
  projectId,
11823
11919
  workspaceSlug,
@@ -11826,7 +11922,7 @@ async function handleWorktreeCreate(request2) {
11826
11922
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
11827
11923
  worktrees: []
11828
11924
  };
11829
- fs22.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
11925
+ fs23.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
11830
11926
  console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
11831
11927
  } catch (cloneError) {
11832
11928
  console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
@@ -11861,16 +11957,16 @@ async function handleWorktreeCreate(request2) {
11861
11957
  let finalError;
11862
11958
  if (envVars && Object.keys(envVars).length > 0) {
11863
11959
  const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
11864
- const envPath = path23.join(worktreePath, ".env");
11865
- fs22.writeFileSync(envPath, envContent + "\n", "utf-8");
11960
+ const envPath = path24.join(worktreePath, ".env");
11961
+ fs23.writeFileSync(envPath, envContent + "\n", "utf-8");
11866
11962
  console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
11867
11963
  }
11868
11964
  const isCloud = process.env.EPISODA_MODE === "cloud";
11869
11965
  let effectiveSetupScript = setupScript || await autoDetectSetupScript(worktreePath);
11870
11966
  const detection = detectPackageManager(worktreePath);
11871
- const buildCmd = getBuildPackagesCommand(detection.packageManager?.name);
11872
11967
  const hasBuildPackages = hasPackageScript(worktreePath, "build:packages");
11873
- if (buildCmd && hasBuildPackages && !effectiveSetupScript?.includes("build:packages")) {
11968
+ const buildCmd = getBuildPackagesCommand(detection.packageManager?.name, hasBuildPackages);
11969
+ if (buildCmd && !effectiveSetupScript?.includes("build:packages")) {
11874
11970
  effectiveSetupScript = effectiveSetupScript ? `${effectiveSetupScript}
11875
11971
  ${buildCmd}` : buildCmd;
11876
11972
  console.log(`[Worktree] EP1386: Added build:packages bootstrap for ${isCloud ? "cloud" : "local"} setup`);
@@ -12003,12 +12099,12 @@ async function handleProjectEject(request2) {
12003
12099
  console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
12004
12100
  try {
12005
12101
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12006
- const bareRepoPath = path23.join(projectPath, ".bare");
12007
- if (!fs22.existsSync(projectPath)) {
12102
+ const bareRepoPath = path24.join(projectPath, ".bare");
12103
+ if (!fs23.existsSync(projectPath)) {
12008
12104
  console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
12009
12105
  return { success: true };
12010
12106
  }
12011
- if (!fs22.existsSync(bareRepoPath)) {
12107
+ if (!fs23.existsSync(bareRepoPath)) {
12012
12108
  console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
12013
12109
  return { success: true };
12014
12110
  }
@@ -12023,12 +12119,12 @@ async function handleProjectEject(request2) {
12023
12119
  };
12024
12120
  }
12025
12121
  }
12026
- const artifactsPath = path23.join(projectPath, "artifacts");
12027
- if (fs22.existsSync(artifactsPath)) {
12122
+ const artifactsPath = path24.join(projectPath, "artifacts");
12123
+ if (fs23.existsSync(artifactsPath)) {
12028
12124
  await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
12029
12125
  }
12030
12126
  console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
12031
- await fs22.promises.rm(projectPath, { recursive: true, force: true });
12127
+ await fs23.promises.rm(projectPath, { recursive: true, force: true });
12032
12128
  console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
12033
12129
  return { success: true };
12034
12130
  } catch (error) {
@@ -12042,7 +12138,7 @@ async function handleProjectEject(request2) {
12042
12138
  async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
12043
12139
  const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
12044
12140
  try {
12045
- const files = await fs22.promises.readdir(artifactsPath);
12141
+ const files = await fs23.promises.readdir(artifactsPath);
12046
12142
  if (files.length === 0) {
12047
12143
  return;
12048
12144
  }
@@ -12056,8 +12152,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12056
12152
  }
12057
12153
  const artifacts = [];
12058
12154
  for (const fileName of files) {
12059
- const filePath = path23.join(artifactsPath, fileName);
12060
- const stat = await fs22.promises.stat(filePath);
12155
+ const filePath = path24.join(artifactsPath, fileName);
12156
+ const stat = await fs23.promises.stat(filePath);
12061
12157
  if (stat.isDirectory()) {
12062
12158
  continue;
12063
12159
  }
@@ -12066,9 +12162,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12066
12162
  continue;
12067
12163
  }
12068
12164
  try {
12069
- const content = await fs22.promises.readFile(filePath);
12165
+ const content = await fs23.promises.readFile(filePath);
12070
12166
  const base64Content = content.toString("base64");
12071
- const ext = path23.extname(fileName).toLowerCase();
12167
+ const ext = path24.extname(fileName).toLowerCase();
12072
12168
  const mimeTypes = {
12073
12169
  ".json": "application/json",
12074
12170
  ".txt": "text/plain",
@@ -12124,8 +12220,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
12124
12220
  }
12125
12221
 
12126
12222
  // src/daemon/handlers/project-handlers.ts
12127
- var path24 = __toESM(require("path"));
12128
- var fs23 = __toESM(require("fs"));
12223
+ var path25 = __toESM(require("path"));
12224
+ var fs24 = __toESM(require("fs"));
12129
12225
  function validateSlug(slug, fieldName) {
12130
12226
  if (!slug || typeof slug !== "string") {
12131
12227
  return `${fieldName} is required`;
@@ -12165,14 +12261,14 @@ async function handleProjectSetup(params) {
12165
12261
  console.log(`[ProjectSetup] EP1199: Setting up project ${workspaceSlug}/${projectSlug}`);
12166
12262
  try {
12167
12263
  const projectPath = getProjectPath(workspaceSlug, projectSlug);
12168
- const artifactsPath = path24.join(projectPath, "artifacts");
12169
- const configDir = path24.join(projectPath, ".episoda");
12170
- const configPath = path24.join(configDir, "config.json");
12171
- await fs23.promises.mkdir(artifactsPath, { recursive: true });
12172
- await fs23.promises.mkdir(configDir, { recursive: true });
12264
+ const artifactsPath = path25.join(projectPath, "artifacts");
12265
+ const configDir = path25.join(projectPath, ".episoda");
12266
+ const configPath = path25.join(configDir, "config.json");
12267
+ await fs24.promises.mkdir(artifactsPath, { recursive: true });
12268
+ await fs24.promises.mkdir(configDir, { recursive: true });
12173
12269
  let existingConfig = {};
12174
12270
  try {
12175
- const existing = await fs23.promises.readFile(configPath, "utf-8");
12271
+ const existing = await fs24.promises.readFile(configPath, "utf-8");
12176
12272
  existingConfig = JSON.parse(existing);
12177
12273
  } catch {
12178
12274
  }
@@ -12185,7 +12281,7 @@ async function handleProjectSetup(params) {
12185
12281
  // Only set created_at if not already present
12186
12282
  created_at: existingConfig.created_at || (/* @__PURE__ */ new Date()).toISOString()
12187
12283
  };
12188
- await fs23.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12284
+ await fs24.promises.writeFile(configPath, JSON.stringify(config, null, 2));
12189
12285
  console.log(`[ProjectSetup] EP1199: Project setup complete at ${projectPath}`);
12190
12286
  return {
12191
12287
  success: true,
@@ -12205,8 +12301,8 @@ async function handleProjectSetup(params) {
12205
12301
  // src/utils/dev-server.ts
12206
12302
  var import_child_process16 = require("child_process");
12207
12303
  var import_core14 = __toESM(require_dist());
12208
- var fs24 = __toESM(require("fs"));
12209
- var path25 = __toESM(require("path"));
12304
+ var fs25 = __toESM(require("fs"));
12305
+ var path26 = __toESM(require("path"));
12210
12306
  var MAX_RESTART_ATTEMPTS = 5;
12211
12307
  var INITIAL_RESTART_DELAY_MS = 2e3;
12212
12308
  var MAX_RESTART_DELAY_MS = 3e4;
@@ -12214,26 +12310,26 @@ var MAX_LOG_SIZE_BYTES2 = 5 * 1024 * 1024;
12214
12310
  var NODE_MEMORY_LIMIT_MB = 2048;
12215
12311
  var activeServers = /* @__PURE__ */ new Map();
12216
12312
  function getLogsDir() {
12217
- const logsDir = path25.join((0, import_core14.getConfigDir)(), "logs");
12218
- if (!fs24.existsSync(logsDir)) {
12219
- fs24.mkdirSync(logsDir, { recursive: true });
12313
+ const logsDir = path26.join((0, import_core14.getConfigDir)(), "logs");
12314
+ if (!fs25.existsSync(logsDir)) {
12315
+ fs25.mkdirSync(logsDir, { recursive: true });
12220
12316
  }
12221
12317
  return logsDir;
12222
12318
  }
12223
12319
  function getLogFilePath(moduleUid) {
12224
- return path25.join(getLogsDir(), `dev-${moduleUid}.log`);
12320
+ return path26.join(getLogsDir(), `dev-${moduleUid}.log`);
12225
12321
  }
12226
12322
  function rotateLogIfNeeded(logPath) {
12227
12323
  try {
12228
- if (fs24.existsSync(logPath)) {
12229
- const stats = fs24.statSync(logPath);
12324
+ if (fs25.existsSync(logPath)) {
12325
+ const stats = fs25.statSync(logPath);
12230
12326
  if (stats.size > MAX_LOG_SIZE_BYTES2) {
12231
12327
  const backupPath = `${logPath}.1`;
12232
- if (fs24.existsSync(backupPath)) {
12233
- fs24.unlinkSync(backupPath);
12328
+ if (fs25.existsSync(backupPath)) {
12329
+ fs25.unlinkSync(backupPath);
12234
12330
  }
12235
- fs24.renameSync(logPath, backupPath);
12236
- console.log(`[DevServer] EP932: Rotated log file for ${path25.basename(logPath)}`);
12331
+ fs25.renameSync(logPath, backupPath);
12332
+ console.log(`[DevServer] EP932: Rotated log file for ${path26.basename(logPath)}`);
12237
12333
  }
12238
12334
  }
12239
12335
  } catch (error) {
@@ -12246,7 +12342,7 @@ function writeToLog(logPath, line, isError = false) {
12246
12342
  const prefix = isError ? "ERR" : "OUT";
12247
12343
  const logLine = `[${timestamp}] [${prefix}] ${line}
12248
12344
  `;
12249
- fs24.appendFileSync(logPath, logLine);
12345
+ fs25.appendFileSync(logPath, logLine);
12250
12346
  } catch {
12251
12347
  }
12252
12348
  }
@@ -12425,8 +12521,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
12425
12521
  });
12426
12522
  injectedEnvVars = result.envVars;
12427
12523
  console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
12428
- const envFilePath = path25.join(projectPath, ".env");
12429
- if (!fs24.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
12524
+ const envFilePath = path26.join(projectPath, ".env");
12525
+ if (!fs25.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
12430
12526
  console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
12431
12527
  writeEnvFile(projectPath, injectedEnvVars);
12432
12528
  }
@@ -12767,10 +12863,11 @@ var IPCRouter = class {
12767
12863
  };
12768
12864
 
12769
12865
  // src/daemon/update-manager.ts
12770
- var fs25 = __toESM(require("fs"));
12771
- var path26 = __toESM(require("path"));
12866
+ var fs26 = __toESM(require("fs"));
12867
+ var path27 = __toESM(require("path"));
12772
12868
  var import_child_process18 = require("child_process");
12773
12869
  var import_core17 = __toESM(require_dist());
12870
+ var semver2 = __toESM(require("semver"));
12774
12871
 
12775
12872
  // src/utils/update-checker.ts
12776
12873
  var import_child_process17 = require("child_process");
@@ -12781,6 +12878,7 @@ var import_core16 = __toESM(require_dist());
12781
12878
 
12782
12879
  // src/utils/update-checker.ts
12783
12880
  var PACKAGE_NAME = "@episoda/cli";
12881
+ var LEGACY_PACKAGE_NAME = "episoda";
12784
12882
  var NPM_REGISTRY = "https://registry.npmjs.org";
12785
12883
  function isFileLinkedInstall() {
12786
12884
  try {
@@ -12853,6 +12951,39 @@ function getInstalledVersion() {
12853
12951
  return null;
12854
12952
  }
12855
12953
  }
12954
+ function getLegacyInstalledVersion() {
12955
+ try {
12956
+ const output = (0, import_child_process17.execSync)(`npm list -g ${LEGACY_PACKAGE_NAME} --json`, {
12957
+ stdio: ["pipe", "pipe", "pipe"],
12958
+ timeout: 1e4
12959
+ }).toString();
12960
+ const data = JSON.parse(output);
12961
+ return data?.dependencies?.[LEGACY_PACKAGE_NAME]?.version || null;
12962
+ } catch {
12963
+ return null;
12964
+ }
12965
+ }
12966
+ function detectCliInstallChannel(embeddedVersion) {
12967
+ const scopedVersion = getInstalledVersion();
12968
+ const legacyVersion = getLegacyInstalledVersion();
12969
+ const effectiveVersion = scopedVersion || legacyVersion || embeddedVersion || null;
12970
+ return {
12971
+ scopedVersion,
12972
+ legacyVersion,
12973
+ legacyOnly: !scopedVersion && !!legacyVersion,
12974
+ effectiveVersion
12975
+ };
12976
+ }
12977
+ function resolveEffectiveCliVersion(embeddedVersion) {
12978
+ const installedVersion = detectCliInstallChannel(embeddedVersion).effectiveVersion;
12979
+ if (!installedVersion) {
12980
+ return embeddedVersion;
12981
+ }
12982
+ if (semver.valid(installedVersion) && semver.valid(embeddedVersion)) {
12983
+ return semver.gt(installedVersion, embeddedVersion) ? installedVersion : embeddedVersion;
12984
+ }
12985
+ return installedVersion === embeddedVersion ? embeddedVersion : installedVersion;
12986
+ }
12856
12987
 
12857
12988
  // src/daemon/update-manager.ts
12858
12989
  var UpdateManager = class _UpdateManager {
@@ -12875,6 +13006,19 @@ var UpdateManager = class _UpdateManager {
12875
13006
  // 4 hours
12876
13007
  this.MAX_UPDATE_ATTEMPTS = 3;
12877
13008
  }
13009
+ /**
13010
+ * Prefer installed CLI version when it's newer than embedded bundle metadata.
13011
+ * This avoids update churn when daemon bundle version lags package install version.
13012
+ */
13013
+ getEffectiveCurrentVersion() {
13014
+ return resolveEffectiveCliVersion(this.currentVersion);
13015
+ }
13016
+ isTargetVersionNewer(targetVersion, currentVersion) {
13017
+ if (semver2.valid(targetVersion) && semver2.valid(currentVersion)) {
13018
+ return semver2.gt(targetVersion, currentVersion);
13019
+ }
13020
+ return targetVersion !== currentVersion;
13021
+ }
12878
13022
  /**
12879
13023
  * Start periodic update checks (every 4 hours).
12880
13024
  * Does nothing if EPISODA_CLI_PIN_VERSION is set.
@@ -12919,7 +13063,8 @@ var UpdateManager = class _UpdateManager {
12919
13063
  return;
12920
13064
  }
12921
13065
  try {
12922
- const result = await checkForUpdates(this.currentVersion);
13066
+ const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
13067
+ const result = await checkForUpdates(effectiveCurrentVersion);
12923
13068
  if (result.updateAvailable) {
12924
13069
  await this.applyUpdateIfIdle(result.latestVersion, "startup");
12925
13070
  }
@@ -12941,8 +13086,9 @@ var UpdateManager = class _UpdateManager {
12941
13086
  }
12942
13087
  async applyUpdateIfIdle(targetVersion, source) {
12943
13088
  if (this.updateInProgress) return;
12944
- if (targetVersion === this.currentVersion) {
12945
- console.log(`[Daemon] EP1324: Already running version ${targetVersion}, skipping update`);
13089
+ const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
13090
+ if (!this.isTargetVersionNewer(targetVersion, effectiveCurrentVersion)) {
13091
+ console.log(`[Daemon] EP1390: Skipping update target=${targetVersion}, current=${effectiveCurrentVersion} (source=${source})`);
12946
13092
  return;
12947
13093
  }
12948
13094
  if (this.lastFailedUpdateVersion === targetVersion && this.updateFailedAttempts >= _UpdateManager.MAX_UPDATE_ATTEMPTS) {
@@ -12952,12 +13098,12 @@ var UpdateManager = class _UpdateManager {
12952
13098
  const activeAgentSessions = this.getActiveAgentSessionCount();
12953
13099
  if (activeAgentSessions > 0) {
12954
13100
  this.pendingUpdateVersion = targetVersion;
12955
- console.log(`[Daemon] EP1319: Update available (${this.currentVersion} \u2192 ${targetVersion}) but ${activeAgentSessions} active agent session(s) \u2014 deferring`);
13101
+ console.log(`[Daemon] EP1319: Update available (${effectiveCurrentVersion} \u2192 ${targetVersion}) but ${activeAgentSessions} active agent session(s) \u2014 deferring`);
12956
13102
  return;
12957
13103
  }
12958
- const currentInstalled = getInstalledVersion();
12959
- if (currentInstalled && currentInstalled === targetVersion) {
12960
- console.log(`[Daemon] EP1343: Already installed version ${targetVersion} (embedded: ${this.currentVersion}), skipping update`);
13104
+ const latestEffectiveCurrent = this.getEffectiveCurrentVersion();
13105
+ if (!this.isTargetVersionNewer(targetVersion, latestEffectiveCurrent)) {
13106
+ console.log(`[Daemon] EP1390: Update no longer needed target=${targetVersion}, current=${latestEffectiveCurrent}`);
12961
13107
  return;
12962
13108
  }
12963
13109
  this.updateInProgress = true;
@@ -12976,16 +13122,24 @@ var UpdateManager = class _UpdateManager {
12976
13122
  console.log(`[Daemon] EP1324: Update to ${targetVersion} installed, restarting daemon...`);
12977
13123
  await this.host.shutdown();
12978
13124
  const configDir = (0, import_core17.getConfigDir)();
12979
- const logPath = path26.join(configDir, "daemon.log");
12980
- const logFd = fs25.openSync(logPath, "a");
13125
+ const logPath = path27.join(configDir, "daemon.log");
13126
+ const logFd = fs26.openSync(logPath, "a");
12981
13127
  const child = (0, import_child_process18.spawn)("node", [this.daemonEntryFile], {
12982
13128
  detached: true,
12983
13129
  stdio: ["ignore", logFd, logFd],
12984
13130
  env: { ...process.env, EPISODA_DAEMON_MODE: "1" }
12985
13131
  });
13132
+ if (!child.pid) {
13133
+ try {
13134
+ fs26.closeSync(logFd);
13135
+ } catch {
13136
+ }
13137
+ this.recordUpdateFailure(targetVersion, "Failed to spawn replacement daemon (missing pid)");
13138
+ return;
13139
+ }
12986
13140
  child.unref();
12987
13141
  const pidPath = getPidFilePath();
12988
- fs25.writeFileSync(pidPath, child.pid.toString(), "utf-8");
13142
+ fs26.writeFileSync(pidPath, child.pid.toString(), "utf-8");
12989
13143
  console.log(`[Daemon] EP1324: New daemon spawned (PID: ${child.pid}), exiting old process`);
12990
13144
  process.exit(0);
12991
13145
  } catch (error) {
@@ -13019,7 +13173,8 @@ var UpdateManager = class _UpdateManager {
13019
13173
  return;
13020
13174
  }
13021
13175
  try {
13022
- const result = await checkForUpdates(this.currentVersion);
13176
+ const effectiveCurrentVersion = this.getEffectiveCurrentVersion();
13177
+ const result = await checkForUpdates(effectiveCurrentVersion);
13023
13178
  if (!result.updateAvailable) {
13024
13179
  if (this.pendingUpdateVersion) {
13025
13180
  console.log(`[Daemon] EP1319: Pending update to ${this.pendingUpdateVersion} is no longer needed (already current)`);
@@ -13035,8 +13190,8 @@ var UpdateManager = class _UpdateManager {
13035
13190
  };
13036
13191
 
13037
13192
  // src/daemon/health-orchestrator.ts
13038
- var fs26 = __toESM(require("fs"));
13039
- var path27 = __toESM(require("path"));
13193
+ var fs27 = __toESM(require("fs"));
13194
+ var path28 = __toESM(require("path"));
13040
13195
  var import_core18 = __toESM(require_dist());
13041
13196
  var HealthOrchestrator = class _HealthOrchestrator {
13042
13197
  constructor(host) {
@@ -13278,26 +13433,26 @@ var HealthOrchestrator = class _HealthOrchestrator {
13278
13433
  checkAndRotateLog() {
13279
13434
  try {
13280
13435
  const configDir = (0, import_core18.getConfigDir)();
13281
- const logPath = path27.join(configDir, "daemon.log");
13282
- if (!fs26.existsSync(logPath)) return;
13283
- const stats = fs26.statSync(logPath);
13436
+ const logPath = path28.join(configDir, "daemon.log");
13437
+ if (!fs27.existsSync(logPath)) return;
13438
+ const stats = fs27.statSync(logPath);
13284
13439
  if (stats.size < _HealthOrchestrator.MAX_LOG_SIZE_BYTES) return;
13285
13440
  console.log(`[Daemon] EP1351: daemon.log is ${Math.round(stats.size / 1024 / 1024)}MB, rotating...`);
13286
13441
  for (let i = _HealthOrchestrator.MAX_LOG_FILES - 1; i >= 1; i--) {
13287
13442
  const src = `${logPath}.${i}`;
13288
13443
  const dst = `${logPath}.${i + 1}`;
13289
- if (fs26.existsSync(src)) {
13444
+ if (fs27.existsSync(src)) {
13290
13445
  try {
13291
- fs26.renameSync(src, dst);
13446
+ fs27.renameSync(src, dst);
13292
13447
  } catch {
13293
13448
  }
13294
13449
  }
13295
13450
  }
13296
13451
  try {
13297
- fs26.copyFileSync(logPath, `${logPath}.1`);
13452
+ fs27.copyFileSync(logPath, `${logPath}.1`);
13298
13453
  } catch {
13299
13454
  }
13300
- fs26.truncateSync(logPath, 0);
13455
+ fs27.truncateSync(logPath, 0);
13301
13456
  console.log("[Daemon] EP1351: Log rotated successfully");
13302
13457
  } catch (error) {
13303
13458
  console.warn("[Daemon] EP1351: Log rotation failed:", error instanceof Error ? error.message : error);
@@ -13679,9 +13834,55 @@ var ConnectionManager = class {
13679
13834
  }
13680
13835
  };
13681
13836
 
13837
+ // src/daemon/ws-endpoint.ts
13838
+ var DEFAULT_API_URL = "https://episoda.dev";
13839
+ var CANONICAL_PROD_WS_HOST = "ws.episoda.dev";
13840
+ var PRODUCTION_API_HOSTS = /* @__PURE__ */ new Set([
13841
+ "episoda.dev",
13842
+ "www.episoda.dev",
13843
+ "api.episoda.dev"
13844
+ ]);
13845
+ function parseApiUrl(rawUrl) {
13846
+ try {
13847
+ return new URL(rawUrl);
13848
+ } catch {
13849
+ return new URL(DEFAULT_API_URL);
13850
+ }
13851
+ }
13852
+ function resolveDaemonWsEndpoint(config, env = process.env) {
13853
+ if (config.ws_url) {
13854
+ return {
13855
+ wsUrl: config.ws_url,
13856
+ source: "config.ws_url",
13857
+ serverUrl: config.project_settings?.local_server_url || config.api_url || env.EPISODA_API_URL || DEFAULT_API_URL
13858
+ };
13859
+ }
13860
+ if (env.EPISODA_WS_URL) {
13861
+ return {
13862
+ wsUrl: env.EPISODA_WS_URL,
13863
+ source: "env.EPISODA_WS_URL",
13864
+ serverUrl: config.project_settings?.local_server_url || config.api_url || env.EPISODA_API_URL || DEFAULT_API_URL
13865
+ };
13866
+ }
13867
+ const rawServerUrl = config.project_settings?.local_server_url || config.api_url || env.EPISODA_API_URL || DEFAULT_API_URL;
13868
+ const serverUrl = parseApiUrl(rawServerUrl);
13869
+ const wsProtocol = serverUrl.protocol === "https:" ? "wss:" : "ws:";
13870
+ const useCanonicalProdHost = PRODUCTION_API_HOSTS.has(serverUrl.hostname);
13871
+ const wsHostname = useCanonicalProdHost ? CANONICAL_PROD_WS_HOST : serverUrl.hostname;
13872
+ const explicitPort = env.EPISODA_WS_PORT?.trim();
13873
+ const inheritedServerPort = !useCanonicalProdHost ? serverUrl.port : "";
13874
+ const wsPort = explicitPort || inheritedServerPort;
13875
+ const wsUrl = wsPort ? `${wsProtocol}//${wsHostname}:${wsPort}` : `${wsProtocol}//${wsHostname}`;
13876
+ return {
13877
+ wsUrl,
13878
+ source: "derived",
13879
+ serverUrl: serverUrl.toString()
13880
+ };
13881
+ }
13882
+
13682
13883
  // src/daemon/project-message-router.ts
13683
13884
  var import_core19 = __toESM(require_dist());
13684
- var path28 = __toESM(require("path"));
13885
+ var path29 = __toESM(require("path"));
13685
13886
  var ProjectMessageRouter = class {
13686
13887
  constructor(host) {
13687
13888
  this.host = host;
@@ -13698,7 +13899,7 @@ var ProjectMessageRouter = class {
13698
13899
  client.updateActivity();
13699
13900
  try {
13700
13901
  const gitCmd = message.command;
13701
- const bareRepoPath = path28.join(projectPath, ".bare");
13902
+ const bareRepoPath = path29.join(projectPath, ".bare");
13702
13903
  const cwd = gitCmd.worktreePath || bareRepoPath;
13703
13904
  if (gitCmd.worktreePath) {
13704
13905
  console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
@@ -14038,32 +14239,32 @@ async function fetchEnvVars2(projectId) {
14038
14239
  }
14039
14240
 
14040
14241
  // src/daemon/project-git-config.ts
14041
- var fs27 = __toESM(require("fs"));
14042
- var path29 = __toESM(require("path"));
14242
+ var fs28 = __toESM(require("fs"));
14243
+ var path30 = __toESM(require("path"));
14043
14244
  var import_core21 = __toESM(require_dist());
14044
14245
  function getGitDirs(projectPath) {
14045
- const bareDir = path29.join(projectPath, ".bare");
14046
- const gitPath = path29.join(projectPath, ".git");
14047
- if (fs27.existsSync(bareDir) && fs27.statSync(bareDir).isDirectory()) {
14246
+ const bareDir = path30.join(projectPath, ".bare");
14247
+ const gitPath = path30.join(projectPath, ".git");
14248
+ if (fs28.existsSync(bareDir) && fs28.statSync(bareDir).isDirectory()) {
14048
14249
  return { gitDir: bareDir, workDir: projectPath };
14049
14250
  }
14050
- if (fs27.existsSync(gitPath) && fs27.statSync(gitPath).isDirectory()) {
14251
+ if (fs28.existsSync(gitPath) && fs28.statSync(gitPath).isDirectory()) {
14051
14252
  return { gitDir: null, workDir: projectPath };
14052
14253
  }
14053
- if (fs27.existsSync(gitPath) && fs27.statSync(gitPath).isFile()) {
14254
+ if (fs28.existsSync(gitPath) && fs28.statSync(gitPath).isFile()) {
14054
14255
  return { gitDir: null, workDir: projectPath };
14055
14256
  }
14056
- const entries = fs27.readdirSync(projectPath, { withFileTypes: true });
14257
+ const entries = fs28.readdirSync(projectPath, { withFileTypes: true });
14057
14258
  for (const entry of entries) {
14058
14259
  if (entry.isDirectory() && entry.name.startsWith("EP")) {
14059
- const worktreePath = path29.join(projectPath, entry.name);
14060
- const worktreeGit = path29.join(worktreePath, ".git");
14061
- if (fs27.existsSync(worktreeGit)) {
14260
+ const worktreePath = path30.join(projectPath, entry.name);
14261
+ const worktreeGit = path30.join(worktreePath, ".git");
14262
+ if (fs28.existsSync(worktreeGit)) {
14062
14263
  return { gitDir: null, workDir: worktreePath };
14063
14264
  }
14064
14265
  }
14065
14266
  }
14066
- if (fs27.existsSync(bareDir)) {
14267
+ if (fs28.existsSync(bareDir)) {
14067
14268
  return { gitDir: bareDir, workDir: projectPath };
14068
14269
  }
14069
14270
  return { gitDir: null, workDir: projectPath };
@@ -14108,24 +14309,24 @@ async function configureGitUser(projectPath, userId, workspaceId, machineId, pro
14108
14309
  async function installGitHooks(projectPath) {
14109
14310
  const hooks = ["post-checkout", "pre-commit", "post-commit"];
14110
14311
  let hooksDir;
14111
- const bareHooksDir = path29.join(projectPath, ".bare", "hooks");
14112
- const gitHooksDir = path29.join(projectPath, ".git", "hooks");
14113
- if (fs27.existsSync(bareHooksDir)) {
14312
+ const bareHooksDir = path30.join(projectPath, ".bare", "hooks");
14313
+ const gitHooksDir = path30.join(projectPath, ".git", "hooks");
14314
+ if (fs28.existsSync(bareHooksDir)) {
14114
14315
  hooksDir = bareHooksDir;
14115
- } else if (fs27.existsSync(gitHooksDir) && fs27.statSync(path29.join(projectPath, ".git")).isDirectory()) {
14316
+ } else if (fs28.existsSync(gitHooksDir) && fs28.statSync(path30.join(projectPath, ".git")).isDirectory()) {
14116
14317
  hooksDir = gitHooksDir;
14117
14318
  } else {
14118
- const parentBareHooks = path29.join(projectPath, "..", ".bare", "hooks");
14119
- if (fs27.existsSync(parentBareHooks)) {
14319
+ const parentBareHooks = path30.join(projectPath, "..", ".bare", "hooks");
14320
+ if (fs28.existsSync(parentBareHooks)) {
14120
14321
  hooksDir = parentBareHooks;
14121
14322
  } else {
14122
14323
  console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
14123
14324
  return;
14124
14325
  }
14125
14326
  }
14126
- if (!fs27.existsSync(hooksDir)) {
14327
+ if (!fs28.existsSync(hooksDir)) {
14127
14328
  try {
14128
- fs27.mkdirSync(hooksDir, { recursive: true });
14329
+ fs28.mkdirSync(hooksDir, { recursive: true });
14129
14330
  } catch {
14130
14331
  console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
14131
14332
  return;
@@ -14133,20 +14334,20 @@ async function installGitHooks(projectPath) {
14133
14334
  }
14134
14335
  for (const hookName of hooks) {
14135
14336
  try {
14136
- const hookPath = path29.join(hooksDir, hookName);
14137
- const bundledHookPath = path29.join(__dirname, "..", "hooks", hookName);
14138
- if (!fs27.existsSync(bundledHookPath)) {
14337
+ const hookPath = path30.join(hooksDir, hookName);
14338
+ const bundledHookPath = path30.join(__dirname, "..", "hooks", hookName);
14339
+ if (!fs28.existsSync(bundledHookPath)) {
14139
14340
  console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
14140
14341
  continue;
14141
14342
  }
14142
- const hookContent = fs27.readFileSync(bundledHookPath, "utf-8");
14143
- if (fs27.existsSync(hookPath)) {
14144
- const existingContent = fs27.readFileSync(hookPath, "utf-8");
14343
+ const hookContent = fs28.readFileSync(bundledHookPath, "utf-8");
14344
+ if (fs28.existsSync(hookPath)) {
14345
+ const existingContent = fs28.readFileSync(hookPath, "utf-8");
14145
14346
  if (existingContent === hookContent) {
14146
14347
  continue;
14147
14348
  }
14148
14349
  }
14149
- fs27.writeFileSync(hookPath, hookContent, { mode: 493 });
14350
+ fs28.writeFileSync(hookPath, hookContent, { mode: 493 });
14150
14351
  console.log(`[Daemon] Installed git hook: ${hookName}`);
14151
14352
  } catch (error) {
14152
14353
  console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
@@ -14178,21 +14379,6 @@ async function cacheMachineUuid(machineUuid, machineId) {
14178
14379
 
14179
14380
  // src/daemon/daemon-process.ts
14180
14381
  var packageJson = require_package();
14181
- function getBuildPackagesCommand2(installCmd) {
14182
- const runner = installCmd?.command?.[0];
14183
- switch (runner) {
14184
- case "pnpm":
14185
- return "pnpm run build:packages";
14186
- case "yarn":
14187
- return "yarn build:packages";
14188
- case "npm":
14189
- return "npm run build:packages";
14190
- case "bun":
14191
- return "bun run build:packages";
14192
- default:
14193
- return null;
14194
- }
14195
- }
14196
14382
  var Daemon = class _Daemon {
14197
14383
  constructor() {
14198
14384
  this.machineId = "";
@@ -14217,10 +14403,11 @@ var Daemon = class _Daemon {
14217
14403
  // 2 minutes
14218
14404
  // EP1360: Per-session monotonic event seq for daemon→platform stream gap detection.
14219
14405
  this.agentEventSeq = /* @__PURE__ */ new Map();
14406
+ this.cliRuntimeVersion = resolveEffectiveCliVersion(packageJson.version);
14220
14407
  this.ipcServer = new IPCServer();
14221
14408
  this.connectionManager = new ConnectionManager();
14222
14409
  this.ipcRouter = new IPCRouter(this, this.ipcServer);
14223
- this.updateManager = new UpdateManager(this, packageJson.version, __filename);
14410
+ this.updateManager = new UpdateManager(this, this.cliRuntimeVersion, __filename);
14224
14411
  this.healthOrchestrator = new HealthOrchestrator(this);
14225
14412
  this.daemonCore = new DaemonCore(this);
14226
14413
  this.projectMessageRouter = new ProjectMessageRouter({
@@ -14303,9 +14490,9 @@ var Daemon = class _Daemon {
14303
14490
  this.healthServer = http2.createServer((req, res) => {
14304
14491
  if (req.url === "/health" || req.url === "/") {
14305
14492
  const isConnected = this.connectionManager.liveConnectionCount() > 0;
14306
- const projects = Array.from(this.connectionManager.entries()).map(([path30, conn]) => ({
14307
- path: path30,
14308
- connected: this.connectionManager.hasLiveConnection(path30)
14493
+ const projects = Array.from(this.connectionManager.entries()).map(([path31, conn]) => ({
14494
+ path: path31,
14495
+ connected: this.connectionManager.hasLiveConnection(path31)
14309
14496
  }));
14310
14497
  const status = {
14311
14498
  status: isConnected ? "healthy" : "degraded",
@@ -14378,8 +14565,8 @@ var Daemon = class _Daemon {
14378
14565
  await this.shutdown();
14379
14566
  try {
14380
14567
  const pidPath = getPidFilePath();
14381
- if (fs28.existsSync(pidPath)) {
14382
- fs28.unlinkSync(pidPath);
14568
+ if (fs29.existsSync(pidPath)) {
14569
+ fs29.unlinkSync(pidPath);
14383
14570
  console.log("[Daemon] PID file cleaned up (watchdog)");
14384
14571
  }
14385
14572
  } catch (error) {
@@ -14485,23 +14672,15 @@ var Daemon = class _Daemon {
14485
14672
  if (!config || !config.access_token) {
14486
14673
  throw new Error("No access token found. Please run: episoda auth");
14487
14674
  }
14488
- let serverUrl = config.api_url || process.env.EPISODA_API_URL || "https://episoda.dev";
14489
- if (config.project_settings?.local_server_url) {
14490
- serverUrl = config.project_settings.local_server_url;
14491
- console.log(`[Daemon] Using cached server URL: ${serverUrl}`);
14492
- }
14493
- let wsUrl;
14494
- if (config.ws_url) {
14495
- wsUrl = config.ws_url;
14496
- console.log(`[Daemon] Using configured ws_url: ${wsUrl}`);
14497
- } else {
14498
- const serverUrlObj = new URL(serverUrl);
14499
- const wsProtocol = serverUrlObj.protocol === "https:" ? "wss:" : "ws:";
14500
- const wsHostname = serverUrlObj.hostname === "episoda.dev" ? "ws.episoda.dev" : serverUrlObj.hostname;
14501
- const wsPort = process.env.EPISODA_WS_PORT;
14502
- wsUrl = wsPort ? `${wsProtocol}//${wsHostname}:${wsPort}` : `${wsProtocol}//${wsHostname}`;
14675
+ const wsEndpoint = resolveDaemonWsEndpoint(config);
14676
+ if (wsEndpoint.source === "config.ws_url") {
14677
+ console.log(`[Daemon] Using configured ws_url: ${wsEndpoint.wsUrl}`);
14678
+ } else if (wsEndpoint.source === "env.EPISODA_WS_URL") {
14679
+ console.log(`[Daemon] Using EPISODA_WS_URL override: ${wsEndpoint.wsUrl}`);
14680
+ } else if (config.project_settings?.local_server_url) {
14681
+ console.log(`[Daemon] Using cached server URL: ${wsEndpoint.serverUrl}`);
14503
14682
  }
14504
- console.log(`[Daemon] Connecting to ${wsUrl} for project ${projectId}...`);
14683
+ console.log(`[Daemon] Connecting to ${wsEndpoint.wsUrl} for project ${projectId} (source: ${wsEndpoint.source})...`);
14505
14684
  const client = new import_core22.EpisodaClient();
14506
14685
  const gitExecutor = new import_core22.GitExecutor();
14507
14686
  const connection = {
@@ -14653,7 +14832,7 @@ var Daemon = class _Daemon {
14653
14832
  const resolvedProjectPath = getProjectPath(cmd.workspaceSlug, cmd.projectSlug);
14654
14833
  if (resolvedProjectPath !== projectPath) {
14655
14834
  try {
14656
- await fs28.promises.access(resolvedProjectPath);
14835
+ await fs29.promises.access(resolvedProjectPath);
14657
14836
  agentWorkingDir = resolvedProjectPath;
14658
14837
  console.log(`[Daemon] EP1230: Agent for ${cmd.moduleUid || "project"} in resolved project path: ${agentWorkingDir}`);
14659
14838
  } catch {
@@ -14985,8 +15164,8 @@ var Daemon = class _Daemon {
14985
15164
  let daemonPid;
14986
15165
  try {
14987
15166
  const pidPath = getPidFilePath();
14988
- if (fs28.existsSync(pidPath)) {
14989
- const pidStr = fs28.readFileSync(pidPath, "utf-8").trim();
15167
+ if (fs29.existsSync(pidPath)) {
15168
+ const pidStr = fs29.readFileSync(pidPath, "utf-8").trim();
14990
15169
  daemonPid = parseInt(pidStr, 10);
14991
15170
  }
14992
15171
  } catch (pidError) {
@@ -14995,12 +15174,15 @@ var Daemon = class _Daemon {
14995
15174
  const modeConfig = getDaemonModeConfig();
14996
15175
  const environment = modeConfig.mode;
14997
15176
  const containerId = process.env.EPISODA_CONTAINER_ID;
14998
- await client.connect(wsUrl, config.access_token, this.machineId, {
15177
+ const capabilities = ["worktree_create_v1"];
15178
+ await client.connect(wsEndpoint.wsUrl, config.access_token, this.machineId, {
14999
15179
  hostname: os12.hostname(),
15000
15180
  osPlatform: os12.platform(),
15001
15181
  osArch: os12.arch(),
15002
15182
  daemonPid,
15003
- cliVersion: packageJson.version,
15183
+ cliVersion: this.cliRuntimeVersion,
15184
+ cliPackageName: packageJson.name,
15185
+ capabilities,
15004
15186
  environment,
15005
15187
  containerId
15006
15188
  });
@@ -15453,23 +15635,25 @@ var Daemon = class _Daemon {
15453
15635
  } else {
15454
15636
  console.log(`[Daemon] EP1002: No package manager detected, skipping dependency installation`);
15455
15637
  }
15456
- if (process.env.EPISODA_MODE === "cloud" && installSucceeded) {
15457
- const buildCmd = getBuildPackagesCommand2(installCmd);
15458
- if (buildCmd) {
15459
- console.log(`[Daemon] EP1262: Bootstrapping packages for cloud dev (${buildCmd})`);
15460
- try {
15461
- const { execSync: execSync10 } = await import("child_process");
15462
- execSync10(buildCmd, {
15463
- cwd: worktreePath,
15464
- stdio: "inherit",
15465
- timeout: 10 * 60 * 1e3,
15466
- env: { ...process.env, CI: "true" }
15467
- });
15468
- console.log("[Daemon] EP1262: Package bootstrap completed");
15469
- } catch (buildError) {
15470
- const errorMsg = buildError instanceof Error ? buildError.message : String(buildError);
15471
- console.warn(`[Daemon] EP1262: Package bootstrap failed (non-fatal): ${errorMsg}`);
15472
- }
15638
+ const hasBuildPackages = hasPackageScript(worktreePath, "build:packages");
15639
+ const buildCmd = getBuildPackagesCommand(installCmd?.command?.[0], hasBuildPackages);
15640
+ if (buildCmd && shouldRunBuildPackagesBootstrap(installSucceeded, buildCmd)) {
15641
+ const bootstrapCmd = buildCmd;
15642
+ console.log(`[Daemon] EP1386: Bootstrapping packages after dependency install (${bootstrapCmd})`);
15643
+ try {
15644
+ const { execSync: execSync10 } = await import("child_process");
15645
+ execSync10(bootstrapCmd, {
15646
+ cwd: worktreePath,
15647
+ stdio: "inherit",
15648
+ timeout: 10 * 60 * 1e3,
15649
+ env: { ...process.env, CI: "true" }
15650
+ });
15651
+ console.log("[Daemon] EP1386: Package bootstrap completed");
15652
+ } catch (buildError) {
15653
+ const errorMsg = buildError instanceof Error ? buildError.message : String(buildError);
15654
+ await worktreeManager.updateWorktreeStatus(moduleUid, "error", errorMsg);
15655
+ await this.updateModuleWorktreeStatus(moduleUid, "error", worktreePath, errorMsg);
15656
+ throw new Error(`Package bootstrap failed: ${errorMsg}`);
15473
15657
  }
15474
15658
  }
15475
15659
  if (setupScript) {
@@ -15551,8 +15735,8 @@ var Daemon = class _Daemon {
15551
15735
  await this.shutdown();
15552
15736
  try {
15553
15737
  const pidPath = getPidFilePath();
15554
- if (fs28.existsSync(pidPath)) {
15555
- fs28.unlinkSync(pidPath);
15738
+ if (fs29.existsSync(pidPath)) {
15739
+ fs29.unlinkSync(pidPath);
15556
15740
  console.log("[Daemon] PID file cleaned up");
15557
15741
  }
15558
15742
  } catch (error) {