@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.
- package/dist/daemon/daemon-process.js +433 -249
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +280 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
1647
|
+
await fs30.access(rebaseMergePath);
|
|
1587
1648
|
inRebase = true;
|
|
1588
1649
|
} catch {
|
|
1589
1650
|
try {
|
|
1590
|
-
await
|
|
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
|
|
1707
|
+
const fs30 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1647
1708
|
try {
|
|
1648
|
-
await
|
|
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
|
|
1768
|
+
const fs30 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1708
1769
|
try {
|
|
1709
|
-
await
|
|
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
|
|
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
|
|
1879
|
-
const
|
|
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
|
|
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 =
|
|
1951
|
+
const parentDir = path31.dirname(command.path);
|
|
1891
1952
|
try {
|
|
1892
|
-
await
|
|
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
|
|
1941
|
-
const
|
|
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 =
|
|
1947
|
-
const episodaDir =
|
|
2007
|
+
const bareDir = path31.join(currentPath, ".bare");
|
|
2008
|
+
const episodaDir = path31.join(currentPath, ".episoda");
|
|
1948
2009
|
try {
|
|
1949
|
-
await
|
|
1950
|
-
await
|
|
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 =
|
|
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
|
|
2611
|
-
var
|
|
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 ||
|
|
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
|
|
2691
|
+
return path31.join(getConfigDir10(), DEFAULT_CONFIG_FILE);
|
|
2625
2692
|
}
|
|
2626
2693
|
function ensureConfigDir(configPath) {
|
|
2627
|
-
const dir =
|
|
2628
|
-
const isNew = !
|
|
2694
|
+
const dir = path31.dirname(configPath);
|
|
2695
|
+
const isNew = !fs30.existsSync(dir);
|
|
2629
2696
|
if (isNew) {
|
|
2630
|
-
|
|
2697
|
+
fs30.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2631
2698
|
}
|
|
2632
2699
|
if (process.platform === "darwin") {
|
|
2633
|
-
const nosyncPath =
|
|
2634
|
-
if (isNew || !
|
|
2700
|
+
const nosyncPath = path31.join(dir, ".nosync");
|
|
2701
|
+
if (isNew || !fs30.existsSync(nosyncPath)) {
|
|
2635
2702
|
try {
|
|
2636
|
-
|
|
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 (!
|
|
2717
|
+
if (!fs30.existsSync(pathToFile)) {
|
|
2651
2718
|
return null;
|
|
2652
2719
|
}
|
|
2653
2720
|
try {
|
|
2654
|
-
const content =
|
|
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 (!
|
|
2760
|
+
if (!fs30.existsSync(workspaceConfigPath)) {
|
|
2694
2761
|
return null;
|
|
2695
2762
|
}
|
|
2696
2763
|
try {
|
|
2697
|
-
const content =
|
|
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 (!
|
|
2792
|
+
if (!fs30.existsSync(projectConfigPath)) {
|
|
2726
2793
|
return null;
|
|
2727
2794
|
}
|
|
2728
2795
|
try {
|
|
2729
|
-
const content =
|
|
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
|
-
|
|
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.
|
|
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
|
|
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]
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
10217
|
-
var
|
|
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: "
|
|
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: "
|
|
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 =
|
|
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 (!
|
|
11841
|
+
if (!fs23.existsSync(workspaceConfigPath)) {
|
|
11720
11842
|
return;
|
|
11721
11843
|
}
|
|
11722
11844
|
try {
|
|
11723
|
-
const content =
|
|
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
|
-
|
|
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 =
|
|
11904
|
+
const bareRepoPath = path24.join(projectPath, ".bare");
|
|
11809
11905
|
const gitEnv = projectId ? { ...process.env, EPISODA_PROJECT_ID: projectId } : process.env;
|
|
11810
|
-
if (!
|
|
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 =
|
|
11814
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
11865
|
-
|
|
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
|
-
|
|
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 =
|
|
12007
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
12027
|
-
if (
|
|
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
|
|
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
|
|
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 =
|
|
12060
|
-
const stat = await
|
|
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
|
|
12165
|
+
const content = await fs23.promises.readFile(filePath);
|
|
12070
12166
|
const base64Content = content.toString("base64");
|
|
12071
|
-
const ext =
|
|
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
|
|
12128
|
-
var
|
|
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 =
|
|
12169
|
-
const configDir =
|
|
12170
|
-
const configPath =
|
|
12171
|
-
await
|
|
12172
|
-
await
|
|
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
|
|
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
|
|
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
|
|
12209
|
-
var
|
|
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 =
|
|
12218
|
-
if (!
|
|
12219
|
-
|
|
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
|
|
12320
|
+
return path26.join(getLogsDir(), `dev-${moduleUid}.log`);
|
|
12225
12321
|
}
|
|
12226
12322
|
function rotateLogIfNeeded(logPath) {
|
|
12227
12323
|
try {
|
|
12228
|
-
if (
|
|
12229
|
-
const stats =
|
|
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 (
|
|
12233
|
-
|
|
12328
|
+
if (fs25.existsSync(backupPath)) {
|
|
12329
|
+
fs25.unlinkSync(backupPath);
|
|
12234
12330
|
}
|
|
12235
|
-
|
|
12236
|
-
console.log(`[DevServer] EP932: Rotated log file for ${
|
|
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
|
-
|
|
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 =
|
|
12429
|
-
if (!
|
|
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
|
|
12771
|
-
var
|
|
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
|
|
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
|
-
|
|
12945
|
-
|
|
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 (${
|
|
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
|
|
12959
|
-
if (
|
|
12960
|
-
console.log(`[Daemon]
|
|
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 =
|
|
12980
|
-
const logFd =
|
|
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
|
-
|
|
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
|
|
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
|
|
13039
|
-
var
|
|
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 =
|
|
13282
|
-
if (!
|
|
13283
|
-
const stats =
|
|
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 (
|
|
13444
|
+
if (fs27.existsSync(src)) {
|
|
13290
13445
|
try {
|
|
13291
|
-
|
|
13446
|
+
fs27.renameSync(src, dst);
|
|
13292
13447
|
} catch {
|
|
13293
13448
|
}
|
|
13294
13449
|
}
|
|
13295
13450
|
}
|
|
13296
13451
|
try {
|
|
13297
|
-
|
|
13452
|
+
fs27.copyFileSync(logPath, `${logPath}.1`);
|
|
13298
13453
|
} catch {
|
|
13299
13454
|
}
|
|
13300
|
-
|
|
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
|
|
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 =
|
|
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
|
|
14042
|
-
var
|
|
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 =
|
|
14046
|
-
const gitPath =
|
|
14047
|
-
if (
|
|
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 (
|
|
14251
|
+
if (fs28.existsSync(gitPath) && fs28.statSync(gitPath).isDirectory()) {
|
|
14051
14252
|
return { gitDir: null, workDir: projectPath };
|
|
14052
14253
|
}
|
|
14053
|
-
if (
|
|
14254
|
+
if (fs28.existsSync(gitPath) && fs28.statSync(gitPath).isFile()) {
|
|
14054
14255
|
return { gitDir: null, workDir: projectPath };
|
|
14055
14256
|
}
|
|
14056
|
-
const entries =
|
|
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 =
|
|
14060
|
-
const worktreeGit =
|
|
14061
|
-
if (
|
|
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 (
|
|
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 =
|
|
14112
|
-
const gitHooksDir =
|
|
14113
|
-
if (
|
|
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 (
|
|
14316
|
+
} else if (fs28.existsSync(gitHooksDir) && fs28.statSync(path30.join(projectPath, ".git")).isDirectory()) {
|
|
14116
14317
|
hooksDir = gitHooksDir;
|
|
14117
14318
|
} else {
|
|
14118
|
-
const parentBareHooks =
|
|
14119
|
-
if (
|
|
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 (!
|
|
14327
|
+
if (!fs28.existsSync(hooksDir)) {
|
|
14127
14328
|
try {
|
|
14128
|
-
|
|
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 =
|
|
14137
|
-
const bundledHookPath =
|
|
14138
|
-
if (!
|
|
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 =
|
|
14143
|
-
if (
|
|
14144
|
-
const existingContent =
|
|
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
|
-
|
|
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,
|
|
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(([
|
|
14307
|
-
path:
|
|
14308
|
-
connected: this.connectionManager.hasLiveConnection(
|
|
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 (
|
|
14382
|
-
|
|
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
|
-
|
|
14489
|
-
if (config.
|
|
14490
|
-
|
|
14491
|
-
|
|
14492
|
-
|
|
14493
|
-
|
|
14494
|
-
|
|
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
|
|
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 (
|
|
14989
|
-
const pidStr =
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15460
|
-
|
|
15461
|
-
|
|
15462
|
-
|
|
15463
|
-
|
|
15464
|
-
|
|
15465
|
-
|
|
15466
|
-
|
|
15467
|
-
}
|
|
15468
|
-
|
|
15469
|
-
|
|
15470
|
-
|
|
15471
|
-
|
|
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 (
|
|
15555
|
-
|
|
15738
|
+
if (fs29.existsSync(pidPath)) {
|
|
15739
|
+
fs29.unlinkSync(pidPath);
|
|
15556
15740
|
console.log("[Daemon] PID file cleaned up");
|
|
15557
15741
|
}
|
|
15558
15742
|
} catch (error) {
|