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