episoda 0.2.93 → 0.2.95
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 +612 -409
- package/dist/daemon/daemon-process.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
|
@@ -1571,15 +1571,15 @@ var require_git_executor = __commonJS({
|
|
|
1571
1571
|
try {
|
|
1572
1572
|
const { stdout: gitDir } = await execAsync3("git rev-parse --git-dir", { cwd, timeout: 5e3 });
|
|
1573
1573
|
const gitDirPath = gitDir.trim();
|
|
1574
|
-
const
|
|
1574
|
+
const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1575
1575
|
const rebaseMergePath = `${gitDirPath}/rebase-merge`;
|
|
1576
1576
|
const rebaseApplyPath = `${gitDirPath}/rebase-apply`;
|
|
1577
1577
|
try {
|
|
1578
|
-
await
|
|
1578
|
+
await fs22.access(rebaseMergePath);
|
|
1579
1579
|
inRebase = true;
|
|
1580
1580
|
} catch {
|
|
1581
1581
|
try {
|
|
1582
|
-
await
|
|
1582
|
+
await fs22.access(rebaseApplyPath);
|
|
1583
1583
|
inRebase = true;
|
|
1584
1584
|
} catch {
|
|
1585
1585
|
inRebase = false;
|
|
@@ -1633,9 +1633,9 @@ var require_git_executor = __commonJS({
|
|
|
1633
1633
|
error: validation.error || "UNKNOWN_ERROR"
|
|
1634
1634
|
};
|
|
1635
1635
|
}
|
|
1636
|
-
const
|
|
1636
|
+
const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1637
1637
|
try {
|
|
1638
|
-
await
|
|
1638
|
+
await fs22.access(command.path);
|
|
1639
1639
|
return {
|
|
1640
1640
|
success: false,
|
|
1641
1641
|
error: "WORKTREE_EXISTS",
|
|
@@ -1689,9 +1689,9 @@ var require_git_executor = __commonJS({
|
|
|
1689
1689
|
*/
|
|
1690
1690
|
async executeWorktreeRemove(command, cwd, options) {
|
|
1691
1691
|
try {
|
|
1692
|
-
const
|
|
1692
|
+
const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1693
1693
|
try {
|
|
1694
|
-
await
|
|
1694
|
+
await fs22.access(command.path);
|
|
1695
1695
|
} catch {
|
|
1696
1696
|
return {
|
|
1697
1697
|
success: false,
|
|
@@ -1726,7 +1726,7 @@ var require_git_executor = __commonJS({
|
|
|
1726
1726
|
const result = await this.runGitCommand(args, cwd, options);
|
|
1727
1727
|
if (result.success) {
|
|
1728
1728
|
try {
|
|
1729
|
-
await
|
|
1729
|
+
await fs22.rm(command.path, { recursive: true, force: true });
|
|
1730
1730
|
} catch {
|
|
1731
1731
|
}
|
|
1732
1732
|
return {
|
|
@@ -1860,10 +1860,10 @@ var require_git_executor = __commonJS({
|
|
|
1860
1860
|
*/
|
|
1861
1861
|
async executeCloneBare(command, options) {
|
|
1862
1862
|
try {
|
|
1863
|
-
const
|
|
1864
|
-
const
|
|
1863
|
+
const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1864
|
+
const path23 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1865
1865
|
try {
|
|
1866
|
-
await
|
|
1866
|
+
await fs22.access(command.path);
|
|
1867
1867
|
return {
|
|
1868
1868
|
success: false,
|
|
1869
1869
|
error: "BRANCH_ALREADY_EXISTS",
|
|
@@ -1872,9 +1872,9 @@ var require_git_executor = __commonJS({
|
|
|
1872
1872
|
};
|
|
1873
1873
|
} catch {
|
|
1874
1874
|
}
|
|
1875
|
-
const parentDir =
|
|
1875
|
+
const parentDir = path23.dirname(command.path);
|
|
1876
1876
|
try {
|
|
1877
|
-
await
|
|
1877
|
+
await fs22.mkdir(parentDir, { recursive: true });
|
|
1878
1878
|
} catch {
|
|
1879
1879
|
}
|
|
1880
1880
|
const { stdout, stderr } = await execAsync3(
|
|
@@ -1922,22 +1922,22 @@ var require_git_executor = __commonJS({
|
|
|
1922
1922
|
*/
|
|
1923
1923
|
async executeProjectInfo(cwd, options) {
|
|
1924
1924
|
try {
|
|
1925
|
-
const
|
|
1926
|
-
const
|
|
1925
|
+
const fs22 = await Promise.resolve().then(() => __importStar(require("fs"))).then((m) => m.promises);
|
|
1926
|
+
const path23 = await Promise.resolve().then(() => __importStar(require("path")));
|
|
1927
1927
|
let currentPath = cwd;
|
|
1928
1928
|
let projectPath = cwd;
|
|
1929
1929
|
let bareRepoPath;
|
|
1930
1930
|
for (let i = 0; i < 10; i++) {
|
|
1931
|
-
const bareDir =
|
|
1932
|
-
const episodaDir =
|
|
1931
|
+
const bareDir = path23.join(currentPath, ".bare");
|
|
1932
|
+
const episodaDir = path23.join(currentPath, ".episoda");
|
|
1933
1933
|
try {
|
|
1934
|
-
await
|
|
1935
|
-
await
|
|
1934
|
+
await fs22.access(bareDir);
|
|
1935
|
+
await fs22.access(episodaDir);
|
|
1936
1936
|
projectPath = currentPath;
|
|
1937
1937
|
bareRepoPath = bareDir;
|
|
1938
1938
|
break;
|
|
1939
1939
|
} catch {
|
|
1940
|
-
const parentPath =
|
|
1940
|
+
const parentPath = path23.dirname(currentPath);
|
|
1941
1941
|
if (parentPath === currentPath) {
|
|
1942
1942
|
break;
|
|
1943
1943
|
}
|
|
@@ -2153,7 +2153,7 @@ var require_websocket_client = __commonJS({
|
|
|
2153
2153
|
clearTimeout(this.reconnectTimeout);
|
|
2154
2154
|
this.reconnectTimeout = void 0;
|
|
2155
2155
|
}
|
|
2156
|
-
return new Promise((
|
|
2156
|
+
return new Promise((resolve4, reject) => {
|
|
2157
2157
|
const connectionTimeout = setTimeout(() => {
|
|
2158
2158
|
if (this.ws) {
|
|
2159
2159
|
this.ws.terminate();
|
|
@@ -2182,7 +2182,7 @@ var require_websocket_client = __commonJS({
|
|
|
2182
2182
|
daemonPid: this.daemonPid
|
|
2183
2183
|
});
|
|
2184
2184
|
this.startHeartbeat();
|
|
2185
|
-
|
|
2185
|
+
resolve4();
|
|
2186
2186
|
});
|
|
2187
2187
|
this.ws.on("pong", () => {
|
|
2188
2188
|
if (this.heartbeatTimeoutTimer) {
|
|
@@ -2313,13 +2313,13 @@ var require_websocket_client = __commonJS({
|
|
|
2313
2313
|
console.warn("[EpisodaClient] Cannot send - WebSocket not connected");
|
|
2314
2314
|
return false;
|
|
2315
2315
|
}
|
|
2316
|
-
return new Promise((
|
|
2316
|
+
return new Promise((resolve4) => {
|
|
2317
2317
|
this.ws.send(JSON.stringify(message), (error) => {
|
|
2318
2318
|
if (error) {
|
|
2319
2319
|
console.error("[EpisodaClient] Failed to send message:", error);
|
|
2320
|
-
|
|
2320
|
+
resolve4(false);
|
|
2321
2321
|
} else {
|
|
2322
|
-
|
|
2322
|
+
resolve4(true);
|
|
2323
2323
|
}
|
|
2324
2324
|
});
|
|
2325
2325
|
});
|
|
@@ -2572,31 +2572,31 @@ var require_auth = __commonJS({
|
|
|
2572
2572
|
exports2.loadConfig = loadConfig8;
|
|
2573
2573
|
exports2.saveConfig = saveConfig2;
|
|
2574
2574
|
exports2.validateToken = validateToken;
|
|
2575
|
-
var
|
|
2576
|
-
var
|
|
2575
|
+
var fs22 = __importStar(require("fs"));
|
|
2576
|
+
var path23 = __importStar(require("path"));
|
|
2577
2577
|
var os9 = __importStar(require("os"));
|
|
2578
2578
|
var child_process_1 = require("child_process");
|
|
2579
2579
|
var DEFAULT_CONFIG_FILE = "config.json";
|
|
2580
2580
|
function getConfigDir8() {
|
|
2581
|
-
return process.env.EPISODA_CONFIG_DIR ||
|
|
2581
|
+
return process.env.EPISODA_CONFIG_DIR || path23.join(os9.homedir(), ".episoda");
|
|
2582
2582
|
}
|
|
2583
2583
|
function getConfigPath(configPath) {
|
|
2584
2584
|
if (configPath) {
|
|
2585
2585
|
return configPath;
|
|
2586
2586
|
}
|
|
2587
|
-
return
|
|
2587
|
+
return path23.join(getConfigDir8(), DEFAULT_CONFIG_FILE);
|
|
2588
2588
|
}
|
|
2589
2589
|
function ensureConfigDir(configPath) {
|
|
2590
|
-
const dir =
|
|
2591
|
-
const isNew = !
|
|
2590
|
+
const dir = path23.dirname(configPath);
|
|
2591
|
+
const isNew = !fs22.existsSync(dir);
|
|
2592
2592
|
if (isNew) {
|
|
2593
|
-
|
|
2593
|
+
fs22.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2594
2594
|
}
|
|
2595
2595
|
if (process.platform === "darwin") {
|
|
2596
|
-
const nosyncPath =
|
|
2597
|
-
if (isNew || !
|
|
2596
|
+
const nosyncPath = path23.join(dir, ".nosync");
|
|
2597
|
+
if (isNew || !fs22.existsSync(nosyncPath)) {
|
|
2598
2598
|
try {
|
|
2599
|
-
|
|
2599
|
+
fs22.writeFileSync(nosyncPath, "", { mode: 384 });
|
|
2600
2600
|
(0, child_process_1.execSync)(`xattr -w com.apple.fileprovider.ignore 1 "${dir}"`, {
|
|
2601
2601
|
stdio: "ignore",
|
|
2602
2602
|
timeout: 5e3
|
|
@@ -2608,9 +2608,9 @@ var require_auth = __commonJS({
|
|
|
2608
2608
|
}
|
|
2609
2609
|
async function loadConfig8(configPath) {
|
|
2610
2610
|
const fullPath = getConfigPath(configPath);
|
|
2611
|
-
if (
|
|
2611
|
+
if (fs22.existsSync(fullPath)) {
|
|
2612
2612
|
try {
|
|
2613
|
-
const content =
|
|
2613
|
+
const content = fs22.readFileSync(fullPath, "utf8");
|
|
2614
2614
|
const config = JSON.parse(content);
|
|
2615
2615
|
return config;
|
|
2616
2616
|
} catch (error) {
|
|
@@ -2620,9 +2620,9 @@ var require_auth = __commonJS({
|
|
|
2620
2620
|
if (process.env.EPISODA_MODE === "cloud" && process.env.EPISODA_WORKSPACE) {
|
|
2621
2621
|
const homeDir = process.env.HOME || require("os").homedir();
|
|
2622
2622
|
const workspaceConfigPath = require("path").join(homeDir, "episoda", process.env.EPISODA_WORKSPACE, ".episoda", "config.json");
|
|
2623
|
-
if (
|
|
2623
|
+
if (fs22.existsSync(workspaceConfigPath)) {
|
|
2624
2624
|
try {
|
|
2625
|
-
const content =
|
|
2625
|
+
const content = fs22.readFileSync(workspaceConfigPath, "utf8");
|
|
2626
2626
|
const workspaceConfig = JSON.parse(content);
|
|
2627
2627
|
return {
|
|
2628
2628
|
access_token: process.env.EPISODA_ACCESS_TOKEN || workspaceConfig.accessToken,
|
|
@@ -2669,7 +2669,7 @@ var require_auth = __commonJS({
|
|
|
2669
2669
|
ensureConfigDir(fullPath);
|
|
2670
2670
|
try {
|
|
2671
2671
|
const content = JSON.stringify(config, null, 2);
|
|
2672
|
-
|
|
2672
|
+
fs22.writeFileSync(fullPath, content, { mode: 384 });
|
|
2673
2673
|
} catch (error) {
|
|
2674
2674
|
throw new Error(`Failed to save config: ${error instanceof Error ? error.message : String(error)}`);
|
|
2675
2675
|
}
|
|
@@ -2786,7 +2786,7 @@ var require_package = __commonJS({
|
|
|
2786
2786
|
"package.json"(exports2, module2) {
|
|
2787
2787
|
module2.exports = {
|
|
2788
2788
|
name: "episoda",
|
|
2789
|
-
version: "0.2.
|
|
2789
|
+
version: "0.2.95",
|
|
2790
2790
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2791
2791
|
main: "dist/index.js",
|
|
2792
2792
|
types: "dist/index.d.ts",
|
|
@@ -2821,7 +2821,8 @@ var require_package = __commonJS({
|
|
|
2821
2821
|
},
|
|
2822
2822
|
optionalDependencies: {
|
|
2823
2823
|
"@anthropic-ai/claude-code": "^2.0.0",
|
|
2824
|
-
"@openai/codex": "^0.86.0"
|
|
2824
|
+
"@openai/codex": "^0.86.0",
|
|
2825
|
+
"@modelcontextprotocol/server-github": "^0.6.0"
|
|
2825
2826
|
},
|
|
2826
2827
|
devDependencies: {
|
|
2827
2828
|
"@episoda/core": "*",
|
|
@@ -3088,10 +3089,10 @@ var IPCServer = class {
|
|
|
3088
3089
|
this.server = net.createServer((socket) => {
|
|
3089
3090
|
this.handleConnection(socket);
|
|
3090
3091
|
});
|
|
3091
|
-
return new Promise((
|
|
3092
|
+
return new Promise((resolve4, reject) => {
|
|
3092
3093
|
this.server.listen(socketPath, () => {
|
|
3093
3094
|
fs3.chmodSync(socketPath, 384);
|
|
3094
|
-
|
|
3095
|
+
resolve4();
|
|
3095
3096
|
});
|
|
3096
3097
|
this.server.on("error", reject);
|
|
3097
3098
|
});
|
|
@@ -3102,12 +3103,12 @@ var IPCServer = class {
|
|
|
3102
3103
|
async stop() {
|
|
3103
3104
|
if (!this.server) return;
|
|
3104
3105
|
const socketPath = getSocketPath();
|
|
3105
|
-
return new Promise((
|
|
3106
|
+
return new Promise((resolve4) => {
|
|
3106
3107
|
this.server.close(() => {
|
|
3107
3108
|
if (fs3.existsSync(socketPath)) {
|
|
3108
3109
|
fs3.unlinkSync(socketPath);
|
|
3109
3110
|
}
|
|
3110
|
-
|
|
3111
|
+
resolve4();
|
|
3111
3112
|
});
|
|
3112
3113
|
});
|
|
3113
3114
|
}
|
|
@@ -3240,18 +3241,171 @@ function performBackgroundUpdate(targetVersion) {
|
|
|
3240
3241
|
}
|
|
3241
3242
|
|
|
3242
3243
|
// src/daemon/handlers/file-handlers.ts
|
|
3243
|
-
var
|
|
3244
|
-
var
|
|
3244
|
+
var fs5 = __toESM(require("fs"));
|
|
3245
|
+
var path6 = __toESM(require("path"));
|
|
3245
3246
|
var readline = __toESM(require("readline"));
|
|
3246
|
-
|
|
3247
|
-
|
|
3247
|
+
|
|
3248
|
+
// src/daemon/permissions/path-classifier.ts
|
|
3249
|
+
var path5 = __toESM(require("path"));
|
|
3250
|
+
var fs4 = __toESM(require("fs"));
|
|
3251
|
+
function classifyPath(targetPath, options) {
|
|
3252
|
+
const { workspaceRoot, projectRoot } = options;
|
|
3253
|
+
const normalizedWorkspace = path5.resolve(workspaceRoot);
|
|
3254
|
+
const normalizedProject = path5.resolve(projectRoot);
|
|
3255
|
+
const resolvedPath = path5.isAbsolute(targetPath) ? path5.resolve(targetPath) : path5.resolve(projectRoot, targetPath);
|
|
3256
|
+
const artifactsDir = path5.join(normalizedWorkspace, "artifacts");
|
|
3257
|
+
if (resolvedPath.startsWith(artifactsDir + path5.sep) || resolvedPath === artifactsDir) {
|
|
3258
|
+
return {
|
|
3259
|
+
classification: "artifacts",
|
|
3260
|
+
resolvedPath
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
if (!resolvedPath.startsWith(normalizedProject + path5.sep) && resolvedPath !== normalizedProject) {
|
|
3264
|
+
if (resolvedPath.startsWith(normalizedWorkspace + path5.sep)) {
|
|
3265
|
+
return {
|
|
3266
|
+
classification: "unknown",
|
|
3267
|
+
resolvedPath
|
|
3268
|
+
};
|
|
3269
|
+
}
|
|
3270
|
+
return {
|
|
3271
|
+
classification: "unknown",
|
|
3272
|
+
resolvedPath
|
|
3273
|
+
};
|
|
3274
|
+
}
|
|
3275
|
+
const relativePath = path5.relative(normalizedProject, resolvedPath);
|
|
3276
|
+
const pathParts = relativePath.split(path5.sep);
|
|
3277
|
+
if (pathParts[0] === ".bare") {
|
|
3278
|
+
return {
|
|
3279
|
+
classification: "bare_repo",
|
|
3280
|
+
resolvedPath
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
if (pathParts[0] === ".episoda") {
|
|
3284
|
+
return {
|
|
3285
|
+
classification: "config",
|
|
3286
|
+
resolvedPath
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
const firstPart = pathParts[0];
|
|
3290
|
+
if (firstPart && isModuleUidPattern(firstPart)) {
|
|
3291
|
+
const worktreeDir = path5.join(normalizedProject, firstPart);
|
|
3292
|
+
const gitFile = path5.join(worktreeDir, ".git");
|
|
3293
|
+
const worktreeExists = fs4.existsSync(gitFile) && fs4.statSync(gitFile).isFile();
|
|
3294
|
+
return {
|
|
3295
|
+
classification: "worktree",
|
|
3296
|
+
moduleUid: firstPart,
|
|
3297
|
+
worktreeExists,
|
|
3298
|
+
resolvedPath
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
return {
|
|
3302
|
+
classification: "project_root",
|
|
3303
|
+
resolvedPath
|
|
3304
|
+
};
|
|
3305
|
+
}
|
|
3306
|
+
function isModuleUidPattern(str) {
|
|
3307
|
+
return /^EP\d+$/.test(str);
|
|
3308
|
+
}
|
|
3309
|
+
function deriveWorkspaceRoot(projectPath) {
|
|
3310
|
+
return path5.dirname(path5.resolve(projectPath));
|
|
3311
|
+
}
|
|
3312
|
+
function validateWorkspacePath(filePath, projectPath) {
|
|
3248
3313
|
const normalizedProjectPath = path5.resolve(projectPath);
|
|
3314
|
+
const workspaceRoot = deriveWorkspaceRoot(projectPath);
|
|
3315
|
+
const normalizedWorkspace = path5.resolve(workspaceRoot);
|
|
3316
|
+
const artifactsDir = path5.join(normalizedWorkspace, "artifacts");
|
|
3249
3317
|
const absolutePath = path5.isAbsolute(filePath) ? path5.resolve(filePath) : path5.resolve(projectPath, filePath);
|
|
3250
3318
|
const normalizedPath = path5.normalize(absolutePath);
|
|
3251
|
-
if (
|
|
3252
|
-
return
|
|
3319
|
+
if (normalizedPath.startsWith(normalizedProjectPath + path5.sep) || normalizedPath === normalizedProjectPath) {
|
|
3320
|
+
return normalizedPath;
|
|
3253
3321
|
}
|
|
3254
|
-
|
|
3322
|
+
if (normalizedPath.startsWith(artifactsDir + path5.sep) || normalizedPath === artifactsDir) {
|
|
3323
|
+
return normalizedPath;
|
|
3324
|
+
}
|
|
3325
|
+
return null;
|
|
3326
|
+
}
|
|
3327
|
+
|
|
3328
|
+
// src/daemon/permissions/write-permission.ts
|
|
3329
|
+
function checkWritePermission(targetPath, projectPath) {
|
|
3330
|
+
const workspaceRoot = deriveWorkspaceRoot(projectPath);
|
|
3331
|
+
const classification = classifyPath(targetPath, {
|
|
3332
|
+
workspaceRoot,
|
|
3333
|
+
projectRoot: projectPath
|
|
3334
|
+
});
|
|
3335
|
+
return evaluatePermission(classification);
|
|
3336
|
+
}
|
|
3337
|
+
function evaluatePermission(classification) {
|
|
3338
|
+
const { classification: pathType, moduleUid, worktreeExists, resolvedPath } = classification;
|
|
3339
|
+
switch (pathType) {
|
|
3340
|
+
case "artifacts":
|
|
3341
|
+
return {
|
|
3342
|
+
allowed: true,
|
|
3343
|
+
classification: pathType
|
|
3344
|
+
};
|
|
3345
|
+
case "bare_repo":
|
|
3346
|
+
return {
|
|
3347
|
+
allowed: false,
|
|
3348
|
+
reason: "Cannot write to .bare/ directory - this is the protected git repository",
|
|
3349
|
+
classification: pathType
|
|
3350
|
+
};
|
|
3351
|
+
case "config":
|
|
3352
|
+
return {
|
|
3353
|
+
allowed: false,
|
|
3354
|
+
reason: "Cannot write to .episoda/ directory - this is protected configuration",
|
|
3355
|
+
classification: pathType
|
|
3356
|
+
};
|
|
3357
|
+
case "worktree":
|
|
3358
|
+
if (worktreeExists) {
|
|
3359
|
+
return {
|
|
3360
|
+
allowed: true,
|
|
3361
|
+
classification: pathType,
|
|
3362
|
+
moduleUid
|
|
3363
|
+
};
|
|
3364
|
+
} else {
|
|
3365
|
+
return {
|
|
3366
|
+
allowed: false,
|
|
3367
|
+
reason: `Cannot write to ${moduleUid}/ - worktree does not exist. Module must be in 'doing' or 'review' state with an active worktree.`,
|
|
3368
|
+
classification: pathType,
|
|
3369
|
+
moduleUid
|
|
3370
|
+
};
|
|
3371
|
+
}
|
|
3372
|
+
case "project_root":
|
|
3373
|
+
return {
|
|
3374
|
+
allowed: false,
|
|
3375
|
+
reason: "Cannot write directly to project root. Files must be in a worktree directory or artifacts/",
|
|
3376
|
+
classification: pathType
|
|
3377
|
+
};
|
|
3378
|
+
case "unknown":
|
|
3379
|
+
default:
|
|
3380
|
+
return {
|
|
3381
|
+
allowed: false,
|
|
3382
|
+
reason: "Path is outside the allowed project directory structure",
|
|
3383
|
+
classification: pathType
|
|
3384
|
+
};
|
|
3385
|
+
}
|
|
3386
|
+
}
|
|
3387
|
+
function formatPermissionDenied(result) {
|
|
3388
|
+
if (result.allowed) {
|
|
3389
|
+
return "Write allowed";
|
|
3390
|
+
}
|
|
3391
|
+
const baseMessage = result.reason || "Write permission denied";
|
|
3392
|
+
switch (result.classification) {
|
|
3393
|
+
case "worktree":
|
|
3394
|
+
return `${baseMessage} Use the platform to move the module to 'doing' state first.`;
|
|
3395
|
+
case "bare_repo":
|
|
3396
|
+
case "config":
|
|
3397
|
+
return `${baseMessage} These directories are managed by the system.`;
|
|
3398
|
+
case "project_root":
|
|
3399
|
+
return `${baseMessage} Create files within your worktree (e.g., ${result.moduleUid || "EPxxx"}/src/...) or use artifacts/ for outputs.`;
|
|
3400
|
+
default:
|
|
3401
|
+
return baseMessage;
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
// src/daemon/handlers/file-handlers.ts
|
|
3406
|
+
var DEFAULT_MAX_FILE_SIZE = 20 * 1024 * 1024;
|
|
3407
|
+
function validatePath(filePath, projectPath) {
|
|
3408
|
+
return validateWorkspacePath(filePath, projectPath);
|
|
3255
3409
|
}
|
|
3256
3410
|
async function handleFileRead(command, projectPath) {
|
|
3257
3411
|
const { path: filePath, encoding = "base64", maxSize = DEFAULT_MAX_FILE_SIZE } = command;
|
|
@@ -3263,13 +3417,13 @@ async function handleFileRead(command, projectPath) {
|
|
|
3263
3417
|
};
|
|
3264
3418
|
}
|
|
3265
3419
|
try {
|
|
3266
|
-
if (!
|
|
3420
|
+
if (!fs5.existsSync(validPath)) {
|
|
3267
3421
|
return {
|
|
3268
3422
|
success: false,
|
|
3269
3423
|
error: "File not found"
|
|
3270
3424
|
};
|
|
3271
3425
|
}
|
|
3272
|
-
const stats =
|
|
3426
|
+
const stats = fs5.statSync(validPath);
|
|
3273
3427
|
if (stats.isDirectory()) {
|
|
3274
3428
|
return {
|
|
3275
3429
|
success: false,
|
|
@@ -3282,7 +3436,7 @@ async function handleFileRead(command, projectPath) {
|
|
|
3282
3436
|
error: `File too large: ${stats.size} bytes exceeds limit of ${maxSize} bytes`
|
|
3283
3437
|
};
|
|
3284
3438
|
}
|
|
3285
|
-
const buffer =
|
|
3439
|
+
const buffer = fs5.readFileSync(validPath);
|
|
3286
3440
|
let content;
|
|
3287
3441
|
if (encoding === "base64") {
|
|
3288
3442
|
content = buffer.toString("base64");
|
|
@@ -3313,11 +3467,18 @@ async function handleFileWrite(command, projectPath) {
|
|
|
3313
3467
|
error: "Invalid path: directory traversal not allowed"
|
|
3314
3468
|
};
|
|
3315
3469
|
}
|
|
3470
|
+
const permission = checkWritePermission(validPath, projectPath);
|
|
3471
|
+
if (!permission.allowed) {
|
|
3472
|
+
return {
|
|
3473
|
+
success: false,
|
|
3474
|
+
error: formatPermissionDenied(permission)
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3316
3477
|
try {
|
|
3317
3478
|
if (createDirs) {
|
|
3318
|
-
const dirPath =
|
|
3319
|
-
if (!
|
|
3320
|
-
|
|
3479
|
+
const dirPath = path6.dirname(validPath);
|
|
3480
|
+
if (!fs5.existsSync(dirPath)) {
|
|
3481
|
+
fs5.mkdirSync(dirPath, { recursive: true });
|
|
3321
3482
|
}
|
|
3322
3483
|
}
|
|
3323
3484
|
let buffer;
|
|
@@ -3327,8 +3488,8 @@ async function handleFileWrite(command, projectPath) {
|
|
|
3327
3488
|
buffer = Buffer.from(content, "utf8");
|
|
3328
3489
|
}
|
|
3329
3490
|
const tempPath = `${validPath}.tmp.${Date.now()}`;
|
|
3330
|
-
|
|
3331
|
-
|
|
3491
|
+
fs5.writeFileSync(tempPath, buffer);
|
|
3492
|
+
fs5.renameSync(tempPath, validPath);
|
|
3332
3493
|
return {
|
|
3333
3494
|
success: true,
|
|
3334
3495
|
bytesWritten: buffer.length
|
|
@@ -3353,13 +3514,13 @@ async function handleFileList(command, projectPath) {
|
|
|
3353
3514
|
};
|
|
3354
3515
|
}
|
|
3355
3516
|
try {
|
|
3356
|
-
if (!
|
|
3517
|
+
if (!fs5.existsSync(validPath)) {
|
|
3357
3518
|
return {
|
|
3358
3519
|
success: false,
|
|
3359
3520
|
error: "Directory not found"
|
|
3360
3521
|
};
|
|
3361
3522
|
}
|
|
3362
|
-
const stats =
|
|
3523
|
+
const stats = fs5.statSync(validPath);
|
|
3363
3524
|
if (!stats.isDirectory()) {
|
|
3364
3525
|
return {
|
|
3365
3526
|
success: false,
|
|
@@ -3370,11 +3531,11 @@ async function handleFileList(command, projectPath) {
|
|
|
3370
3531
|
if (recursive) {
|
|
3371
3532
|
await listDirectoryRecursive(validPath, validPath, entries, includeHidden);
|
|
3372
3533
|
} else {
|
|
3373
|
-
const dirEntries = await
|
|
3534
|
+
const dirEntries = await fs5.promises.readdir(validPath, { withFileTypes: true });
|
|
3374
3535
|
for (const entry of dirEntries) {
|
|
3375
3536
|
if (!includeHidden && entry.name.startsWith(".")) continue;
|
|
3376
|
-
const entryPath =
|
|
3377
|
-
const entryStats = await
|
|
3537
|
+
const entryPath = path6.join(validPath, entry.name);
|
|
3538
|
+
const entryStats = await fs5.promises.stat(entryPath);
|
|
3378
3539
|
entries.push({
|
|
3379
3540
|
name: entry.name,
|
|
3380
3541
|
type: entry.isDirectory() ? "directory" : "file",
|
|
@@ -3396,13 +3557,13 @@ async function handleFileList(command, projectPath) {
|
|
|
3396
3557
|
}
|
|
3397
3558
|
}
|
|
3398
3559
|
async function listDirectoryRecursive(basePath, currentPath, entries, includeHidden) {
|
|
3399
|
-
const dirEntries = await
|
|
3560
|
+
const dirEntries = await fs5.promises.readdir(currentPath, { withFileTypes: true });
|
|
3400
3561
|
for (const entry of dirEntries) {
|
|
3401
3562
|
if (!includeHidden && entry.name.startsWith(".")) continue;
|
|
3402
|
-
const entryPath =
|
|
3403
|
-
const relativePath =
|
|
3563
|
+
const entryPath = path6.join(currentPath, entry.name);
|
|
3564
|
+
const relativePath = path6.relative(basePath, entryPath);
|
|
3404
3565
|
try {
|
|
3405
|
-
const entryStats = await
|
|
3566
|
+
const entryStats = await fs5.promises.stat(entryPath);
|
|
3406
3567
|
entries.push({
|
|
3407
3568
|
name: relativePath,
|
|
3408
3569
|
type: entry.isDirectory() ? "directory" : "file",
|
|
@@ -3426,7 +3587,7 @@ async function handleFileSearch(command, projectPath) {
|
|
|
3426
3587
|
};
|
|
3427
3588
|
}
|
|
3428
3589
|
try {
|
|
3429
|
-
if (!
|
|
3590
|
+
if (!fs5.existsSync(validPath)) {
|
|
3430
3591
|
return {
|
|
3431
3592
|
success: false,
|
|
3432
3593
|
error: "Directory not found"
|
|
@@ -3516,12 +3677,12 @@ function findClosingBracket(pattern, start) {
|
|
|
3516
3677
|
}
|
|
3517
3678
|
async function searchFilesRecursive(basePath, currentPath, pattern, files, maxResults) {
|
|
3518
3679
|
if (files.length >= maxResults) return;
|
|
3519
|
-
const entries = await
|
|
3680
|
+
const entries = await fs5.promises.readdir(currentPath, { withFileTypes: true });
|
|
3520
3681
|
for (const entry of entries) {
|
|
3521
3682
|
if (files.length >= maxResults) return;
|
|
3522
3683
|
if (entry.name.startsWith(".")) continue;
|
|
3523
|
-
const entryPath =
|
|
3524
|
-
const relativePath =
|
|
3684
|
+
const entryPath = path6.join(currentPath, entry.name);
|
|
3685
|
+
const relativePath = path6.relative(basePath, entryPath);
|
|
3525
3686
|
try {
|
|
3526
3687
|
if (entry.isDirectory()) {
|
|
3527
3688
|
await searchFilesRecursive(basePath, entryPath, pattern, files, maxResults);
|
|
@@ -3549,7 +3710,7 @@ async function handleFileGrep(command, projectPath) {
|
|
|
3549
3710
|
};
|
|
3550
3711
|
}
|
|
3551
3712
|
try {
|
|
3552
|
-
if (!
|
|
3713
|
+
if (!fs5.existsSync(validPath)) {
|
|
3553
3714
|
return {
|
|
3554
3715
|
success: false,
|
|
3555
3716
|
error: "Path not found"
|
|
@@ -3558,7 +3719,7 @@ async function handleFileGrep(command, projectPath) {
|
|
|
3558
3719
|
const matches = [];
|
|
3559
3720
|
const searchRegex = new RegExp(pattern, caseSensitive ? "" : "i");
|
|
3560
3721
|
const fileGlobRegex = globToRegex(filePattern);
|
|
3561
|
-
const stats =
|
|
3722
|
+
const stats = fs5.statSync(validPath);
|
|
3562
3723
|
if (stats.isFile()) {
|
|
3563
3724
|
await grepFile(validPath, validPath, searchRegex, matches, effectiveMaxResults);
|
|
3564
3725
|
} else {
|
|
@@ -3579,8 +3740,8 @@ async function handleFileGrep(command, projectPath) {
|
|
|
3579
3740
|
}
|
|
3580
3741
|
async function grepFile(basePath, filePath, pattern, matches, maxResults) {
|
|
3581
3742
|
if (matches.length >= maxResults) return;
|
|
3582
|
-
const relativePath =
|
|
3583
|
-
const fileStream =
|
|
3743
|
+
const relativePath = path6.relative(basePath, filePath);
|
|
3744
|
+
const fileStream = fs5.createReadStream(filePath, { encoding: "utf8" });
|
|
3584
3745
|
const rl = readline.createInterface({
|
|
3585
3746
|
input: fileStream,
|
|
3586
3747
|
crlfDelay: Infinity
|
|
@@ -3594,7 +3755,7 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
|
|
|
3594
3755
|
}
|
|
3595
3756
|
if (pattern.test(line)) {
|
|
3596
3757
|
matches.push({
|
|
3597
|
-
file: relativePath ||
|
|
3758
|
+
file: relativePath || path6.basename(filePath),
|
|
3598
3759
|
line: lineNumber,
|
|
3599
3760
|
content: line.slice(0, 500)
|
|
3600
3761
|
// Truncate long lines
|
|
@@ -3604,11 +3765,11 @@ async function grepFile(basePath, filePath, pattern, matches, maxResults) {
|
|
|
3604
3765
|
}
|
|
3605
3766
|
async function grepDirectoryRecursive(basePath, currentPath, searchPattern, filePattern, matches, maxResults) {
|
|
3606
3767
|
if (matches.length >= maxResults) return;
|
|
3607
|
-
const entries = await
|
|
3768
|
+
const entries = await fs5.promises.readdir(currentPath, { withFileTypes: true });
|
|
3608
3769
|
for (const entry of entries) {
|
|
3609
3770
|
if (matches.length >= maxResults) return;
|
|
3610
3771
|
if (entry.name.startsWith(".")) continue;
|
|
3611
|
-
const entryPath =
|
|
3772
|
+
const entryPath = path6.join(currentPath, entry.name);
|
|
3612
3773
|
try {
|
|
3613
3774
|
if (entry.isDirectory()) {
|
|
3614
3775
|
await grepDirectoryRecursive(basePath, entryPath, searchPattern, filePattern, matches, maxResults);
|
|
@@ -3628,14 +3789,21 @@ async function handleFileEdit(command, projectPath) {
|
|
|
3628
3789
|
error: "Invalid path: directory traversal not allowed"
|
|
3629
3790
|
};
|
|
3630
3791
|
}
|
|
3792
|
+
const permission = checkWritePermission(validPath, projectPath);
|
|
3793
|
+
if (!permission.allowed) {
|
|
3794
|
+
return {
|
|
3795
|
+
success: false,
|
|
3796
|
+
error: formatPermissionDenied(permission)
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3631
3799
|
try {
|
|
3632
|
-
if (!
|
|
3800
|
+
if (!fs5.existsSync(validPath)) {
|
|
3633
3801
|
return {
|
|
3634
3802
|
success: false,
|
|
3635
3803
|
error: "File not found"
|
|
3636
3804
|
};
|
|
3637
3805
|
}
|
|
3638
|
-
const stats =
|
|
3806
|
+
const stats = fs5.statSync(validPath);
|
|
3639
3807
|
if (stats.isDirectory()) {
|
|
3640
3808
|
return {
|
|
3641
3809
|
success: false,
|
|
@@ -3649,7 +3817,7 @@ async function handleFileEdit(command, projectPath) {
|
|
|
3649
3817
|
error: `File too large for edit operation: ${stats.size} bytes exceeds limit of ${MAX_EDIT_SIZE} bytes (10MB). Use write-file for large files.`
|
|
3650
3818
|
};
|
|
3651
3819
|
}
|
|
3652
|
-
const content =
|
|
3820
|
+
const content = fs5.readFileSync(validPath, "utf8");
|
|
3653
3821
|
const occurrences = content.split(oldString).length - 1;
|
|
3654
3822
|
if (occurrences === 0) {
|
|
3655
3823
|
return {
|
|
@@ -3666,8 +3834,8 @@ async function handleFileEdit(command, projectPath) {
|
|
|
3666
3834
|
const newContent = replaceAll ? content.split(oldString).join(newString) : content.replace(oldString, newString);
|
|
3667
3835
|
const replacements = replaceAll ? occurrences : 1;
|
|
3668
3836
|
const tempPath = `${validPath}.tmp.${Date.now()}`;
|
|
3669
|
-
|
|
3670
|
-
|
|
3837
|
+
fs5.writeFileSync(tempPath, newContent, "utf8");
|
|
3838
|
+
fs5.renameSync(tempPath, validPath);
|
|
3671
3839
|
const newSize = Buffer.byteLength(newContent, "utf8");
|
|
3672
3840
|
return {
|
|
3673
3841
|
success: true,
|
|
@@ -3692,21 +3860,28 @@ async function handleFileDelete(command, projectPath) {
|
|
|
3692
3860
|
error: "Invalid path: directory traversal not allowed"
|
|
3693
3861
|
};
|
|
3694
3862
|
}
|
|
3695
|
-
const normalizedProjectPath =
|
|
3863
|
+
const normalizedProjectPath = path6.resolve(projectPath);
|
|
3696
3864
|
if (validPath === normalizedProjectPath) {
|
|
3697
3865
|
return {
|
|
3698
3866
|
success: false,
|
|
3699
3867
|
error: "Cannot delete project root directory"
|
|
3700
3868
|
};
|
|
3701
3869
|
}
|
|
3870
|
+
const permission = checkWritePermission(validPath, projectPath);
|
|
3871
|
+
if (!permission.allowed) {
|
|
3872
|
+
return {
|
|
3873
|
+
success: false,
|
|
3874
|
+
error: formatPermissionDenied(permission)
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3702
3877
|
try {
|
|
3703
|
-
if (!
|
|
3878
|
+
if (!fs5.existsSync(validPath)) {
|
|
3704
3879
|
return {
|
|
3705
3880
|
success: false,
|
|
3706
3881
|
error: "Path not found"
|
|
3707
3882
|
};
|
|
3708
3883
|
}
|
|
3709
|
-
const stats =
|
|
3884
|
+
const stats = fs5.statSync(validPath);
|
|
3710
3885
|
const isDirectory = stats.isDirectory();
|
|
3711
3886
|
if (isDirectory && !recursive) {
|
|
3712
3887
|
return {
|
|
@@ -3715,9 +3890,9 @@ async function handleFileDelete(command, projectPath) {
|
|
|
3715
3890
|
};
|
|
3716
3891
|
}
|
|
3717
3892
|
if (isDirectory) {
|
|
3718
|
-
|
|
3893
|
+
fs5.rmSync(validPath, { recursive: true, force: true });
|
|
3719
3894
|
} else {
|
|
3720
|
-
|
|
3895
|
+
fs5.unlinkSync(validPath);
|
|
3721
3896
|
}
|
|
3722
3897
|
return {
|
|
3723
3898
|
success: true,
|
|
@@ -3742,9 +3917,16 @@ async function handleFileMkdir(command, projectPath) {
|
|
|
3742
3917
|
error: "Invalid path: directory traversal not allowed"
|
|
3743
3918
|
};
|
|
3744
3919
|
}
|
|
3920
|
+
const permission = checkWritePermission(validPath, projectPath);
|
|
3921
|
+
if (!permission.allowed) {
|
|
3922
|
+
return {
|
|
3923
|
+
success: false,
|
|
3924
|
+
error: formatPermissionDenied(permission)
|
|
3925
|
+
};
|
|
3926
|
+
}
|
|
3745
3927
|
try {
|
|
3746
|
-
if (
|
|
3747
|
-
const stats =
|
|
3928
|
+
if (fs5.existsSync(validPath)) {
|
|
3929
|
+
const stats = fs5.statSync(validPath);
|
|
3748
3930
|
if (stats.isDirectory()) {
|
|
3749
3931
|
return {
|
|
3750
3932
|
success: true,
|
|
@@ -3759,7 +3941,7 @@ async function handleFileMkdir(command, projectPath) {
|
|
|
3759
3941
|
}
|
|
3760
3942
|
}
|
|
3761
3943
|
const modeNum = parseInt(mode, 8);
|
|
3762
|
-
|
|
3944
|
+
fs5.mkdirSync(validPath, { recursive: true, mode: modeNum });
|
|
3763
3945
|
return {
|
|
3764
3946
|
success: true,
|
|
3765
3947
|
created: true
|
|
@@ -3786,7 +3968,7 @@ async function handleExec(command, projectPath) {
|
|
|
3786
3968
|
env = {}
|
|
3787
3969
|
} = command;
|
|
3788
3970
|
const effectiveTimeout = Math.min(Math.max(timeout, 1e3), MAX_TIMEOUT);
|
|
3789
|
-
return new Promise((
|
|
3971
|
+
return new Promise((resolve4) => {
|
|
3790
3972
|
let stdout = "";
|
|
3791
3973
|
let stderr = "";
|
|
3792
3974
|
let timedOut = false;
|
|
@@ -3794,7 +3976,7 @@ async function handleExec(command, projectPath) {
|
|
|
3794
3976
|
const done = (result) => {
|
|
3795
3977
|
if (resolved) return;
|
|
3796
3978
|
resolved = true;
|
|
3797
|
-
|
|
3979
|
+
resolve4(result);
|
|
3798
3980
|
};
|
|
3799
3981
|
try {
|
|
3800
3982
|
const proc = (0, import_child_process3.spawn)(cmd, {
|
|
@@ -3858,14 +4040,14 @@ async function handleExec(command, projectPath) {
|
|
|
3858
4040
|
}
|
|
3859
4041
|
|
|
3860
4042
|
// src/daemon/handlers/worktree-handlers.ts
|
|
3861
|
-
var
|
|
3862
|
-
var
|
|
4043
|
+
var path15 = __toESM(require("path"));
|
|
4044
|
+
var fs14 = __toESM(require("fs"));
|
|
3863
4045
|
var import_child_process8 = require("child_process");
|
|
3864
4046
|
var import_util = require("util");
|
|
3865
4047
|
|
|
3866
4048
|
// src/daemon/worktree-manager.ts
|
|
3867
|
-
var
|
|
3868
|
-
var
|
|
4049
|
+
var fs6 = __toESM(require("fs"));
|
|
4050
|
+
var path7 = __toESM(require("path"));
|
|
3869
4051
|
var import_core6 = __toESM(require_dist());
|
|
3870
4052
|
function validateModuleUid(moduleUid) {
|
|
3871
4053
|
if (!moduleUid || typeof moduleUid !== "string" || !moduleUid.trim()) {
|
|
@@ -3889,8 +4071,8 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3889
4071
|
// ============================================================
|
|
3890
4072
|
this.lockPath = "";
|
|
3891
4073
|
this.projectRoot = projectRoot;
|
|
3892
|
-
this.bareRepoPath =
|
|
3893
|
-
this.configPath =
|
|
4074
|
+
this.bareRepoPath = path7.join(projectRoot, ".bare");
|
|
4075
|
+
this.configPath = path7.join(projectRoot, ".episoda", "config.json");
|
|
3894
4076
|
this.gitExecutor = new import_core6.GitExecutor();
|
|
3895
4077
|
}
|
|
3896
4078
|
/**
|
|
@@ -3901,13 +4083,13 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3901
4083
|
*/
|
|
3902
4084
|
async initialize() {
|
|
3903
4085
|
const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
|
|
3904
|
-
if (!
|
|
4086
|
+
if (!fs6.existsSync(this.bareRepoPath)) {
|
|
3905
4087
|
if (debug) {
|
|
3906
4088
|
console.log(`[WorktreeManager] initialize: .bare not found at ${this.bareRepoPath}`);
|
|
3907
4089
|
}
|
|
3908
4090
|
return false;
|
|
3909
4091
|
}
|
|
3910
|
-
if (!
|
|
4092
|
+
if (!fs6.existsSync(this.configPath)) {
|
|
3911
4093
|
if (debug) {
|
|
3912
4094
|
console.log(`[WorktreeManager] initialize: config not found at ${this.configPath}`);
|
|
3913
4095
|
}
|
|
@@ -3966,8 +4148,8 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3966
4148
|
*/
|
|
3967
4149
|
static async createProject(projectRoot, repoUrl, projectId, workspaceSlug, projectSlug) {
|
|
3968
4150
|
const manager = new _WorktreeManager(projectRoot);
|
|
3969
|
-
const episodaDir =
|
|
3970
|
-
|
|
4151
|
+
const episodaDir = path7.join(projectRoot, ".episoda");
|
|
4152
|
+
fs6.mkdirSync(episodaDir, { recursive: true });
|
|
3971
4153
|
const cloneResult = await manager.gitExecutor.execute({
|
|
3972
4154
|
action: "clone_bare",
|
|
3973
4155
|
url: repoUrl,
|
|
@@ -3998,7 +4180,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
3998
4180
|
error: `Invalid module UID: "${moduleUid}" - contains disallowed characters`
|
|
3999
4181
|
};
|
|
4000
4182
|
}
|
|
4001
|
-
const worktreePath =
|
|
4183
|
+
const worktreePath = path7.join(this.projectRoot, moduleUid);
|
|
4002
4184
|
const lockAcquired = await this.acquireLock();
|
|
4003
4185
|
if (!lockAcquired) {
|
|
4004
4186
|
return {
|
|
@@ -4189,7 +4371,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4189
4371
|
let prunedCount = 0;
|
|
4190
4372
|
await this.updateConfigSafe((config) => {
|
|
4191
4373
|
const initialCount = config.worktrees.length;
|
|
4192
|
-
config.worktrees = config.worktrees.filter((w) =>
|
|
4374
|
+
config.worktrees = config.worktrees.filter((w) => fs6.existsSync(w.worktreePath));
|
|
4193
4375
|
prunedCount = initialCount - config.worktrees.length;
|
|
4194
4376
|
return config;
|
|
4195
4377
|
});
|
|
@@ -4270,25 +4452,25 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4270
4452
|
const retryInterval = 50;
|
|
4271
4453
|
while (Date.now() - startTime < timeoutMs) {
|
|
4272
4454
|
try {
|
|
4273
|
-
|
|
4455
|
+
fs6.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
4274
4456
|
return true;
|
|
4275
4457
|
} catch (err) {
|
|
4276
4458
|
if (err.code === "EEXIST") {
|
|
4277
4459
|
try {
|
|
4278
|
-
const stats =
|
|
4460
|
+
const stats = fs6.statSync(lockPath);
|
|
4279
4461
|
const lockAge = Date.now() - stats.mtimeMs;
|
|
4280
4462
|
if (lockAge > 3e4) {
|
|
4281
4463
|
try {
|
|
4282
|
-
const lockContent =
|
|
4464
|
+
const lockContent = fs6.readFileSync(lockPath, "utf-8").trim();
|
|
4283
4465
|
const lockPid = parseInt(lockContent, 10);
|
|
4284
4466
|
if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
|
|
4285
|
-
await new Promise((
|
|
4467
|
+
await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
|
|
4286
4468
|
continue;
|
|
4287
4469
|
}
|
|
4288
4470
|
} catch {
|
|
4289
4471
|
}
|
|
4290
4472
|
try {
|
|
4291
|
-
|
|
4473
|
+
fs6.unlinkSync(lockPath);
|
|
4292
4474
|
} catch {
|
|
4293
4475
|
}
|
|
4294
4476
|
continue;
|
|
@@ -4296,7 +4478,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4296
4478
|
} catch {
|
|
4297
4479
|
continue;
|
|
4298
4480
|
}
|
|
4299
|
-
await new Promise((
|
|
4481
|
+
await new Promise((resolve4) => setTimeout(resolve4, retryInterval));
|
|
4300
4482
|
continue;
|
|
4301
4483
|
}
|
|
4302
4484
|
throw err;
|
|
@@ -4309,7 +4491,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4309
4491
|
*/
|
|
4310
4492
|
releaseLock() {
|
|
4311
4493
|
try {
|
|
4312
|
-
|
|
4494
|
+
fs6.unlinkSync(this.getLockPath());
|
|
4313
4495
|
} catch {
|
|
4314
4496
|
}
|
|
4315
4497
|
}
|
|
@@ -4333,11 +4515,11 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4333
4515
|
// Turborepo cache
|
|
4334
4516
|
];
|
|
4335
4517
|
for (const cacheDir of cacheDirs) {
|
|
4336
|
-
const cachePath =
|
|
4518
|
+
const cachePath = path7.join(worktreePath, cacheDir);
|
|
4337
4519
|
try {
|
|
4338
|
-
if (
|
|
4520
|
+
if (fs6.existsSync(cachePath)) {
|
|
4339
4521
|
console.log(`[WorktreeManager] EP1070: Cleaning build cache: ${cacheDir}`);
|
|
4340
|
-
|
|
4522
|
+
fs6.rmSync(cachePath, { recursive: true, force: true });
|
|
4341
4523
|
}
|
|
4342
4524
|
} catch (error) {
|
|
4343
4525
|
console.warn(`[WorktreeManager] EP1070: Failed to clean ${cacheDir} (non-blocking):`, error.message);
|
|
@@ -4346,10 +4528,10 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4346
4528
|
}
|
|
4347
4529
|
readConfig() {
|
|
4348
4530
|
try {
|
|
4349
|
-
if (!
|
|
4531
|
+
if (!fs6.existsSync(this.configPath)) {
|
|
4350
4532
|
return null;
|
|
4351
4533
|
}
|
|
4352
|
-
const content =
|
|
4534
|
+
const content = fs6.readFileSync(this.configPath, "utf-8");
|
|
4353
4535
|
return JSON.parse(content);
|
|
4354
4536
|
} catch (error) {
|
|
4355
4537
|
if (error instanceof SyntaxError) {
|
|
@@ -4363,11 +4545,11 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4363
4545
|
}
|
|
4364
4546
|
writeConfig(config) {
|
|
4365
4547
|
try {
|
|
4366
|
-
const dir =
|
|
4367
|
-
if (!
|
|
4368
|
-
|
|
4548
|
+
const dir = path7.dirname(this.configPath);
|
|
4549
|
+
if (!fs6.existsSync(dir)) {
|
|
4550
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
4369
4551
|
}
|
|
4370
|
-
|
|
4552
|
+
fs6.writeFileSync(this.configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
4371
4553
|
} catch (error) {
|
|
4372
4554
|
console.error("[WorktreeManager] Failed to write config:", error);
|
|
4373
4555
|
throw error;
|
|
@@ -4449,14 +4631,14 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4449
4631
|
}
|
|
4450
4632
|
try {
|
|
4451
4633
|
for (const file of files) {
|
|
4452
|
-
const srcPath =
|
|
4453
|
-
const destPath =
|
|
4454
|
-
if (
|
|
4455
|
-
const destDir =
|
|
4456
|
-
if (!
|
|
4457
|
-
|
|
4458
|
-
}
|
|
4459
|
-
|
|
4634
|
+
const srcPath = path7.join(mainWorktree.worktreePath, file);
|
|
4635
|
+
const destPath = path7.join(worktree.worktreePath, file);
|
|
4636
|
+
if (fs6.existsSync(srcPath)) {
|
|
4637
|
+
const destDir = path7.dirname(destPath);
|
|
4638
|
+
if (!fs6.existsSync(destDir)) {
|
|
4639
|
+
fs6.mkdirSync(destDir, { recursive: true });
|
|
4640
|
+
}
|
|
4641
|
+
fs6.copyFileSync(srcPath, destPath);
|
|
4460
4642
|
console.log(`[WorktreeManager] EP964: Copied ${file} to ${moduleUid} (deprecated)`);
|
|
4461
4643
|
} else {
|
|
4462
4644
|
console.log(`[WorktreeManager] EP964: Skipped ${file} (not found in main)`);
|
|
@@ -4539,10 +4721,10 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4539
4721
|
}
|
|
4540
4722
|
};
|
|
4541
4723
|
function getEpisodaRoot() {
|
|
4542
|
-
return process.env.EPISODA_ROOT ||
|
|
4724
|
+
return process.env.EPISODA_ROOT || path7.join(require("os").homedir(), "episoda");
|
|
4543
4725
|
}
|
|
4544
4726
|
function getProjectPath(workspaceSlug, projectSlug) {
|
|
4545
|
-
return
|
|
4727
|
+
return path7.join(getEpisodaRoot(), workspaceSlug, projectSlug);
|
|
4546
4728
|
}
|
|
4547
4729
|
async function isWorktreeProject(projectRoot) {
|
|
4548
4730
|
const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
|
|
@@ -4557,7 +4739,7 @@ async function isWorktreeProject(projectRoot) {
|
|
|
4557
4739
|
return result;
|
|
4558
4740
|
}
|
|
4559
4741
|
async function findProjectRoot(startPath) {
|
|
4560
|
-
let current =
|
|
4742
|
+
let current = path7.resolve(startPath);
|
|
4561
4743
|
const episodaRoot = getEpisodaRoot();
|
|
4562
4744
|
const debug = process.env.EPISODA_DEBUG === "1" || process.env.GIT_CREDENTIAL_EPISODA_DEBUG === "1";
|
|
4563
4745
|
if (debug) {
|
|
@@ -4570,14 +4752,14 @@ async function findProjectRoot(startPath) {
|
|
|
4570
4752
|
return null;
|
|
4571
4753
|
}
|
|
4572
4754
|
for (let i = 0; i < 10; i++) {
|
|
4573
|
-
const bareDir =
|
|
4574
|
-
const episodaDir =
|
|
4755
|
+
const bareDir = path7.join(current, ".bare");
|
|
4756
|
+
const episodaDir = path7.join(current, ".episoda");
|
|
4575
4757
|
if (debug) {
|
|
4576
|
-
const bareExists =
|
|
4577
|
-
const episodaExists =
|
|
4758
|
+
const bareExists = fs6.existsSync(bareDir);
|
|
4759
|
+
const episodaExists = fs6.existsSync(episodaDir);
|
|
4578
4760
|
console.log(`[WorktreeManager] findProjectRoot: checking ${current} (.bare=${bareExists}, .episoda=${episodaExists})`);
|
|
4579
4761
|
}
|
|
4580
|
-
if (
|
|
4762
|
+
if (fs6.existsSync(bareDir) && fs6.existsSync(episodaDir)) {
|
|
4581
4763
|
if (await isWorktreeProject(current)) {
|
|
4582
4764
|
if (debug) {
|
|
4583
4765
|
console.log(`[WorktreeManager] findProjectRoot: found valid project at ${current}`);
|
|
@@ -4585,7 +4767,7 @@ async function findProjectRoot(startPath) {
|
|
|
4585
4767
|
return current;
|
|
4586
4768
|
}
|
|
4587
4769
|
}
|
|
4588
|
-
const parent =
|
|
4770
|
+
const parent = path7.dirname(current);
|
|
4589
4771
|
if (parent === current) {
|
|
4590
4772
|
break;
|
|
4591
4773
|
}
|
|
@@ -4623,39 +4805,39 @@ var import_fs = require("fs");
|
|
|
4623
4805
|
// src/preview/dev-server-runner.ts
|
|
4624
4806
|
var import_child_process5 = require("child_process");
|
|
4625
4807
|
var http = __toESM(require("http"));
|
|
4626
|
-
var
|
|
4627
|
-
var
|
|
4808
|
+
var fs10 = __toESM(require("fs"));
|
|
4809
|
+
var path11 = __toESM(require("path"));
|
|
4628
4810
|
var import_events = require("events");
|
|
4629
4811
|
var import_core7 = __toESM(require_dist());
|
|
4630
4812
|
|
|
4631
4813
|
// src/utils/port-check.ts
|
|
4632
4814
|
var net2 = __toESM(require("net"));
|
|
4633
4815
|
async function isPortInUse(port) {
|
|
4634
|
-
return new Promise((
|
|
4816
|
+
return new Promise((resolve4) => {
|
|
4635
4817
|
const server = net2.createServer();
|
|
4636
4818
|
server.once("error", (err) => {
|
|
4637
4819
|
if (err.code === "EADDRINUSE") {
|
|
4638
|
-
|
|
4820
|
+
resolve4(true);
|
|
4639
4821
|
} else {
|
|
4640
|
-
|
|
4822
|
+
resolve4(false);
|
|
4641
4823
|
}
|
|
4642
4824
|
});
|
|
4643
4825
|
server.once("listening", () => {
|
|
4644
4826
|
server.close();
|
|
4645
|
-
|
|
4827
|
+
resolve4(false);
|
|
4646
4828
|
});
|
|
4647
4829
|
server.listen(port);
|
|
4648
4830
|
});
|
|
4649
4831
|
}
|
|
4650
4832
|
|
|
4651
4833
|
// src/utils/env-cache.ts
|
|
4652
|
-
var
|
|
4653
|
-
var
|
|
4834
|
+
var fs8 = __toESM(require("fs"));
|
|
4835
|
+
var path9 = __toESM(require("path"));
|
|
4654
4836
|
var os = __toESM(require("os"));
|
|
4655
4837
|
|
|
4656
4838
|
// src/utils/env-setup.ts
|
|
4657
|
-
var
|
|
4658
|
-
var
|
|
4839
|
+
var fs7 = __toESM(require("fs"));
|
|
4840
|
+
var path8 = __toESM(require("path"));
|
|
4659
4841
|
async function fetchEnvVars(apiUrl, accessToken) {
|
|
4660
4842
|
try {
|
|
4661
4843
|
const url = `${apiUrl}/api/cli/env-vars`;
|
|
@@ -4690,29 +4872,29 @@ function writeEnvFile(targetPath, envVars) {
|
|
|
4690
4872
|
}
|
|
4691
4873
|
return `${key}=${value}`;
|
|
4692
4874
|
}).join("\n") + "\n";
|
|
4693
|
-
const envPath =
|
|
4694
|
-
|
|
4875
|
+
const envPath = path8.join(targetPath, ".env");
|
|
4876
|
+
fs7.writeFileSync(envPath, envContent, { mode: 384 });
|
|
4695
4877
|
console.log(`[env-setup] Wrote ${Object.keys(envVars).length} env vars to ${envPath}`);
|
|
4696
4878
|
}
|
|
4697
4879
|
|
|
4698
4880
|
// src/utils/env-cache.ts
|
|
4699
4881
|
var DEFAULT_CACHE_TTL = 60;
|
|
4700
|
-
var CACHE_DIR =
|
|
4882
|
+
var CACHE_DIR = path9.join(os.homedir(), ".episoda", "cache");
|
|
4701
4883
|
function getCacheFilePath(projectId) {
|
|
4702
|
-
return
|
|
4884
|
+
return path9.join(CACHE_DIR, `env-vars-${projectId}.json`);
|
|
4703
4885
|
}
|
|
4704
4886
|
function ensureCacheDir() {
|
|
4705
|
-
if (!
|
|
4706
|
-
|
|
4887
|
+
if (!fs8.existsSync(CACHE_DIR)) {
|
|
4888
|
+
fs8.mkdirSync(CACHE_DIR, { recursive: true, mode: 448 });
|
|
4707
4889
|
}
|
|
4708
4890
|
}
|
|
4709
4891
|
function readCache(projectId) {
|
|
4710
4892
|
try {
|
|
4711
4893
|
const cacheFile = getCacheFilePath(projectId);
|
|
4712
|
-
if (!
|
|
4894
|
+
if (!fs8.existsSync(cacheFile)) {
|
|
4713
4895
|
return null;
|
|
4714
4896
|
}
|
|
4715
|
-
const content =
|
|
4897
|
+
const content = fs8.readFileSync(cacheFile, "utf-8");
|
|
4716
4898
|
const data = JSON.parse(content);
|
|
4717
4899
|
if (!data.vars || typeof data.vars !== "object" || !data.fetchedAt) {
|
|
4718
4900
|
return null;
|
|
@@ -4731,7 +4913,7 @@ function writeCache(projectId, vars) {
|
|
|
4731
4913
|
fetchedAt: Date.now(),
|
|
4732
4914
|
projectId
|
|
4733
4915
|
};
|
|
4734
|
-
|
|
4916
|
+
fs8.writeFileSync(cacheFile, JSON.stringify(data, null, 2), { mode: 384 });
|
|
4735
4917
|
} catch (error) {
|
|
4736
4918
|
console.warn("[env-cache] Failed to write cache:", error instanceof Error ? error.message : error);
|
|
4737
4919
|
}
|
|
@@ -4800,11 +4982,11 @@ No cached values available as fallback.`
|
|
|
4800
4982
|
}
|
|
4801
4983
|
|
|
4802
4984
|
// src/preview/dev-server-registry.ts
|
|
4803
|
-
var
|
|
4804
|
-
var
|
|
4985
|
+
var fs9 = __toESM(require("fs"));
|
|
4986
|
+
var path10 = __toESM(require("path"));
|
|
4805
4987
|
var os2 = __toESM(require("os"));
|
|
4806
4988
|
var import_child_process4 = require("child_process");
|
|
4807
|
-
var DEV_SERVER_REGISTRY_DIR =
|
|
4989
|
+
var DEV_SERVER_REGISTRY_DIR = path10.join(os2.homedir(), ".episoda", "dev-servers");
|
|
4808
4990
|
var DevServerRegistry = class {
|
|
4809
4991
|
constructor() {
|
|
4810
4992
|
this.ensureRegistryDir();
|
|
@@ -4814,9 +4996,9 @@ var DevServerRegistry = class {
|
|
|
4814
4996
|
*/
|
|
4815
4997
|
ensureRegistryDir() {
|
|
4816
4998
|
try {
|
|
4817
|
-
if (!
|
|
4999
|
+
if (!fs9.existsSync(DEV_SERVER_REGISTRY_DIR)) {
|
|
4818
5000
|
console.log(`[DevServerRegistry] EP1042: Creating registry directory: ${DEV_SERVER_REGISTRY_DIR}`);
|
|
4819
|
-
|
|
5001
|
+
fs9.mkdirSync(DEV_SERVER_REGISTRY_DIR, { recursive: true });
|
|
4820
5002
|
}
|
|
4821
5003
|
} catch (error) {
|
|
4822
5004
|
console.error(`[DevServerRegistry] EP1042: Failed to create registry directory:`, error);
|
|
@@ -4827,7 +5009,7 @@ var DevServerRegistry = class {
|
|
|
4827
5009
|
* Get the registry file path for a module
|
|
4828
5010
|
*/
|
|
4829
5011
|
getEntryPath(moduleUid) {
|
|
4830
|
-
return
|
|
5012
|
+
return path10.join(DEV_SERVER_REGISTRY_DIR, `${moduleUid}.json`);
|
|
4831
5013
|
}
|
|
4832
5014
|
/**
|
|
4833
5015
|
* Register a dev server
|
|
@@ -4838,7 +5020,7 @@ var DevServerRegistry = class {
|
|
|
4838
5020
|
try {
|
|
4839
5021
|
this.ensureRegistryDir();
|
|
4840
5022
|
const entryPath = this.getEntryPath(entry.moduleUid);
|
|
4841
|
-
|
|
5023
|
+
fs9.writeFileSync(entryPath, JSON.stringify(entry, null, 2), "utf8");
|
|
4842
5024
|
console.log(`[DevServerRegistry] EP1042: Registered ${entry.moduleUid} (PID ${entry.pid}, port ${entry.port})`);
|
|
4843
5025
|
} catch (error) {
|
|
4844
5026
|
console.error(`[DevServerRegistry] EP1042: Failed to register ${entry.moduleUid}:`, error);
|
|
@@ -4852,8 +5034,8 @@ var DevServerRegistry = class {
|
|
|
4852
5034
|
unregister(moduleUid) {
|
|
4853
5035
|
try {
|
|
4854
5036
|
const entryPath = this.getEntryPath(moduleUid);
|
|
4855
|
-
if (
|
|
4856
|
-
|
|
5037
|
+
if (fs9.existsSync(entryPath)) {
|
|
5038
|
+
fs9.unlinkSync(entryPath);
|
|
4857
5039
|
console.log(`[DevServerRegistry] EP1042: Unregistered ${moduleUid}`);
|
|
4858
5040
|
}
|
|
4859
5041
|
} catch (error) {
|
|
@@ -4869,10 +5051,10 @@ var DevServerRegistry = class {
|
|
|
4869
5051
|
getByModule(moduleUid) {
|
|
4870
5052
|
try {
|
|
4871
5053
|
const entryPath = this.getEntryPath(moduleUid);
|
|
4872
|
-
if (!
|
|
5054
|
+
if (!fs9.existsSync(entryPath)) {
|
|
4873
5055
|
return null;
|
|
4874
5056
|
}
|
|
4875
|
-
const content =
|
|
5057
|
+
const content = fs9.readFileSync(entryPath, "utf8");
|
|
4876
5058
|
const entry = JSON.parse(content);
|
|
4877
5059
|
if (!entry.pid || !entry.port || !entry.worktreePath) {
|
|
4878
5060
|
console.warn(`[DevServerRegistry] EP1042: Invalid entry for ${moduleUid}, removing`);
|
|
@@ -4910,7 +5092,7 @@ var DevServerRegistry = class {
|
|
|
4910
5092
|
const entries = [];
|
|
4911
5093
|
try {
|
|
4912
5094
|
this.ensureRegistryDir();
|
|
4913
|
-
const files =
|
|
5095
|
+
const files = fs9.readdirSync(DEV_SERVER_REGISTRY_DIR).filter((f) => f.endsWith(".json"));
|
|
4914
5096
|
for (const file of files) {
|
|
4915
5097
|
const moduleUid = file.replace(".json", "");
|
|
4916
5098
|
const entry = this.getByModule(moduleUid);
|
|
@@ -4968,7 +5150,7 @@ var DevServerRegistry = class {
|
|
|
4968
5150
|
return null;
|
|
4969
5151
|
} catch {
|
|
4970
5152
|
try {
|
|
4971
|
-
return
|
|
5153
|
+
return fs9.readlinkSync(`/proc/${pid}/cwd`);
|
|
4972
5154
|
} catch {
|
|
4973
5155
|
return null;
|
|
4974
5156
|
}
|
|
@@ -5078,7 +5260,7 @@ var DevServerRegistry = class {
|
|
|
5078
5260
|
return killed;
|
|
5079
5261
|
}
|
|
5080
5262
|
wait(ms) {
|
|
5081
|
-
return new Promise((
|
|
5263
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
5082
5264
|
}
|
|
5083
5265
|
};
|
|
5084
5266
|
var registryInstance = null;
|
|
@@ -5338,8 +5520,8 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5338
5520
|
cacheTtl: 300
|
|
5339
5521
|
});
|
|
5340
5522
|
console.log(`[DevServerRunner] Loaded ${Object.keys(result.envVars).length} env vars`);
|
|
5341
|
-
const envFilePath =
|
|
5342
|
-
if (!
|
|
5523
|
+
const envFilePath = path11.join(projectPath, ".env");
|
|
5524
|
+
if (!fs10.existsSync(envFilePath) && Object.keys(result.envVars).length > 0) {
|
|
5343
5525
|
console.log(`[DevServerRunner] Writing .env file`);
|
|
5344
5526
|
writeEnvFile(projectPath, result.envVars);
|
|
5345
5527
|
}
|
|
@@ -5454,7 +5636,7 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5454
5636
|
return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
|
|
5455
5637
|
}
|
|
5456
5638
|
async checkHealth(port) {
|
|
5457
|
-
return new Promise((
|
|
5639
|
+
return new Promise((resolve4) => {
|
|
5458
5640
|
const req = http.request(
|
|
5459
5641
|
{
|
|
5460
5642
|
hostname: "localhost",
|
|
@@ -5463,12 +5645,12 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5463
5645
|
method: "HEAD",
|
|
5464
5646
|
timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
|
|
5465
5647
|
},
|
|
5466
|
-
() =>
|
|
5648
|
+
() => resolve4(true)
|
|
5467
5649
|
);
|
|
5468
|
-
req.on("error", () =>
|
|
5650
|
+
req.on("error", () => resolve4(false));
|
|
5469
5651
|
req.on("timeout", () => {
|
|
5470
5652
|
req.destroy();
|
|
5471
|
-
|
|
5653
|
+
resolve4(false);
|
|
5472
5654
|
});
|
|
5473
5655
|
req.end();
|
|
5474
5656
|
});
|
|
@@ -5485,28 +5667,28 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5485
5667
|
return false;
|
|
5486
5668
|
}
|
|
5487
5669
|
wait(ms) {
|
|
5488
|
-
return new Promise((
|
|
5670
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
5489
5671
|
}
|
|
5490
5672
|
getLogsDir() {
|
|
5491
|
-
const logsDir =
|
|
5492
|
-
if (!
|
|
5493
|
-
|
|
5673
|
+
const logsDir = path11.join((0, import_core7.getConfigDir)(), "logs");
|
|
5674
|
+
if (!fs10.existsSync(logsDir)) {
|
|
5675
|
+
fs10.mkdirSync(logsDir, { recursive: true });
|
|
5494
5676
|
}
|
|
5495
5677
|
return logsDir;
|
|
5496
5678
|
}
|
|
5497
5679
|
getLogFilePath(moduleUid) {
|
|
5498
|
-
return
|
|
5680
|
+
return path11.join(this.getLogsDir(), `dev-${moduleUid}.log`);
|
|
5499
5681
|
}
|
|
5500
5682
|
rotateLogIfNeeded(logPath) {
|
|
5501
5683
|
try {
|
|
5502
|
-
if (
|
|
5503
|
-
const stats =
|
|
5684
|
+
if (fs10.existsSync(logPath)) {
|
|
5685
|
+
const stats = fs10.statSync(logPath);
|
|
5504
5686
|
if (stats.size > DEV_SERVER_CONSTANTS.MAX_LOG_SIZE_BYTES) {
|
|
5505
5687
|
const backupPath = `${logPath}.1`;
|
|
5506
|
-
if (
|
|
5507
|
-
|
|
5688
|
+
if (fs10.existsSync(backupPath)) {
|
|
5689
|
+
fs10.unlinkSync(backupPath);
|
|
5508
5690
|
}
|
|
5509
|
-
|
|
5691
|
+
fs10.renameSync(logPath, backupPath);
|
|
5510
5692
|
}
|
|
5511
5693
|
}
|
|
5512
5694
|
} catch {
|
|
@@ -5517,7 +5699,7 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5517
5699
|
try {
|
|
5518
5700
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
5519
5701
|
const prefix = isError ? "ERR" : "OUT";
|
|
5520
|
-
|
|
5702
|
+
fs10.appendFileSync(logPath, `[${timestamp}] [${prefix}] ${line}
|
|
5521
5703
|
`);
|
|
5522
5704
|
} catch {
|
|
5523
5705
|
}
|
|
@@ -5547,14 +5729,14 @@ function getDevServerRunner() {
|
|
|
5547
5729
|
// src/tunnel/tunnel-manager.ts
|
|
5548
5730
|
var import_child_process7 = require("child_process");
|
|
5549
5731
|
var import_events2 = require("events");
|
|
5550
|
-
var
|
|
5551
|
-
var
|
|
5732
|
+
var fs12 = __toESM(require("fs"));
|
|
5733
|
+
var path13 = __toESM(require("path"));
|
|
5552
5734
|
var os4 = __toESM(require("os"));
|
|
5553
5735
|
|
|
5554
5736
|
// src/tunnel/cloudflared-manager.ts
|
|
5555
5737
|
var import_child_process6 = require("child_process");
|
|
5556
|
-
var
|
|
5557
|
-
var
|
|
5738
|
+
var fs11 = __toESM(require("fs"));
|
|
5739
|
+
var path12 = __toESM(require("path"));
|
|
5558
5740
|
var os3 = __toESM(require("os"));
|
|
5559
5741
|
var https = __toESM(require("https"));
|
|
5560
5742
|
var tar = __toESM(require("tar"));
|
|
@@ -5573,11 +5755,11 @@ var DOWNLOAD_URLS = {
|
|
|
5573
5755
|
}
|
|
5574
5756
|
};
|
|
5575
5757
|
function getEpisodaBinDir() {
|
|
5576
|
-
return
|
|
5758
|
+
return path12.join(os3.homedir(), ".episoda", "bin");
|
|
5577
5759
|
}
|
|
5578
5760
|
function getCloudflaredPath() {
|
|
5579
5761
|
const binaryName = os3.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
5580
|
-
return
|
|
5762
|
+
return path12.join(getEpisodaBinDir(), binaryName);
|
|
5581
5763
|
}
|
|
5582
5764
|
function isCloudflaredInPath() {
|
|
5583
5765
|
try {
|
|
@@ -5594,7 +5776,7 @@ function isCloudflaredInPath() {
|
|
|
5594
5776
|
function isCloudflaredInstalled() {
|
|
5595
5777
|
const cloudflaredPath = getCloudflaredPath();
|
|
5596
5778
|
try {
|
|
5597
|
-
|
|
5779
|
+
fs11.accessSync(cloudflaredPath, fs11.constants.X_OK);
|
|
5598
5780
|
return true;
|
|
5599
5781
|
} catch {
|
|
5600
5782
|
return false;
|
|
@@ -5618,7 +5800,7 @@ function getDownloadUrl() {
|
|
|
5618
5800
|
return platformUrls[arch3] || null;
|
|
5619
5801
|
}
|
|
5620
5802
|
async function downloadFile(url, destPath) {
|
|
5621
|
-
return new Promise((
|
|
5803
|
+
return new Promise((resolve4, reject) => {
|
|
5622
5804
|
const followRedirect = (currentUrl, redirectCount = 0) => {
|
|
5623
5805
|
if (redirectCount > 5) {
|
|
5624
5806
|
reject(new Error("Too many redirects"));
|
|
@@ -5644,14 +5826,14 @@ async function downloadFile(url, destPath) {
|
|
|
5644
5826
|
reject(new Error(`Failed to download: HTTP ${response.statusCode}`));
|
|
5645
5827
|
return;
|
|
5646
5828
|
}
|
|
5647
|
-
const file =
|
|
5829
|
+
const file = fs11.createWriteStream(destPath);
|
|
5648
5830
|
response.pipe(file);
|
|
5649
5831
|
file.on("finish", () => {
|
|
5650
5832
|
file.close();
|
|
5651
|
-
|
|
5833
|
+
resolve4();
|
|
5652
5834
|
});
|
|
5653
5835
|
file.on("error", (err) => {
|
|
5654
|
-
|
|
5836
|
+
fs11.unlinkSync(destPath);
|
|
5655
5837
|
reject(err);
|
|
5656
5838
|
});
|
|
5657
5839
|
}).on("error", reject);
|
|
@@ -5672,21 +5854,21 @@ async function downloadCloudflared() {
|
|
|
5672
5854
|
}
|
|
5673
5855
|
const binDir = getEpisodaBinDir();
|
|
5674
5856
|
const cloudflaredPath = getCloudflaredPath();
|
|
5675
|
-
|
|
5857
|
+
fs11.mkdirSync(binDir, { recursive: true });
|
|
5676
5858
|
const isTgz = url.endsWith(".tgz");
|
|
5677
5859
|
if (isTgz) {
|
|
5678
|
-
const tempFile =
|
|
5860
|
+
const tempFile = path12.join(binDir, "cloudflared.tgz");
|
|
5679
5861
|
console.log(`[Tunnel] Downloading cloudflared from ${url}...`);
|
|
5680
5862
|
await downloadFile(url, tempFile);
|
|
5681
5863
|
console.log("[Tunnel] Extracting cloudflared...");
|
|
5682
5864
|
await extractTgz(tempFile, binDir);
|
|
5683
|
-
|
|
5865
|
+
fs11.unlinkSync(tempFile);
|
|
5684
5866
|
} else {
|
|
5685
5867
|
console.log(`[Tunnel] Downloading cloudflared from ${url}...`);
|
|
5686
5868
|
await downloadFile(url, cloudflaredPath);
|
|
5687
5869
|
}
|
|
5688
5870
|
if (os3.platform() !== "win32") {
|
|
5689
|
-
|
|
5871
|
+
fs11.chmodSync(cloudflaredPath, 493);
|
|
5690
5872
|
}
|
|
5691
5873
|
if (!verifyCloudflared(cloudflaredPath)) {
|
|
5692
5874
|
throw new Error("Downloaded cloudflared binary failed verification");
|
|
@@ -5850,7 +6032,7 @@ function getDaemonModeConfig() {
|
|
|
5850
6032
|
}
|
|
5851
6033
|
|
|
5852
6034
|
// src/tunnel/tunnel-manager.ts
|
|
5853
|
-
var TUNNEL_PID_DIR =
|
|
6035
|
+
var TUNNEL_PID_DIR = path13.join(os4.homedir(), ".episoda", "tunnels");
|
|
5854
6036
|
var TUNNEL_TIMEOUTS = {
|
|
5855
6037
|
/** Time to wait for Named Tunnel connection (includes API token fetch + connect) */
|
|
5856
6038
|
NAMED_TUNNEL_CONNECT: 6e4,
|
|
@@ -5882,9 +6064,9 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
5882
6064
|
*/
|
|
5883
6065
|
ensurePidDir() {
|
|
5884
6066
|
try {
|
|
5885
|
-
if (!
|
|
6067
|
+
if (!fs12.existsSync(TUNNEL_PID_DIR)) {
|
|
5886
6068
|
console.log(`[Tunnel] EP904: Creating PID directory: ${TUNNEL_PID_DIR}`);
|
|
5887
|
-
|
|
6069
|
+
fs12.mkdirSync(TUNNEL_PID_DIR, { recursive: true });
|
|
5888
6070
|
console.log(`[Tunnel] EP904: PID directory created successfully`);
|
|
5889
6071
|
}
|
|
5890
6072
|
} catch (error) {
|
|
@@ -5896,7 +6078,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
5896
6078
|
* EP877: Get PID file path for a module
|
|
5897
6079
|
*/
|
|
5898
6080
|
getPidFilePath(moduleUid) {
|
|
5899
|
-
return
|
|
6081
|
+
return path13.join(TUNNEL_PID_DIR, `${moduleUid}.pid`);
|
|
5900
6082
|
}
|
|
5901
6083
|
/**
|
|
5902
6084
|
* EP877: Write PID to file for tracking across restarts
|
|
@@ -5906,7 +6088,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
5906
6088
|
try {
|
|
5907
6089
|
this.ensurePidDir();
|
|
5908
6090
|
const pidPath = this.getPidFilePath(moduleUid);
|
|
5909
|
-
|
|
6091
|
+
fs12.writeFileSync(pidPath, pid.toString(), "utf8");
|
|
5910
6092
|
console.log(`[Tunnel] EP904: Wrote PID ${pid} for ${moduleUid} to ${pidPath}`);
|
|
5911
6093
|
} catch (error) {
|
|
5912
6094
|
console.error(`[Tunnel] EP904: Failed to write PID file for ${moduleUid}:`, error);
|
|
@@ -5919,10 +6101,10 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
5919
6101
|
readPidFile(moduleUid) {
|
|
5920
6102
|
try {
|
|
5921
6103
|
const pidPath = this.getPidFilePath(moduleUid);
|
|
5922
|
-
if (!
|
|
6104
|
+
if (!fs12.existsSync(pidPath)) {
|
|
5923
6105
|
return null;
|
|
5924
6106
|
}
|
|
5925
|
-
const content =
|
|
6107
|
+
const content = fs12.readFileSync(pidPath, "utf8").trim();
|
|
5926
6108
|
const pid = parseInt(content, 10);
|
|
5927
6109
|
if (isNaN(pid) || pid <= 0) {
|
|
5928
6110
|
console.warn(`[Tunnel] EP948: Invalid PID file content for ${moduleUid}: "${content}", removing stale file`);
|
|
@@ -5950,8 +6132,8 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
5950
6132
|
removePidFile(moduleUid) {
|
|
5951
6133
|
try {
|
|
5952
6134
|
const pidPath = this.getPidFilePath(moduleUid);
|
|
5953
|
-
if (
|
|
5954
|
-
|
|
6135
|
+
if (fs12.existsSync(pidPath)) {
|
|
6136
|
+
fs12.unlinkSync(pidPath);
|
|
5955
6137
|
console.log(`[Tunnel] EP877: Removed PID file for ${moduleUid}`);
|
|
5956
6138
|
}
|
|
5957
6139
|
} catch (error) {
|
|
@@ -6026,10 +6208,10 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6026
6208
|
const isTracked = Array.from(this.tunnelStates.values()).some((s) => s.info.pid === pid);
|
|
6027
6209
|
console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`);
|
|
6028
6210
|
this.killByPid(pid, "SIGTERM");
|
|
6029
|
-
await new Promise((
|
|
6211
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
6030
6212
|
if (this.isProcessRunning(pid)) {
|
|
6031
6213
|
this.killByPid(pid, "SIGKILL");
|
|
6032
|
-
await new Promise((
|
|
6214
|
+
await new Promise((resolve4) => setTimeout(resolve4, 200));
|
|
6033
6215
|
}
|
|
6034
6216
|
killed.push(pid);
|
|
6035
6217
|
}
|
|
@@ -6055,7 +6237,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6055
6237
|
}
|
|
6056
6238
|
try {
|
|
6057
6239
|
this.ensurePidDir();
|
|
6058
|
-
const pidFiles =
|
|
6240
|
+
const pidFiles = fs12.readdirSync(TUNNEL_PID_DIR).filter((f) => f.endsWith(".pid"));
|
|
6059
6241
|
for (const pidFile of pidFiles) {
|
|
6060
6242
|
const moduleUid = pidFile.replace(".pid", "");
|
|
6061
6243
|
const pid = this.readPidFile(moduleUid);
|
|
@@ -6063,7 +6245,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6063
6245
|
if (!this.tunnelStates.has(moduleUid)) {
|
|
6064
6246
|
console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`);
|
|
6065
6247
|
this.killByPid(pid, "SIGTERM");
|
|
6066
|
-
await new Promise((
|
|
6248
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
6067
6249
|
if (this.isProcessRunning(pid)) {
|
|
6068
6250
|
this.killByPid(pid, "SIGKILL");
|
|
6069
6251
|
}
|
|
@@ -6078,7 +6260,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6078
6260
|
if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {
|
|
6079
6261
|
console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`);
|
|
6080
6262
|
this.killByPid(pid, "SIGTERM");
|
|
6081
|
-
await new Promise((
|
|
6263
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
6082
6264
|
if (this.isProcessRunning(pid)) {
|
|
6083
6265
|
this.killByPid(pid, "SIGKILL");
|
|
6084
6266
|
}
|
|
@@ -6168,7 +6350,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6168
6350
|
return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
|
|
6169
6351
|
}
|
|
6170
6352
|
}
|
|
6171
|
-
return new Promise((
|
|
6353
|
+
return new Promise((resolve4) => {
|
|
6172
6354
|
const tunnelInfo = {
|
|
6173
6355
|
moduleUid,
|
|
6174
6356
|
url: previewUrl || "",
|
|
@@ -6234,7 +6416,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6234
6416
|
moduleUid,
|
|
6235
6417
|
url: tunnelInfo.url
|
|
6236
6418
|
});
|
|
6237
|
-
|
|
6419
|
+
resolve4({ success: true, url: tunnelInfo.url });
|
|
6238
6420
|
}
|
|
6239
6421
|
};
|
|
6240
6422
|
process2.stderr?.on("data", (data) => {
|
|
@@ -6261,7 +6443,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6261
6443
|
onStatusChange?.("error", errorMsg);
|
|
6262
6444
|
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
6263
6445
|
}
|
|
6264
|
-
|
|
6446
|
+
resolve4({ success: false, error: errorMsg });
|
|
6265
6447
|
} else if (wasConnected) {
|
|
6266
6448
|
if (currentState && !currentState.intentionallyStopped) {
|
|
6267
6449
|
console.log(`[Tunnel] EP948: Named tunnel ${moduleUid} crashed unexpectedly, attempting reconnect...`);
|
|
@@ -6292,7 +6474,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6292
6474
|
this.emitEvent({ type: "error", moduleUid, error: error.message });
|
|
6293
6475
|
}
|
|
6294
6476
|
if (!connected) {
|
|
6295
|
-
|
|
6477
|
+
resolve4({ success: false, error: error.message });
|
|
6296
6478
|
}
|
|
6297
6479
|
});
|
|
6298
6480
|
setTimeout(() => {
|
|
@@ -6315,7 +6497,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6315
6497
|
onStatusChange?.("error", errorMsg);
|
|
6316
6498
|
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
6317
6499
|
}
|
|
6318
|
-
|
|
6500
|
+
resolve4({ success: false, error: errorMsg });
|
|
6319
6501
|
}
|
|
6320
6502
|
}, TUNNEL_TIMEOUTS.NAMED_TUNNEL_CONNECT);
|
|
6321
6503
|
});
|
|
@@ -6376,7 +6558,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6376
6558
|
if (orphanPid && this.isProcessRunning(orphanPid)) {
|
|
6377
6559
|
console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`);
|
|
6378
6560
|
this.killByPid(orphanPid, "SIGTERM");
|
|
6379
|
-
await new Promise((
|
|
6561
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
6380
6562
|
if (this.isProcessRunning(orphanPid)) {
|
|
6381
6563
|
this.killByPid(orphanPid, "SIGKILL");
|
|
6382
6564
|
}
|
|
@@ -6385,7 +6567,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6385
6567
|
const killedOnPort = await this.killCloudflaredOnPort(port);
|
|
6386
6568
|
if (killedOnPort.length > 0) {
|
|
6387
6569
|
console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`);
|
|
6388
|
-
await new Promise((
|
|
6570
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
6389
6571
|
}
|
|
6390
6572
|
const cleanup = await this.cleanupOrphanedProcesses();
|
|
6391
6573
|
if (cleanup.cleaned > 0) {
|
|
@@ -6425,7 +6607,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6425
6607
|
if (orphanPid && this.isProcessRunning(orphanPid)) {
|
|
6426
6608
|
console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`);
|
|
6427
6609
|
this.killByPid(orphanPid, "SIGTERM");
|
|
6428
|
-
await new Promise((
|
|
6610
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
6429
6611
|
if (this.isProcessRunning(orphanPid)) {
|
|
6430
6612
|
this.killByPid(orphanPid, "SIGKILL");
|
|
6431
6613
|
}
|
|
@@ -6441,16 +6623,16 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6441
6623
|
const tunnel = state.info;
|
|
6442
6624
|
if (tunnel.process && !tunnel.process.killed) {
|
|
6443
6625
|
tunnel.process.kill("SIGTERM");
|
|
6444
|
-
await new Promise((
|
|
6626
|
+
await new Promise((resolve4) => {
|
|
6445
6627
|
const timeout = setTimeout(() => {
|
|
6446
6628
|
if (tunnel.process && !tunnel.process.killed) {
|
|
6447
6629
|
tunnel.process.kill("SIGKILL");
|
|
6448
6630
|
}
|
|
6449
|
-
|
|
6631
|
+
resolve4();
|
|
6450
6632
|
}, 3e3);
|
|
6451
6633
|
tunnel.process.once("exit", () => {
|
|
6452
6634
|
clearTimeout(timeout);
|
|
6453
|
-
|
|
6635
|
+
resolve4();
|
|
6454
6636
|
});
|
|
6455
6637
|
});
|
|
6456
6638
|
}
|
|
@@ -6511,21 +6693,21 @@ function getTunnelManager() {
|
|
|
6511
6693
|
}
|
|
6512
6694
|
|
|
6513
6695
|
// src/utils/port-allocator.ts
|
|
6514
|
-
var
|
|
6515
|
-
var
|
|
6696
|
+
var fs13 = __toESM(require("fs"));
|
|
6697
|
+
var path14 = __toESM(require("path"));
|
|
6516
6698
|
var os5 = __toESM(require("os"));
|
|
6517
6699
|
var PORT_RANGE_START = 3100;
|
|
6518
6700
|
var PORT_RANGE_END = 3199;
|
|
6519
6701
|
var PORT_WARNING_THRESHOLD = 80;
|
|
6520
|
-
var PORTS_FILE =
|
|
6702
|
+
var PORTS_FILE = path14.join(os5.homedir(), ".episoda", "ports.json");
|
|
6521
6703
|
var portAssignments = /* @__PURE__ */ new Map();
|
|
6522
6704
|
var initialized = false;
|
|
6523
6705
|
function loadFromDisk() {
|
|
6524
6706
|
if (initialized) return;
|
|
6525
6707
|
initialized = true;
|
|
6526
6708
|
try {
|
|
6527
|
-
if (
|
|
6528
|
-
const content =
|
|
6709
|
+
if (fs13.existsSync(PORTS_FILE)) {
|
|
6710
|
+
const content = fs13.readFileSync(PORTS_FILE, "utf8");
|
|
6529
6711
|
const data = JSON.parse(content);
|
|
6530
6712
|
for (const [moduleUid, port] of Object.entries(data)) {
|
|
6531
6713
|
if (typeof port === "number" && port >= PORT_RANGE_START && port <= PORT_RANGE_END) {
|
|
@@ -6542,15 +6724,15 @@ function loadFromDisk() {
|
|
|
6542
6724
|
}
|
|
6543
6725
|
function saveToDisk() {
|
|
6544
6726
|
try {
|
|
6545
|
-
const dir =
|
|
6546
|
-
if (!
|
|
6547
|
-
|
|
6727
|
+
const dir = path14.dirname(PORTS_FILE);
|
|
6728
|
+
if (!fs13.existsSync(dir)) {
|
|
6729
|
+
fs13.mkdirSync(dir, { recursive: true });
|
|
6548
6730
|
}
|
|
6549
6731
|
const data = {};
|
|
6550
6732
|
for (const [moduleUid, port] of portAssignments) {
|
|
6551
6733
|
data[moduleUid] = port;
|
|
6552
6734
|
}
|
|
6553
|
-
|
|
6735
|
+
fs13.writeFileSync(PORTS_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
6554
6736
|
} catch (error) {
|
|
6555
6737
|
console.warn(`[PortAllocator] EP1042: Failed to save ports.json:`, error);
|
|
6556
6738
|
}
|
|
@@ -6734,7 +6916,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
6734
6916
|
for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
|
|
6735
6917
|
if (attempt > 1) {
|
|
6736
6918
|
console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
|
|
6737
|
-
await new Promise((
|
|
6919
|
+
await new Promise((resolve4) => setTimeout(resolve4, 2e3));
|
|
6738
6920
|
}
|
|
6739
6921
|
tunnelResult = await this.tunnel.startTunnel({
|
|
6740
6922
|
moduleUid,
|
|
@@ -6855,7 +7037,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
6855
7037
|
}
|
|
6856
7038
|
console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
|
|
6857
7039
|
await this.stopPreview(moduleUid);
|
|
6858
|
-
await new Promise((
|
|
7040
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
6859
7041
|
return this.startPreview({
|
|
6860
7042
|
moduleUid,
|
|
6861
7043
|
worktreePath: state.worktreePath,
|
|
@@ -7126,18 +7308,18 @@ async function handleWorktreeCreate(request2) {
|
|
|
7126
7308
|
}
|
|
7127
7309
|
try {
|
|
7128
7310
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
7129
|
-
const bareRepoPath =
|
|
7130
|
-
if (!
|
|
7311
|
+
const bareRepoPath = path15.join(projectPath, ".bare");
|
|
7312
|
+
if (!fs14.existsSync(bareRepoPath)) {
|
|
7131
7313
|
console.log(`[Worktree] K1273: Project not found, cloning lazily...`);
|
|
7132
7314
|
console.log(`[Worktree] Repo URL: ${repoUrl.replace(/\/\/[^@]*@/, "//***@")}`);
|
|
7133
|
-
const episodaDir =
|
|
7134
|
-
|
|
7315
|
+
const episodaDir = path15.join(projectPath, ".episoda");
|
|
7316
|
+
fs14.mkdirSync(episodaDir, { recursive: true });
|
|
7135
7317
|
try {
|
|
7136
7318
|
console.log(`[Worktree] K1273: Starting git clone...`);
|
|
7137
7319
|
await execAsync(`git clone --bare "${repoUrl}" "${bareRepoPath}"`);
|
|
7138
7320
|
console.log(`[Worktree] K1273: Clone successful`);
|
|
7139
7321
|
await execAsync(`git -C "${bareRepoPath}" config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"`);
|
|
7140
|
-
const configPath =
|
|
7322
|
+
const configPath = path15.join(episodaDir, "config.json");
|
|
7141
7323
|
const config2 = {
|
|
7142
7324
|
projectId,
|
|
7143
7325
|
workspaceSlug,
|
|
@@ -7146,7 +7328,7 @@ async function handleWorktreeCreate(request2) {
|
|
|
7146
7328
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7147
7329
|
worktrees: []
|
|
7148
7330
|
};
|
|
7149
|
-
|
|
7331
|
+
fs14.writeFileSync(configPath, JSON.stringify(config2, null, 2), "utf-8");
|
|
7150
7332
|
console.log(`[Worktree] K1273: Project initialized at ${projectPath}`);
|
|
7151
7333
|
} catch (cloneError) {
|
|
7152
7334
|
console.error(`[Worktree] K1273: Git clone failed: ${cloneError.message}`);
|
|
@@ -7194,8 +7376,8 @@ async function handleWorktreeCreate(request2) {
|
|
|
7194
7376
|
}
|
|
7195
7377
|
if (envVars && Object.keys(envVars).length > 0) {
|
|
7196
7378
|
const envContent = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
7197
|
-
const envPath =
|
|
7198
|
-
|
|
7379
|
+
const envPath = path15.join(worktreePath, ".env");
|
|
7380
|
+
fs14.writeFileSync(envPath, envContent + "\n", "utf-8");
|
|
7199
7381
|
console.log(`[Worktree] EP1143: Wrote ${Object.keys(envVars).length} env vars to .env`);
|
|
7200
7382
|
}
|
|
7201
7383
|
if (setupScript) {
|
|
@@ -7368,12 +7550,12 @@ async function handleProjectEject(request2) {
|
|
|
7368
7550
|
console.log(`[Worktree] EP1144: Ejecting project ${projectSlug} from workspace ${workspaceSlug}`);
|
|
7369
7551
|
try {
|
|
7370
7552
|
const projectPath = getProjectPath(workspaceSlug, projectSlug);
|
|
7371
|
-
const bareRepoPath =
|
|
7372
|
-
if (!
|
|
7553
|
+
const bareRepoPath = path15.join(projectPath, ".bare");
|
|
7554
|
+
if (!fs14.existsSync(projectPath)) {
|
|
7373
7555
|
console.log(`[Worktree] EP1144: Project path not found, nothing to eject: ${projectPath}`);
|
|
7374
7556
|
return { success: true };
|
|
7375
7557
|
}
|
|
7376
|
-
if (!
|
|
7558
|
+
if (!fs14.existsSync(bareRepoPath)) {
|
|
7377
7559
|
console.log(`[Worktree] EP1144: Bare repo not found, nothing to eject: ${bareRepoPath}`);
|
|
7378
7560
|
return { success: true };
|
|
7379
7561
|
}
|
|
@@ -7388,12 +7570,12 @@ async function handleProjectEject(request2) {
|
|
|
7388
7570
|
};
|
|
7389
7571
|
}
|
|
7390
7572
|
}
|
|
7391
|
-
const artifactsPath =
|
|
7392
|
-
if (
|
|
7573
|
+
const artifactsPath = path15.join(projectPath, "artifacts");
|
|
7574
|
+
if (fs14.existsSync(artifactsPath)) {
|
|
7393
7575
|
await persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug);
|
|
7394
7576
|
}
|
|
7395
7577
|
console.log(`[Worktree] EP1144: Removing project directory: ${projectPath}`);
|
|
7396
|
-
await
|
|
7578
|
+
await fs14.promises.rm(projectPath, { recursive: true, force: true });
|
|
7397
7579
|
console.log(`[Worktree] EP1144: Successfully ejected project ${projectSlug}`);
|
|
7398
7580
|
return { success: true };
|
|
7399
7581
|
} catch (error) {
|
|
@@ -7407,7 +7589,7 @@ async function handleProjectEject(request2) {
|
|
|
7407
7589
|
async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug) {
|
|
7408
7590
|
const MAX_ARTIFACT_SIZE = 10 * 1024 * 1024;
|
|
7409
7591
|
try {
|
|
7410
|
-
const files = await
|
|
7592
|
+
const files = await fs14.promises.readdir(artifactsPath);
|
|
7411
7593
|
if (files.length === 0) {
|
|
7412
7594
|
return;
|
|
7413
7595
|
}
|
|
@@ -7421,8 +7603,8 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
7421
7603
|
}
|
|
7422
7604
|
const artifacts = [];
|
|
7423
7605
|
for (const fileName of files) {
|
|
7424
|
-
const filePath =
|
|
7425
|
-
const stat = await
|
|
7606
|
+
const filePath = path15.join(artifactsPath, fileName);
|
|
7607
|
+
const stat = await fs14.promises.stat(filePath);
|
|
7426
7608
|
if (stat.isDirectory()) {
|
|
7427
7609
|
continue;
|
|
7428
7610
|
}
|
|
@@ -7431,9 +7613,9 @@ async function persistArtifactsBeforeEject(artifactsPath, projectId, projectSlug
|
|
|
7431
7613
|
continue;
|
|
7432
7614
|
}
|
|
7433
7615
|
try {
|
|
7434
|
-
const content = await
|
|
7616
|
+
const content = await fs14.promises.readFile(filePath);
|
|
7435
7617
|
const base64Content = content.toString("base64");
|
|
7436
|
-
const ext =
|
|
7618
|
+
const ext = path15.extname(fileName).toLowerCase();
|
|
7437
7619
|
const mimeTypes = {
|
|
7438
7620
|
".json": "application/json",
|
|
7439
7621
|
".txt": "text/plain",
|
|
@@ -7585,12 +7767,12 @@ async function cleanupStaleCommits(projectPath) {
|
|
|
7585
7767
|
|
|
7586
7768
|
// src/agent/claude-binary.ts
|
|
7587
7769
|
var import_child_process10 = require("child_process");
|
|
7588
|
-
var
|
|
7589
|
-
var
|
|
7770
|
+
var path16 = __toESM(require("path"));
|
|
7771
|
+
var fs15 = __toESM(require("fs"));
|
|
7590
7772
|
var cachedBinaryPath = null;
|
|
7591
7773
|
function isValidClaudeBinary(binaryPath) {
|
|
7592
7774
|
try {
|
|
7593
|
-
|
|
7775
|
+
fs15.accessSync(binaryPath, fs15.constants.X_OK);
|
|
7594
7776
|
const version = (0, import_child_process10.execSync)(`"${binaryPath}" --version`, {
|
|
7595
7777
|
encoding: "utf-8",
|
|
7596
7778
|
timeout: 5e3,
|
|
@@ -7623,14 +7805,14 @@ async function ensureClaudeBinary() {
|
|
|
7623
7805
|
}
|
|
7624
7806
|
const bundledPaths = [
|
|
7625
7807
|
// In production: node_modules/.bin/claude
|
|
7626
|
-
|
|
7808
|
+
path16.join(__dirname, "..", "..", "node_modules", ".bin", "claude"),
|
|
7627
7809
|
// In monorepo development: packages/episoda/node_modules/.bin/claude
|
|
7628
|
-
|
|
7810
|
+
path16.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "claude"),
|
|
7629
7811
|
// Root monorepo node_modules
|
|
7630
|
-
|
|
7812
|
+
path16.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "claude")
|
|
7631
7813
|
];
|
|
7632
7814
|
for (const bundledPath of bundledPaths) {
|
|
7633
|
-
if (
|
|
7815
|
+
if (fs15.existsSync(bundledPath) && isValidClaudeBinary(bundledPath)) {
|
|
7634
7816
|
cachedBinaryPath = bundledPath;
|
|
7635
7817
|
return cachedBinaryPath;
|
|
7636
7818
|
}
|
|
@@ -7656,12 +7838,12 @@ async function ensureClaudeBinary() {
|
|
|
7656
7838
|
|
|
7657
7839
|
// src/agent/codex-binary.ts
|
|
7658
7840
|
var import_child_process11 = require("child_process");
|
|
7659
|
-
var
|
|
7660
|
-
var
|
|
7841
|
+
var path17 = __toESM(require("path"));
|
|
7842
|
+
var fs16 = __toESM(require("fs"));
|
|
7661
7843
|
var cachedBinaryPath2 = null;
|
|
7662
7844
|
function isValidCodexBinary(binaryPath) {
|
|
7663
7845
|
try {
|
|
7664
|
-
|
|
7846
|
+
fs16.accessSync(binaryPath, fs16.constants.X_OK);
|
|
7665
7847
|
const version = (0, import_child_process11.execSync)(`"${binaryPath}" --version`, {
|
|
7666
7848
|
encoding: "utf-8",
|
|
7667
7849
|
timeout: 5e3,
|
|
@@ -7694,14 +7876,14 @@ async function ensureCodexBinary() {
|
|
|
7694
7876
|
}
|
|
7695
7877
|
const bundledPaths = [
|
|
7696
7878
|
// In production: node_modules/.bin/codex
|
|
7697
|
-
|
|
7879
|
+
path17.join(__dirname, "..", "..", "node_modules", ".bin", "codex"),
|
|
7698
7880
|
// In monorepo development: packages/episoda/node_modules/.bin/codex
|
|
7699
|
-
|
|
7881
|
+
path17.join(__dirname, "..", "..", "..", "..", "node_modules", ".bin", "codex"),
|
|
7700
7882
|
// Root monorepo node_modules
|
|
7701
|
-
|
|
7883
|
+
path17.join(__dirname, "..", "..", "..", "..", "..", "node_modules", ".bin", "codex")
|
|
7702
7884
|
];
|
|
7703
7885
|
for (const bundledPath of bundledPaths) {
|
|
7704
|
-
if (
|
|
7886
|
+
if (fs16.existsSync(bundledPath) && isValidCodexBinary(bundledPath)) {
|
|
7705
7887
|
cachedBinaryPath2 = bundledPath;
|
|
7706
7888
|
return cachedBinaryPath2;
|
|
7707
7889
|
}
|
|
@@ -7768,8 +7950,8 @@ function generateCodexConfig(credentials, projectPath) {
|
|
|
7768
7950
|
|
|
7769
7951
|
// src/agent/agent-manager.ts
|
|
7770
7952
|
var import_child_process12 = require("child_process");
|
|
7771
|
-
var
|
|
7772
|
-
var
|
|
7953
|
+
var path18 = __toESM(require("path"));
|
|
7954
|
+
var fs17 = __toESM(require("fs"));
|
|
7773
7955
|
var os6 = __toESM(require("os"));
|
|
7774
7956
|
|
|
7775
7957
|
// src/agent/claude-config.ts
|
|
@@ -7780,8 +7962,8 @@ function generateStatsigHash() {
|
|
|
7780
7962
|
function generateNumericHash() {
|
|
7781
7963
|
return Math.floor(1e9 + Math.random() * 9e9).toString();
|
|
7782
7964
|
}
|
|
7783
|
-
function generateSettings() {
|
|
7784
|
-
|
|
7965
|
+
function generateSettings(options = {}) {
|
|
7966
|
+
const settings = {
|
|
7785
7967
|
permissions: {
|
|
7786
7968
|
allow: [
|
|
7787
7969
|
"Bash",
|
|
@@ -7790,7 +7972,9 @@ function generateSettings() {
|
|
|
7790
7972
|
"Edit",
|
|
7791
7973
|
"Glob",
|
|
7792
7974
|
"Grep",
|
|
7793
|
-
"WebFetch"
|
|
7975
|
+
"WebFetch",
|
|
7976
|
+
"mcp__github"
|
|
7977
|
+
// EP1146: Allow GitHub MCP tools
|
|
7794
7978
|
],
|
|
7795
7979
|
deny: [],
|
|
7796
7980
|
ask: [],
|
|
@@ -7798,6 +7982,20 @@ function generateSettings() {
|
|
|
7798
7982
|
},
|
|
7799
7983
|
alwaysThinkingEnabled: true
|
|
7800
7984
|
};
|
|
7985
|
+
if (options.githubToken) {
|
|
7986
|
+
const mcpEnv = {
|
|
7987
|
+
GITHUB_PERSONAL_ACCESS_TOKEN: options.githubToken
|
|
7988
|
+
};
|
|
7989
|
+
settings.mcpServers = {
|
|
7990
|
+
github: {
|
|
7991
|
+
command: "npx",
|
|
7992
|
+
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
7993
|
+
env: mcpEnv
|
|
7994
|
+
}
|
|
7995
|
+
};
|
|
7996
|
+
console.log("[ClaudeConfig] EP1146: GitHub MCP server configured");
|
|
7997
|
+
}
|
|
7998
|
+
return settings;
|
|
7801
7999
|
}
|
|
7802
8000
|
function generateStableId() {
|
|
7803
8001
|
return JSON.stringify((0, import_crypto.randomUUID)());
|
|
@@ -8039,13 +8237,13 @@ function generateCachedEvaluations(stableId, sessionId) {
|
|
|
8039
8237
|
fullUserHash: Math.floor(Math.random() * 1e10).toString()
|
|
8040
8238
|
};
|
|
8041
8239
|
}
|
|
8042
|
-
function generateClaudeConfig() {
|
|
8240
|
+
function generateClaudeConfig(options = {}) {
|
|
8043
8241
|
const hash = generateStatsigHash();
|
|
8044
8242
|
const numericHash = generateNumericHash();
|
|
8045
8243
|
const stableId = (0, import_crypto.randomUUID)();
|
|
8046
8244
|
const sessionId = (0, import_crypto.randomUUID)();
|
|
8047
8245
|
return {
|
|
8048
|
-
"settings.json": JSON.stringify(generateSettings(), null, 2),
|
|
8246
|
+
"settings.json": JSON.stringify(generateSettings(options), null, 2),
|
|
8049
8247
|
statsig: {
|
|
8050
8248
|
[`statsig.stable_id.${numericHash}`]: generateStableId(),
|
|
8051
8249
|
[`statsig.session_id.${numericHash}`]: JSON.stringify(
|
|
@@ -8076,7 +8274,7 @@ var AgentManager = class {
|
|
|
8076
8274
|
this.initialized = false;
|
|
8077
8275
|
// EP1133: Lock for config file writes to prevent race conditions
|
|
8078
8276
|
this.configWriteLock = Promise.resolve();
|
|
8079
|
-
this.pidDir =
|
|
8277
|
+
this.pidDir = path18.join(os6.homedir(), ".episoda", "agent-pids");
|
|
8080
8278
|
}
|
|
8081
8279
|
/**
|
|
8082
8280
|
* EP1133: Acquire lock for config file writes
|
|
@@ -8085,8 +8283,8 @@ var AgentManager = class {
|
|
|
8085
8283
|
async withConfigLock(fn) {
|
|
8086
8284
|
const previousLock = this.configWriteLock;
|
|
8087
8285
|
let releaseLock;
|
|
8088
|
-
this.configWriteLock = new Promise((
|
|
8089
|
-
releaseLock =
|
|
8286
|
+
this.configWriteLock = new Promise((resolve4) => {
|
|
8287
|
+
releaseLock = resolve4;
|
|
8090
8288
|
});
|
|
8091
8289
|
try {
|
|
8092
8290
|
await previousLock;
|
|
@@ -8105,8 +8303,8 @@ var AgentManager = class {
|
|
|
8105
8303
|
return;
|
|
8106
8304
|
}
|
|
8107
8305
|
console.log("[AgentManager] Initializing...");
|
|
8108
|
-
if (!
|
|
8109
|
-
|
|
8306
|
+
if (!fs17.existsSync(this.pidDir)) {
|
|
8307
|
+
fs17.mkdirSync(this.pidDir, { recursive: true });
|
|
8110
8308
|
}
|
|
8111
8309
|
await this.cleanupOrphanedProcesses();
|
|
8112
8310
|
try {
|
|
@@ -8272,9 +8470,9 @@ ${message}`;
|
|
|
8272
8470
|
const useApiKey = !useOAuth && !!session.credentials.apiKey;
|
|
8273
8471
|
if (provider === "codex") {
|
|
8274
8472
|
await this.withConfigLock(async () => {
|
|
8275
|
-
const codexDir =
|
|
8276
|
-
if (!
|
|
8277
|
-
|
|
8473
|
+
const codexDir = path18.join(os6.homedir(), ".codex");
|
|
8474
|
+
if (!fs17.existsSync(codexDir)) {
|
|
8475
|
+
fs17.mkdirSync(codexDir, { recursive: true });
|
|
8278
8476
|
}
|
|
8279
8477
|
if (useOAuth) {
|
|
8280
8478
|
const codexConfig = generateCodexConfig({
|
|
@@ -8284,21 +8482,21 @@ ${message}`;
|
|
|
8284
8482
|
accountId: session.credentials.accountId,
|
|
8285
8483
|
expiresAt: session.credentials.expiresAt
|
|
8286
8484
|
}, session.projectPath);
|
|
8287
|
-
const authJsonPath =
|
|
8288
|
-
|
|
8485
|
+
const authJsonPath = path18.join(codexDir, "auth.json");
|
|
8486
|
+
fs17.writeFileSync(authJsonPath, codexConfig["auth.json"], { mode: 384 });
|
|
8289
8487
|
console.log("[AgentManager] EP1133: Wrote Codex auth.json to ~/.codex/auth.json");
|
|
8290
8488
|
if (codexConfig["config.toml"]) {
|
|
8291
|
-
const configTomlPath =
|
|
8489
|
+
const configTomlPath = path18.join(codexDir, "config.toml");
|
|
8292
8490
|
let existingConfig = "";
|
|
8293
8491
|
try {
|
|
8294
|
-
existingConfig =
|
|
8492
|
+
existingConfig = fs17.readFileSync(configTomlPath, "utf-8");
|
|
8295
8493
|
} catch {
|
|
8296
8494
|
}
|
|
8297
8495
|
const escapedPathForRegex = session.projectPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8298
8496
|
const projectKeyPattern = new RegExp(`\\[projects\\."${escapedPathForRegex}"\\]`);
|
|
8299
8497
|
const projectAlreadyTrusted = projectKeyPattern.test(existingConfig);
|
|
8300
8498
|
if (!projectAlreadyTrusted) {
|
|
8301
|
-
|
|
8499
|
+
fs17.writeFileSync(configTomlPath, existingConfig + "\n" + codexConfig["config.toml"], { mode: 420 });
|
|
8302
8500
|
console.log("[AgentManager] EP1133: Updated Codex config.toml with project trust");
|
|
8303
8501
|
}
|
|
8304
8502
|
}
|
|
@@ -8308,14 +8506,14 @@ ${message}`;
|
|
|
8308
8506
|
});
|
|
8309
8507
|
} else {
|
|
8310
8508
|
await this.withConfigLock(async () => {
|
|
8311
|
-
const claudeDir =
|
|
8312
|
-
const credentialsPath =
|
|
8313
|
-
const statsigDir =
|
|
8314
|
-
if (!
|
|
8315
|
-
|
|
8509
|
+
const claudeDir = path18.join(os6.homedir(), ".claude");
|
|
8510
|
+
const credentialsPath = path18.join(claudeDir, ".credentials.json");
|
|
8511
|
+
const statsigDir = path18.join(claudeDir, "statsig");
|
|
8512
|
+
if (!fs17.existsSync(claudeDir)) {
|
|
8513
|
+
fs17.mkdirSync(claudeDir, { recursive: true });
|
|
8316
8514
|
}
|
|
8317
|
-
if (!
|
|
8318
|
-
|
|
8515
|
+
if (!fs17.existsSync(statsigDir)) {
|
|
8516
|
+
fs17.mkdirSync(statsigDir, { recursive: true });
|
|
8319
8517
|
}
|
|
8320
8518
|
if (useOAuth) {
|
|
8321
8519
|
const oauthCredentials = {
|
|
@@ -8333,21 +8531,26 @@ ${message}`;
|
|
|
8333
8531
|
const credentialsContent = JSON.stringify({
|
|
8334
8532
|
claudeAiOauth: oauthCredentials
|
|
8335
8533
|
}, null, 2);
|
|
8336
|
-
|
|
8534
|
+
fs17.writeFileSync(credentialsPath, credentialsContent, { mode: 384 });
|
|
8337
8535
|
console.log("[AgentManager] Wrote OAuth credentials to ~/.claude/.credentials.json");
|
|
8338
8536
|
try {
|
|
8339
|
-
const claudeConfig = generateClaudeConfig(
|
|
8537
|
+
const claudeConfig = generateClaudeConfig({
|
|
8538
|
+
githubToken: session.credentials.githubToken
|
|
8539
|
+
});
|
|
8340
8540
|
const statsigFiles = Object.keys(claudeConfig.statsig);
|
|
8341
8541
|
const hasEvaluations = statsigFiles.some((f) => f.includes("cached.evaluations"));
|
|
8342
8542
|
const hasStableId = statsigFiles.some((f) => f.includes("stable_id"));
|
|
8343
8543
|
if (!hasEvaluations || !hasStableId) {
|
|
8344
8544
|
throw new Error(`Invalid statsig config: missing required files`);
|
|
8345
8545
|
}
|
|
8346
|
-
const settingsPath =
|
|
8347
|
-
|
|
8546
|
+
const settingsPath = path18.join(claudeDir, "settings.json");
|
|
8547
|
+
fs17.writeFileSync(settingsPath, claudeConfig["settings.json"], { mode: 384 });
|
|
8348
8548
|
for (const [filename, content] of Object.entries(claudeConfig.statsig)) {
|
|
8349
|
-
const filePath =
|
|
8350
|
-
|
|
8549
|
+
const filePath = path18.join(statsigDir, filename);
|
|
8550
|
+
fs17.writeFileSync(filePath, content, { mode: 420 });
|
|
8551
|
+
}
|
|
8552
|
+
if (session.credentials.githubToken) {
|
|
8553
|
+
console.log("[AgentManager] EP1146: GitHub MCP server enabled with installation token");
|
|
8351
8554
|
}
|
|
8352
8555
|
console.log("[AgentManager] Wrote Claude config files");
|
|
8353
8556
|
} catch (configError) {
|
|
@@ -8542,17 +8745,17 @@ ${message}`;
|
|
|
8542
8745
|
if (agentProcess && !agentProcess.killed) {
|
|
8543
8746
|
console.log(`[AgentManager] Stopping session ${sessionId}`);
|
|
8544
8747
|
agentProcess.kill("SIGINT");
|
|
8545
|
-
await new Promise((
|
|
8748
|
+
await new Promise((resolve4) => {
|
|
8546
8749
|
const timeout = setTimeout(() => {
|
|
8547
8750
|
if (!agentProcess.killed) {
|
|
8548
8751
|
console.log(`[AgentManager] Force killing session ${sessionId}`);
|
|
8549
8752
|
agentProcess.kill("SIGTERM");
|
|
8550
8753
|
}
|
|
8551
|
-
|
|
8754
|
+
resolve4();
|
|
8552
8755
|
}, 5e3);
|
|
8553
8756
|
agentProcess.once("exit", () => {
|
|
8554
8757
|
clearTimeout(timeout);
|
|
8555
|
-
|
|
8758
|
+
resolve4();
|
|
8556
8759
|
});
|
|
8557
8760
|
});
|
|
8558
8761
|
}
|
|
@@ -8594,14 +8797,14 @@ ${message}`;
|
|
|
8594
8797
|
*/
|
|
8595
8798
|
async cleanupOrphanedProcesses() {
|
|
8596
8799
|
let cleaned = 0;
|
|
8597
|
-
if (!
|
|
8800
|
+
if (!fs17.existsSync(this.pidDir)) {
|
|
8598
8801
|
return { cleaned };
|
|
8599
8802
|
}
|
|
8600
|
-
const pidFiles =
|
|
8803
|
+
const pidFiles = fs17.readdirSync(this.pidDir).filter((f) => f.endsWith(".pid"));
|
|
8601
8804
|
for (const pidFile of pidFiles) {
|
|
8602
|
-
const pidPath =
|
|
8805
|
+
const pidPath = path18.join(this.pidDir, pidFile);
|
|
8603
8806
|
try {
|
|
8604
|
-
const pidStr =
|
|
8807
|
+
const pidStr = fs17.readFileSync(pidPath, "utf-8").trim();
|
|
8605
8808
|
const pid = parseInt(pidStr, 10);
|
|
8606
8809
|
if (!isNaN(pid)) {
|
|
8607
8810
|
try {
|
|
@@ -8612,7 +8815,7 @@ ${message}`;
|
|
|
8612
8815
|
} catch {
|
|
8613
8816
|
}
|
|
8614
8817
|
}
|
|
8615
|
-
|
|
8818
|
+
fs17.unlinkSync(pidPath);
|
|
8616
8819
|
} catch (error) {
|
|
8617
8820
|
console.warn(`[AgentManager] Error cleaning PID file ${pidFile}:`, error);
|
|
8618
8821
|
}
|
|
@@ -8626,17 +8829,17 @@ ${message}`;
|
|
|
8626
8829
|
* Write PID file for session tracking
|
|
8627
8830
|
*/
|
|
8628
8831
|
writePidFile(sessionId, pid) {
|
|
8629
|
-
const pidPath =
|
|
8630
|
-
|
|
8832
|
+
const pidPath = path18.join(this.pidDir, `${sessionId}.pid`);
|
|
8833
|
+
fs17.writeFileSync(pidPath, pid.toString());
|
|
8631
8834
|
}
|
|
8632
8835
|
/**
|
|
8633
8836
|
* Remove PID file for session
|
|
8634
8837
|
*/
|
|
8635
8838
|
removePidFile(sessionId) {
|
|
8636
|
-
const pidPath =
|
|
8839
|
+
const pidPath = path18.join(this.pidDir, `${sessionId}.pid`);
|
|
8637
8840
|
try {
|
|
8638
|
-
if (
|
|
8639
|
-
|
|
8841
|
+
if (fs17.existsSync(pidPath)) {
|
|
8842
|
+
fs17.unlinkSync(pidPath);
|
|
8640
8843
|
}
|
|
8641
8844
|
} catch {
|
|
8642
8845
|
}
|
|
@@ -8646,8 +8849,8 @@ ${message}`;
|
|
|
8646
8849
|
// src/utils/dev-server.ts
|
|
8647
8850
|
var import_child_process13 = require("child_process");
|
|
8648
8851
|
var import_core11 = __toESM(require_dist());
|
|
8649
|
-
var
|
|
8650
|
-
var
|
|
8852
|
+
var fs18 = __toESM(require("fs"));
|
|
8853
|
+
var path19 = __toESM(require("path"));
|
|
8651
8854
|
var MAX_RESTART_ATTEMPTS = 5;
|
|
8652
8855
|
var INITIAL_RESTART_DELAY_MS = 2e3;
|
|
8653
8856
|
var MAX_RESTART_DELAY_MS = 3e4;
|
|
@@ -8655,26 +8858,26 @@ var MAX_LOG_SIZE_BYTES = 5 * 1024 * 1024;
|
|
|
8655
8858
|
var NODE_MEMORY_LIMIT_MB = 2048;
|
|
8656
8859
|
var activeServers = /* @__PURE__ */ new Map();
|
|
8657
8860
|
function getLogsDir() {
|
|
8658
|
-
const logsDir =
|
|
8659
|
-
if (!
|
|
8660
|
-
|
|
8861
|
+
const logsDir = path19.join((0, import_core11.getConfigDir)(), "logs");
|
|
8862
|
+
if (!fs18.existsSync(logsDir)) {
|
|
8863
|
+
fs18.mkdirSync(logsDir, { recursive: true });
|
|
8661
8864
|
}
|
|
8662
8865
|
return logsDir;
|
|
8663
8866
|
}
|
|
8664
8867
|
function getLogFilePath(moduleUid) {
|
|
8665
|
-
return
|
|
8868
|
+
return path19.join(getLogsDir(), `dev-${moduleUid}.log`);
|
|
8666
8869
|
}
|
|
8667
8870
|
function rotateLogIfNeeded(logPath) {
|
|
8668
8871
|
try {
|
|
8669
|
-
if (
|
|
8670
|
-
const stats =
|
|
8872
|
+
if (fs18.existsSync(logPath)) {
|
|
8873
|
+
const stats = fs18.statSync(logPath);
|
|
8671
8874
|
if (stats.size > MAX_LOG_SIZE_BYTES) {
|
|
8672
8875
|
const backupPath = `${logPath}.1`;
|
|
8673
|
-
if (
|
|
8674
|
-
|
|
8876
|
+
if (fs18.existsSync(backupPath)) {
|
|
8877
|
+
fs18.unlinkSync(backupPath);
|
|
8675
8878
|
}
|
|
8676
|
-
|
|
8677
|
-
console.log(`[DevServer] EP932: Rotated log file for ${
|
|
8879
|
+
fs18.renameSync(logPath, backupPath);
|
|
8880
|
+
console.log(`[DevServer] EP932: Rotated log file for ${path19.basename(logPath)}`);
|
|
8678
8881
|
}
|
|
8679
8882
|
}
|
|
8680
8883
|
} catch (error) {
|
|
@@ -8687,7 +8890,7 @@ function writeToLog(logPath, line, isError = false) {
|
|
|
8687
8890
|
const prefix = isError ? "ERR" : "OUT";
|
|
8688
8891
|
const logLine = `[${timestamp}] [${prefix}] ${line}
|
|
8689
8892
|
`;
|
|
8690
|
-
|
|
8893
|
+
fs18.appendFileSync(logPath, logLine);
|
|
8691
8894
|
} catch {
|
|
8692
8895
|
}
|
|
8693
8896
|
}
|
|
@@ -8707,7 +8910,7 @@ async function killProcessOnPort(port) {
|
|
|
8707
8910
|
} catch {
|
|
8708
8911
|
}
|
|
8709
8912
|
}
|
|
8710
|
-
await new Promise((
|
|
8913
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
8711
8914
|
for (const pid of pids) {
|
|
8712
8915
|
try {
|
|
8713
8916
|
(0, import_child_process13.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
|
|
@@ -8716,7 +8919,7 @@ async function killProcessOnPort(port) {
|
|
|
8716
8919
|
} catch {
|
|
8717
8920
|
}
|
|
8718
8921
|
}
|
|
8719
|
-
await new Promise((
|
|
8922
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
8720
8923
|
const stillInUse = await isPortInUse(port);
|
|
8721
8924
|
if (stillInUse) {
|
|
8722
8925
|
console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
|
|
@@ -8736,7 +8939,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
|
|
|
8736
8939
|
if (await isPortInUse(port)) {
|
|
8737
8940
|
return true;
|
|
8738
8941
|
}
|
|
8739
|
-
await new Promise((
|
|
8942
|
+
await new Promise((resolve4) => setTimeout(resolve4, checkInterval));
|
|
8740
8943
|
}
|
|
8741
8944
|
return false;
|
|
8742
8945
|
}
|
|
@@ -8808,7 +9011,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
8808
9011
|
const delay = calculateRestartDelay(serverInfo.restartCount);
|
|
8809
9012
|
console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
|
|
8810
9013
|
writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
|
|
8811
|
-
await new Promise((
|
|
9014
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
8812
9015
|
if (!activeServers.has(moduleUid)) {
|
|
8813
9016
|
console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
|
|
8814
9017
|
return;
|
|
@@ -8866,8 +9069,8 @@ async function startDevServer(projectPath, port = 3e3, moduleUid = "default", op
|
|
|
8866
9069
|
});
|
|
8867
9070
|
injectedEnvVars = result.envVars;
|
|
8868
9071
|
console.log(`[DevServer] EP998: Loaded ${Object.keys(injectedEnvVars).length} env vars (from ${result.fromCache ? "cache" : "server"})`);
|
|
8869
|
-
const envFilePath =
|
|
8870
|
-
if (!
|
|
9072
|
+
const envFilePath = path19.join(projectPath, ".env");
|
|
9073
|
+
if (!fs18.existsSync(envFilePath) && Object.keys(injectedEnvVars).length > 0) {
|
|
8871
9074
|
console.log(`[DevServer] EP1004: .env file missing, writing ${Object.keys(injectedEnvVars).length} vars to ${envFilePath}`);
|
|
8872
9075
|
writeEnvFile(projectPath, injectedEnvVars);
|
|
8873
9076
|
}
|
|
@@ -8933,7 +9136,7 @@ async function stopDevServer(moduleUid) {
|
|
|
8933
9136
|
writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
|
|
8934
9137
|
}
|
|
8935
9138
|
serverInfo.process.kill("SIGTERM");
|
|
8936
|
-
await new Promise((
|
|
9139
|
+
await new Promise((resolve4) => setTimeout(resolve4, 2e3));
|
|
8937
9140
|
if (!serverInfo.process.killed) {
|
|
8938
9141
|
serverInfo.process.kill("SIGKILL");
|
|
8939
9142
|
}
|
|
@@ -8951,7 +9154,7 @@ async function restartDevServer(moduleUid) {
|
|
|
8951
9154
|
writeToLog(logFile, `Manual restart requested`, false);
|
|
8952
9155
|
}
|
|
8953
9156
|
await stopDevServer(moduleUid);
|
|
8954
|
-
await new Promise((
|
|
9157
|
+
await new Promise((resolve4) => setTimeout(resolve4, 1e3));
|
|
8955
9158
|
if (await isPortInUse(port)) {
|
|
8956
9159
|
await killProcessOnPort(port);
|
|
8957
9160
|
}
|
|
@@ -8973,19 +9176,19 @@ function getDevServerStatus() {
|
|
|
8973
9176
|
}
|
|
8974
9177
|
|
|
8975
9178
|
// src/utils/worktree.ts
|
|
8976
|
-
var
|
|
8977
|
-
var
|
|
9179
|
+
var path20 = __toESM(require("path"));
|
|
9180
|
+
var fs19 = __toESM(require("fs"));
|
|
8978
9181
|
var os7 = __toESM(require("os"));
|
|
8979
9182
|
var import_core12 = __toESM(require_dist());
|
|
8980
9183
|
function getEpisodaRoot2() {
|
|
8981
|
-
return process.env.EPISODA_ROOT ||
|
|
9184
|
+
return process.env.EPISODA_ROOT || path20.join(os7.homedir(), "episoda");
|
|
8982
9185
|
}
|
|
8983
9186
|
function getWorktreeInfo(moduleUid, workspaceSlug, projectSlug) {
|
|
8984
9187
|
const root = getEpisodaRoot2();
|
|
8985
|
-
const worktreePath =
|
|
9188
|
+
const worktreePath = path20.join(root, workspaceSlug, projectSlug, moduleUid);
|
|
8986
9189
|
return {
|
|
8987
9190
|
path: worktreePath,
|
|
8988
|
-
exists:
|
|
9191
|
+
exists: fs19.existsSync(worktreePath),
|
|
8989
9192
|
moduleUid
|
|
8990
9193
|
};
|
|
8991
9194
|
}
|
|
@@ -8999,15 +9202,15 @@ async function getWorktreeInfoForModule(moduleUid) {
|
|
|
8999
9202
|
return null;
|
|
9000
9203
|
}
|
|
9001
9204
|
const root = getEpisodaRoot2();
|
|
9002
|
-
const workspaceRoot =
|
|
9205
|
+
const workspaceRoot = path20.join(root, config.workspace_slug);
|
|
9003
9206
|
try {
|
|
9004
|
-
const entries =
|
|
9207
|
+
const entries = fs19.readdirSync(workspaceRoot, { withFileTypes: true });
|
|
9005
9208
|
for (const entry of entries) {
|
|
9006
9209
|
if (!entry.isDirectory()) {
|
|
9007
9210
|
continue;
|
|
9008
9211
|
}
|
|
9009
|
-
const worktreePath =
|
|
9010
|
-
if (
|
|
9212
|
+
const worktreePath = path20.join(workspaceRoot, entry.name, moduleUid);
|
|
9213
|
+
if (fs19.existsSync(worktreePath)) {
|
|
9011
9214
|
return {
|
|
9012
9215
|
path: worktreePath,
|
|
9013
9216
|
exists: true,
|
|
@@ -9024,61 +9227,61 @@ async function getWorktreeInfoForModule(moduleUid) {
|
|
|
9024
9227
|
}
|
|
9025
9228
|
|
|
9026
9229
|
// src/framework-detector.ts
|
|
9027
|
-
var
|
|
9028
|
-
var
|
|
9230
|
+
var fs20 = __toESM(require("fs"));
|
|
9231
|
+
var path21 = __toESM(require("path"));
|
|
9029
9232
|
function getInstallCommand(cwd) {
|
|
9030
|
-
if (
|
|
9233
|
+
if (fs20.existsSync(path21.join(cwd, "bun.lockb"))) {
|
|
9031
9234
|
return {
|
|
9032
9235
|
command: ["bun", "install"],
|
|
9033
9236
|
description: "Installing dependencies with bun",
|
|
9034
9237
|
detectedFrom: "bun.lockb"
|
|
9035
9238
|
};
|
|
9036
9239
|
}
|
|
9037
|
-
if (
|
|
9240
|
+
if (fs20.existsSync(path21.join(cwd, "pnpm-lock.yaml"))) {
|
|
9038
9241
|
return {
|
|
9039
9242
|
command: ["pnpm", "install"],
|
|
9040
9243
|
description: "Installing dependencies with pnpm",
|
|
9041
9244
|
detectedFrom: "pnpm-lock.yaml"
|
|
9042
9245
|
};
|
|
9043
9246
|
}
|
|
9044
|
-
if (
|
|
9247
|
+
if (fs20.existsSync(path21.join(cwd, "yarn.lock"))) {
|
|
9045
9248
|
return {
|
|
9046
9249
|
command: ["yarn", "install"],
|
|
9047
9250
|
description: "Installing dependencies with yarn",
|
|
9048
9251
|
detectedFrom: "yarn.lock"
|
|
9049
9252
|
};
|
|
9050
9253
|
}
|
|
9051
|
-
if (
|
|
9254
|
+
if (fs20.existsSync(path21.join(cwd, "package-lock.json"))) {
|
|
9052
9255
|
return {
|
|
9053
9256
|
command: ["npm", "ci"],
|
|
9054
9257
|
description: "Installing dependencies with npm ci",
|
|
9055
9258
|
detectedFrom: "package-lock.json"
|
|
9056
9259
|
};
|
|
9057
9260
|
}
|
|
9058
|
-
if (
|
|
9261
|
+
if (fs20.existsSync(path21.join(cwd, "package.json"))) {
|
|
9059
9262
|
return {
|
|
9060
9263
|
command: ["npm", "install"],
|
|
9061
9264
|
description: "Installing dependencies with npm",
|
|
9062
9265
|
detectedFrom: "package.json"
|
|
9063
9266
|
};
|
|
9064
9267
|
}
|
|
9065
|
-
if (
|
|
9268
|
+
if (fs20.existsSync(path21.join(cwd, "Pipfile.lock")) || fs20.existsSync(path21.join(cwd, "Pipfile"))) {
|
|
9066
9269
|
return {
|
|
9067
9270
|
command: ["pipenv", "install"],
|
|
9068
9271
|
description: "Installing dependencies with pipenv",
|
|
9069
|
-
detectedFrom:
|
|
9272
|
+
detectedFrom: fs20.existsSync(path21.join(cwd, "Pipfile.lock")) ? "Pipfile.lock" : "Pipfile"
|
|
9070
9273
|
};
|
|
9071
9274
|
}
|
|
9072
|
-
if (
|
|
9275
|
+
if (fs20.existsSync(path21.join(cwd, "poetry.lock"))) {
|
|
9073
9276
|
return {
|
|
9074
9277
|
command: ["poetry", "install"],
|
|
9075
9278
|
description: "Installing dependencies with poetry",
|
|
9076
9279
|
detectedFrom: "poetry.lock"
|
|
9077
9280
|
};
|
|
9078
9281
|
}
|
|
9079
|
-
if (
|
|
9080
|
-
const pyprojectPath =
|
|
9081
|
-
const content =
|
|
9282
|
+
if (fs20.existsSync(path21.join(cwd, "pyproject.toml"))) {
|
|
9283
|
+
const pyprojectPath = path21.join(cwd, "pyproject.toml");
|
|
9284
|
+
const content = fs20.readFileSync(pyprojectPath, "utf-8");
|
|
9082
9285
|
if (content.includes("[tool.poetry]")) {
|
|
9083
9286
|
return {
|
|
9084
9287
|
command: ["poetry", "install"],
|
|
@@ -9087,41 +9290,41 @@ function getInstallCommand(cwd) {
|
|
|
9087
9290
|
};
|
|
9088
9291
|
}
|
|
9089
9292
|
}
|
|
9090
|
-
if (
|
|
9293
|
+
if (fs20.existsSync(path21.join(cwd, "requirements.txt"))) {
|
|
9091
9294
|
return {
|
|
9092
9295
|
command: ["pip", "install", "-r", "requirements.txt"],
|
|
9093
9296
|
description: "Installing dependencies with pip",
|
|
9094
9297
|
detectedFrom: "requirements.txt"
|
|
9095
9298
|
};
|
|
9096
9299
|
}
|
|
9097
|
-
if (
|
|
9300
|
+
if (fs20.existsSync(path21.join(cwd, "Gemfile.lock")) || fs20.existsSync(path21.join(cwd, "Gemfile"))) {
|
|
9098
9301
|
return {
|
|
9099
9302
|
command: ["bundle", "install"],
|
|
9100
9303
|
description: "Installing dependencies with bundler",
|
|
9101
|
-
detectedFrom:
|
|
9304
|
+
detectedFrom: fs20.existsSync(path21.join(cwd, "Gemfile.lock")) ? "Gemfile.lock" : "Gemfile"
|
|
9102
9305
|
};
|
|
9103
9306
|
}
|
|
9104
|
-
if (
|
|
9307
|
+
if (fs20.existsSync(path21.join(cwd, "go.sum")) || fs20.existsSync(path21.join(cwd, "go.mod"))) {
|
|
9105
9308
|
return {
|
|
9106
9309
|
command: ["go", "mod", "download"],
|
|
9107
9310
|
description: "Downloading Go modules",
|
|
9108
|
-
detectedFrom:
|
|
9311
|
+
detectedFrom: fs20.existsSync(path21.join(cwd, "go.sum")) ? "go.sum" : "go.mod"
|
|
9109
9312
|
};
|
|
9110
9313
|
}
|
|
9111
|
-
if (
|
|
9314
|
+
if (fs20.existsSync(path21.join(cwd, "Cargo.lock")) || fs20.existsSync(path21.join(cwd, "Cargo.toml"))) {
|
|
9112
9315
|
return {
|
|
9113
9316
|
command: ["cargo", "build"],
|
|
9114
9317
|
description: "Building Rust project (downloads dependencies)",
|
|
9115
|
-
detectedFrom:
|
|
9318
|
+
detectedFrom: fs20.existsSync(path21.join(cwd, "Cargo.lock")) ? "Cargo.lock" : "Cargo.toml"
|
|
9116
9319
|
};
|
|
9117
9320
|
}
|
|
9118
9321
|
return null;
|
|
9119
9322
|
}
|
|
9120
9323
|
|
|
9121
9324
|
// src/daemon/daemon-process.ts
|
|
9122
|
-
var
|
|
9325
|
+
var fs21 = __toESM(require("fs"));
|
|
9123
9326
|
var os8 = __toESM(require("os"));
|
|
9124
|
-
var
|
|
9327
|
+
var path22 = __toESM(require("path"));
|
|
9125
9328
|
var packageJson = require_package();
|
|
9126
9329
|
async function ensureValidToken(config, bufferMs = 5 * 60 * 1e3) {
|
|
9127
9330
|
const now = Date.now();
|
|
@@ -9371,7 +9574,7 @@ var Daemon = class _Daemon {
|
|
|
9371
9574
|
if (attempt < MAX_RETRIES) {
|
|
9372
9575
|
const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
|
|
9373
9576
|
console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
|
|
9374
|
-
await new Promise((
|
|
9577
|
+
await new Promise((resolve4) => setTimeout(resolve4, delay));
|
|
9375
9578
|
await this.disconnectProject(projectPath);
|
|
9376
9579
|
}
|
|
9377
9580
|
}
|
|
@@ -9572,8 +9775,8 @@ var Daemon = class _Daemon {
|
|
|
9572
9775
|
}
|
|
9573
9776
|
}
|
|
9574
9777
|
let releaseLock;
|
|
9575
|
-
const lockPromise = new Promise((
|
|
9576
|
-
releaseLock =
|
|
9778
|
+
const lockPromise = new Promise((resolve4) => {
|
|
9779
|
+
releaseLock = resolve4;
|
|
9577
9780
|
});
|
|
9578
9781
|
this.tunnelOperationLocks.set(moduleUid, lockPromise);
|
|
9579
9782
|
try {
|
|
@@ -9599,7 +9802,7 @@ var Daemon = class _Daemon {
|
|
|
9599
9802
|
const maxWait = 35e3;
|
|
9600
9803
|
const startTime = Date.now();
|
|
9601
9804
|
while (this.pendingConnections.has(projectPath) && Date.now() - startTime < maxWait) {
|
|
9602
|
-
await new Promise((
|
|
9805
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
9603
9806
|
}
|
|
9604
9807
|
if (this.liveConnections.has(projectPath)) {
|
|
9605
9808
|
console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
|
|
@@ -9647,7 +9850,7 @@ var Daemon = class _Daemon {
|
|
|
9647
9850
|
client.updateActivity();
|
|
9648
9851
|
try {
|
|
9649
9852
|
const gitCmd = message.command;
|
|
9650
|
-
const bareRepoPath =
|
|
9853
|
+
const bareRepoPath = path22.join(projectPath, ".bare");
|
|
9651
9854
|
const cwd = gitCmd.worktreePath || bareRepoPath;
|
|
9652
9855
|
if (gitCmd.worktreePath) {
|
|
9653
9856
|
console.log(`[Daemon] Routing command to worktree: ${gitCmd.worktreePath}`);
|
|
@@ -10191,21 +10394,21 @@ var Daemon = class _Daemon {
|
|
|
10191
10394
|
let daemonPid;
|
|
10192
10395
|
try {
|
|
10193
10396
|
const pidPath = getPidFilePath();
|
|
10194
|
-
if (
|
|
10195
|
-
const pidStr =
|
|
10397
|
+
if (fs21.existsSync(pidPath)) {
|
|
10398
|
+
const pidStr = fs21.readFileSync(pidPath, "utf-8").trim();
|
|
10196
10399
|
daemonPid = parseInt(pidStr, 10);
|
|
10197
10400
|
}
|
|
10198
10401
|
} catch (pidError) {
|
|
10199
10402
|
console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError);
|
|
10200
10403
|
}
|
|
10201
|
-
const authSuccessPromise = new Promise((
|
|
10404
|
+
const authSuccessPromise = new Promise((resolve4, reject) => {
|
|
10202
10405
|
const AUTH_TIMEOUT = 3e4;
|
|
10203
10406
|
const timeout = setTimeout(() => {
|
|
10204
10407
|
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
10205
10408
|
}, AUTH_TIMEOUT);
|
|
10206
10409
|
const authHandler = () => {
|
|
10207
10410
|
clearTimeout(timeout);
|
|
10208
|
-
|
|
10411
|
+
resolve4();
|
|
10209
10412
|
};
|
|
10210
10413
|
client.once("auth_success", authHandler);
|
|
10211
10414
|
const errorHandler = (message) => {
|
|
@@ -10273,28 +10476,28 @@ var Daemon = class _Daemon {
|
|
|
10273
10476
|
* - workDir: The directory to run git commands in (cwd)
|
|
10274
10477
|
*/
|
|
10275
10478
|
getGitDirs(projectPath) {
|
|
10276
|
-
const bareDir =
|
|
10277
|
-
const gitPath =
|
|
10278
|
-
if (
|
|
10479
|
+
const bareDir = path22.join(projectPath, ".bare");
|
|
10480
|
+
const gitPath = path22.join(projectPath, ".git");
|
|
10481
|
+
if (fs21.existsSync(bareDir) && fs21.statSync(bareDir).isDirectory()) {
|
|
10279
10482
|
return { gitDir: bareDir, workDir: projectPath };
|
|
10280
10483
|
}
|
|
10281
|
-
if (
|
|
10484
|
+
if (fs21.existsSync(gitPath) && fs21.statSync(gitPath).isDirectory()) {
|
|
10282
10485
|
return { gitDir: null, workDir: projectPath };
|
|
10283
10486
|
}
|
|
10284
|
-
if (
|
|
10487
|
+
if (fs21.existsSync(gitPath) && fs21.statSync(gitPath).isFile()) {
|
|
10285
10488
|
return { gitDir: null, workDir: projectPath };
|
|
10286
10489
|
}
|
|
10287
|
-
const entries =
|
|
10490
|
+
const entries = fs21.readdirSync(projectPath, { withFileTypes: true });
|
|
10288
10491
|
for (const entry of entries) {
|
|
10289
10492
|
if (entry.isDirectory() && entry.name.startsWith("EP")) {
|
|
10290
|
-
const worktreePath =
|
|
10291
|
-
const worktreeGit =
|
|
10292
|
-
if (
|
|
10493
|
+
const worktreePath = path22.join(projectPath, entry.name);
|
|
10494
|
+
const worktreeGit = path22.join(worktreePath, ".git");
|
|
10495
|
+
if (fs21.existsSync(worktreeGit)) {
|
|
10293
10496
|
return { gitDir: null, workDir: worktreePath };
|
|
10294
10497
|
}
|
|
10295
10498
|
}
|
|
10296
10499
|
}
|
|
10297
|
-
if (
|
|
10500
|
+
if (fs21.existsSync(bareDir)) {
|
|
10298
10501
|
return { gitDir: bareDir, workDir: projectPath };
|
|
10299
10502
|
}
|
|
10300
10503
|
return { gitDir: null, workDir: projectPath };
|
|
@@ -10358,24 +10561,24 @@ var Daemon = class _Daemon {
|
|
|
10358
10561
|
async installGitHooks(projectPath) {
|
|
10359
10562
|
const hooks = ["post-checkout", "pre-commit", "post-commit"];
|
|
10360
10563
|
let hooksDir;
|
|
10361
|
-
const bareHooksDir =
|
|
10362
|
-
const gitHooksDir =
|
|
10363
|
-
if (
|
|
10564
|
+
const bareHooksDir = path22.join(projectPath, ".bare", "hooks");
|
|
10565
|
+
const gitHooksDir = path22.join(projectPath, ".git", "hooks");
|
|
10566
|
+
if (fs21.existsSync(bareHooksDir)) {
|
|
10364
10567
|
hooksDir = bareHooksDir;
|
|
10365
|
-
} else if (
|
|
10568
|
+
} else if (fs21.existsSync(gitHooksDir) && fs21.statSync(path22.join(projectPath, ".git")).isDirectory()) {
|
|
10366
10569
|
hooksDir = gitHooksDir;
|
|
10367
10570
|
} else {
|
|
10368
|
-
const parentBareHooks =
|
|
10369
|
-
if (
|
|
10571
|
+
const parentBareHooks = path22.join(projectPath, "..", ".bare", "hooks");
|
|
10572
|
+
if (fs21.existsSync(parentBareHooks)) {
|
|
10370
10573
|
hooksDir = parentBareHooks;
|
|
10371
10574
|
} else {
|
|
10372
10575
|
console.warn(`[Daemon] Hooks directory not found for: ${projectPath}`);
|
|
10373
10576
|
return;
|
|
10374
10577
|
}
|
|
10375
10578
|
}
|
|
10376
|
-
if (!
|
|
10579
|
+
if (!fs21.existsSync(hooksDir)) {
|
|
10377
10580
|
try {
|
|
10378
|
-
|
|
10581
|
+
fs21.mkdirSync(hooksDir, { recursive: true });
|
|
10379
10582
|
} catch (error) {
|
|
10380
10583
|
console.warn(`[Daemon] Hooks directory not found and could not create: ${hooksDir}`);
|
|
10381
10584
|
return;
|
|
@@ -10383,20 +10586,20 @@ var Daemon = class _Daemon {
|
|
|
10383
10586
|
}
|
|
10384
10587
|
for (const hookName of hooks) {
|
|
10385
10588
|
try {
|
|
10386
|
-
const hookPath =
|
|
10387
|
-
const bundledHookPath =
|
|
10388
|
-
if (!
|
|
10589
|
+
const hookPath = path22.join(hooksDir, hookName);
|
|
10590
|
+
const bundledHookPath = path22.join(__dirname, "..", "hooks", hookName);
|
|
10591
|
+
if (!fs21.existsSync(bundledHookPath)) {
|
|
10389
10592
|
console.warn(`[Daemon] Bundled hook not found: ${bundledHookPath}`);
|
|
10390
10593
|
continue;
|
|
10391
10594
|
}
|
|
10392
|
-
const hookContent =
|
|
10393
|
-
if (
|
|
10394
|
-
const existingContent =
|
|
10595
|
+
const hookContent = fs21.readFileSync(bundledHookPath, "utf-8");
|
|
10596
|
+
if (fs21.existsSync(hookPath)) {
|
|
10597
|
+
const existingContent = fs21.readFileSync(hookPath, "utf-8");
|
|
10395
10598
|
if (existingContent === hookContent) {
|
|
10396
10599
|
continue;
|
|
10397
10600
|
}
|
|
10398
10601
|
}
|
|
10399
|
-
|
|
10602
|
+
fs21.writeFileSync(hookPath, hookContent, { mode: 493 });
|
|
10400
10603
|
console.log(`[Daemon] Installed git hook: ${hookName}`);
|
|
10401
10604
|
} catch (error) {
|
|
10402
10605
|
console.warn(`[Daemon] Failed to install ${hookName} hook:`, error instanceof Error ? error.message : error);
|
|
@@ -11500,8 +11703,8 @@ var Daemon = class _Daemon {
|
|
|
11500
11703
|
await this.shutdown();
|
|
11501
11704
|
try {
|
|
11502
11705
|
const pidPath = getPidFilePath();
|
|
11503
|
-
if (
|
|
11504
|
-
|
|
11706
|
+
if (fs21.existsSync(pidPath)) {
|
|
11707
|
+
fs21.unlinkSync(pidPath);
|
|
11505
11708
|
console.log("[Daemon] PID file cleaned up");
|
|
11506
11709
|
}
|
|
11507
11710
|
} catch (error) {
|