episoda 0.2.112 → 0.2.114
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.
|
@@ -2165,7 +2165,7 @@ var require_websocket_client = __commonJS({
|
|
|
2165
2165
|
clearTimeout(this.reconnectTimeout);
|
|
2166
2166
|
this.reconnectTimeout = void 0;
|
|
2167
2167
|
}
|
|
2168
|
-
return new Promise((
|
|
2168
|
+
return new Promise((resolve5, reject) => {
|
|
2169
2169
|
const connectionTimeout = setTimeout(() => {
|
|
2170
2170
|
if (this.ws) {
|
|
2171
2171
|
this.ws.terminate();
|
|
@@ -2194,7 +2194,7 @@ var require_websocket_client = __commonJS({
|
|
|
2194
2194
|
daemonPid: this.daemonPid
|
|
2195
2195
|
});
|
|
2196
2196
|
this.startHeartbeat();
|
|
2197
|
-
|
|
2197
|
+
resolve5();
|
|
2198
2198
|
});
|
|
2199
2199
|
this.ws.on("pong", () => {
|
|
2200
2200
|
if (this.heartbeatTimeoutTimer) {
|
|
@@ -2325,13 +2325,13 @@ var require_websocket_client = __commonJS({
|
|
|
2325
2325
|
console.warn("[EpisodaClient] Cannot send - WebSocket not connected");
|
|
2326
2326
|
return false;
|
|
2327
2327
|
}
|
|
2328
|
-
return new Promise((
|
|
2328
|
+
return new Promise((resolve5) => {
|
|
2329
2329
|
this.ws.send(JSON.stringify(message), (error) => {
|
|
2330
2330
|
if (error) {
|
|
2331
2331
|
console.error("[EpisodaClient] Failed to send message:", error);
|
|
2332
|
-
|
|
2332
|
+
resolve5(false);
|
|
2333
2333
|
} else {
|
|
2334
|
-
|
|
2334
|
+
resolve5(true);
|
|
2335
2335
|
}
|
|
2336
2336
|
});
|
|
2337
2337
|
});
|
|
@@ -2815,7 +2815,7 @@ var require_package = __commonJS({
|
|
|
2815
2815
|
"package.json"(exports2, module2) {
|
|
2816
2816
|
module2.exports = {
|
|
2817
2817
|
name: "episoda",
|
|
2818
|
-
version: "0.2.
|
|
2818
|
+
version: "0.2.113",
|
|
2819
2819
|
description: "CLI tool for Episoda local development workflow orchestration",
|
|
2820
2820
|
main: "dist/index.js",
|
|
2821
2821
|
types: "dist/index.d.ts",
|
|
@@ -3118,10 +3118,10 @@ var IPCServer = class {
|
|
|
3118
3118
|
this.server = net.createServer((socket) => {
|
|
3119
3119
|
this.handleConnection(socket);
|
|
3120
3120
|
});
|
|
3121
|
-
return new Promise((
|
|
3121
|
+
return new Promise((resolve5, reject) => {
|
|
3122
3122
|
this.server.listen(socketPath, () => {
|
|
3123
3123
|
fs3.chmodSync(socketPath, 384);
|
|
3124
|
-
|
|
3124
|
+
resolve5();
|
|
3125
3125
|
});
|
|
3126
3126
|
this.server.on("error", reject);
|
|
3127
3127
|
});
|
|
@@ -3132,12 +3132,12 @@ var IPCServer = class {
|
|
|
3132
3132
|
async stop() {
|
|
3133
3133
|
if (!this.server) return;
|
|
3134
3134
|
const socketPath = getSocketPath();
|
|
3135
|
-
return new Promise((
|
|
3135
|
+
return new Promise((resolve5) => {
|
|
3136
3136
|
this.server.close(() => {
|
|
3137
3137
|
if (fs3.existsSync(socketPath)) {
|
|
3138
3138
|
fs3.unlinkSync(socketPath);
|
|
3139
3139
|
}
|
|
3140
|
-
|
|
3140
|
+
resolve5();
|
|
3141
3141
|
});
|
|
3142
3142
|
});
|
|
3143
3143
|
}
|
|
@@ -3997,7 +3997,7 @@ async function handleExec(command, projectPath) {
|
|
|
3997
3997
|
env = {}
|
|
3998
3998
|
} = command;
|
|
3999
3999
|
const effectiveTimeout = Math.min(Math.max(timeout, 1e3), MAX_TIMEOUT);
|
|
4000
|
-
return new Promise((
|
|
4000
|
+
return new Promise((resolve5) => {
|
|
4001
4001
|
let stdout = "";
|
|
4002
4002
|
let stderr = "";
|
|
4003
4003
|
let timedOut = false;
|
|
@@ -4005,7 +4005,7 @@ async function handleExec(command, projectPath) {
|
|
|
4005
4005
|
const done = (result) => {
|
|
4006
4006
|
if (resolved) return;
|
|
4007
4007
|
resolved = true;
|
|
4008
|
-
|
|
4008
|
+
resolve5(result);
|
|
4009
4009
|
};
|
|
4010
4010
|
try {
|
|
4011
4011
|
const proc = (0, import_child_process3.spawn)(cmd, {
|
|
@@ -4535,7 +4535,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4535
4535
|
const lockContent = fs6.readFileSync(lockPath, "utf-8").trim();
|
|
4536
4536
|
const lockPid = parseInt(lockContent, 10);
|
|
4537
4537
|
if (!isNaN(lockPid) && this.isProcessRunning(lockPid)) {
|
|
4538
|
-
await new Promise((
|
|
4538
|
+
await new Promise((resolve5) => setTimeout(resolve5, retryInterval));
|
|
4539
4539
|
continue;
|
|
4540
4540
|
}
|
|
4541
4541
|
} catch {
|
|
@@ -4549,7 +4549,7 @@ var WorktreeManager = class _WorktreeManager {
|
|
|
4549
4549
|
} catch {
|
|
4550
4550
|
continue;
|
|
4551
4551
|
}
|
|
4552
|
-
await new Promise((
|
|
4552
|
+
await new Promise((resolve5) => setTimeout(resolve5, retryInterval));
|
|
4553
4553
|
continue;
|
|
4554
4554
|
}
|
|
4555
4555
|
throw err;
|
|
@@ -4890,18 +4890,18 @@ var import_core7 = __toESM(require_dist());
|
|
|
4890
4890
|
// src/utils/port-check.ts
|
|
4891
4891
|
var net2 = __toESM(require("net"));
|
|
4892
4892
|
async function isPortInUse(port) {
|
|
4893
|
-
return new Promise((
|
|
4893
|
+
return new Promise((resolve5) => {
|
|
4894
4894
|
const server = net2.createServer();
|
|
4895
4895
|
server.once("error", (err) => {
|
|
4896
4896
|
if (err.code === "EADDRINUSE") {
|
|
4897
|
-
|
|
4897
|
+
resolve5(true);
|
|
4898
4898
|
} else {
|
|
4899
|
-
|
|
4899
|
+
resolve5(false);
|
|
4900
4900
|
}
|
|
4901
4901
|
});
|
|
4902
4902
|
server.once("listening", () => {
|
|
4903
4903
|
server.close();
|
|
4904
|
-
|
|
4904
|
+
resolve5(false);
|
|
4905
4905
|
});
|
|
4906
4906
|
server.listen(port);
|
|
4907
4907
|
});
|
|
@@ -5337,7 +5337,7 @@ var DevServerRegistry = class {
|
|
|
5337
5337
|
return killed;
|
|
5338
5338
|
}
|
|
5339
5339
|
wait(ms) {
|
|
5340
|
-
return new Promise((
|
|
5340
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
5341
5341
|
}
|
|
5342
5342
|
};
|
|
5343
5343
|
var registryInstance = null;
|
|
@@ -5713,7 +5713,7 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5713
5713
|
return Math.min(delay, DEV_SERVER_CONSTANTS.MAX_RESTART_DELAY_MS);
|
|
5714
5714
|
}
|
|
5715
5715
|
async checkHealth(port) {
|
|
5716
|
-
return new Promise((
|
|
5716
|
+
return new Promise((resolve5) => {
|
|
5717
5717
|
const req = http.request(
|
|
5718
5718
|
{
|
|
5719
5719
|
hostname: "localhost",
|
|
@@ -5722,12 +5722,12 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5722
5722
|
method: "HEAD",
|
|
5723
5723
|
timeout: DEV_SERVER_CONSTANTS.HEALTH_CHECK_TIMEOUT_MS
|
|
5724
5724
|
},
|
|
5725
|
-
() =>
|
|
5725
|
+
() => resolve5(true)
|
|
5726
5726
|
);
|
|
5727
|
-
req.on("error", () =>
|
|
5727
|
+
req.on("error", () => resolve5(false));
|
|
5728
5728
|
req.on("timeout", () => {
|
|
5729
5729
|
req.destroy();
|
|
5730
|
-
|
|
5730
|
+
resolve5(false);
|
|
5731
5731
|
});
|
|
5732
5732
|
req.end();
|
|
5733
5733
|
});
|
|
@@ -5744,7 +5744,7 @@ var DevServerRunner = class extends import_events.EventEmitter {
|
|
|
5744
5744
|
return false;
|
|
5745
5745
|
}
|
|
5746
5746
|
wait(ms) {
|
|
5747
|
-
return new Promise((
|
|
5747
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
5748
5748
|
}
|
|
5749
5749
|
getLogsDir() {
|
|
5750
5750
|
const logsDir = path11.join((0, import_core7.getConfigDir)(), "logs");
|
|
@@ -5877,7 +5877,7 @@ function getDownloadUrl() {
|
|
|
5877
5877
|
return platformUrls[arch3] || null;
|
|
5878
5878
|
}
|
|
5879
5879
|
async function downloadFile(url, destPath) {
|
|
5880
|
-
return new Promise((
|
|
5880
|
+
return new Promise((resolve5, reject) => {
|
|
5881
5881
|
const followRedirect = (currentUrl, redirectCount = 0) => {
|
|
5882
5882
|
if (redirectCount > 5) {
|
|
5883
5883
|
reject(new Error("Too many redirects"));
|
|
@@ -5907,7 +5907,7 @@ async function downloadFile(url, destPath) {
|
|
|
5907
5907
|
response.pipe(file);
|
|
5908
5908
|
file.on("finish", () => {
|
|
5909
5909
|
file.close();
|
|
5910
|
-
|
|
5910
|
+
resolve5();
|
|
5911
5911
|
});
|
|
5912
5912
|
file.on("error", (err) => {
|
|
5913
5913
|
fs11.unlinkSync(destPath);
|
|
@@ -6285,10 +6285,10 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6285
6285
|
const isTracked = Array.from(this.tunnelStates.values()).some((s) => s.info.pid === pid);
|
|
6286
6286
|
console.log(`[Tunnel] EP904: Found cloudflared PID ${pid} on port ${port} (tracked: ${isTracked})`);
|
|
6287
6287
|
this.killByPid(pid, "SIGTERM");
|
|
6288
|
-
await new Promise((
|
|
6288
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
6289
6289
|
if (this.isProcessRunning(pid)) {
|
|
6290
6290
|
this.killByPid(pid, "SIGKILL");
|
|
6291
|
-
await new Promise((
|
|
6291
|
+
await new Promise((resolve5) => setTimeout(resolve5, 200));
|
|
6292
6292
|
}
|
|
6293
6293
|
killed.push(pid);
|
|
6294
6294
|
}
|
|
@@ -6322,7 +6322,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6322
6322
|
if (!this.tunnelStates.has(moduleUid)) {
|
|
6323
6323
|
console.log(`[Tunnel] EP877: Found orphaned process PID ${pid} for ${moduleUid}, killing...`);
|
|
6324
6324
|
this.killByPid(pid, "SIGTERM");
|
|
6325
|
-
await new Promise((
|
|
6325
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
6326
6326
|
if (this.isProcessRunning(pid)) {
|
|
6327
6327
|
this.killByPid(pid, "SIGKILL");
|
|
6328
6328
|
}
|
|
@@ -6337,7 +6337,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6337
6337
|
if (!trackedPids.includes(pid) && !cleaned.includes(pid)) {
|
|
6338
6338
|
console.log(`[Tunnel] EP877: Found untracked cloudflared process PID ${pid}, killing...`);
|
|
6339
6339
|
this.killByPid(pid, "SIGTERM");
|
|
6340
|
-
await new Promise((
|
|
6340
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
6341
6341
|
if (this.isProcessRunning(pid)) {
|
|
6342
6342
|
this.killByPid(pid, "SIGKILL");
|
|
6343
6343
|
}
|
|
@@ -6427,7 +6427,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6427
6427
|
return { success: false, error: `Failed to get cloudflared: ${errorMessage}` };
|
|
6428
6428
|
}
|
|
6429
6429
|
}
|
|
6430
|
-
return new Promise((
|
|
6430
|
+
return new Promise((resolve5) => {
|
|
6431
6431
|
const tunnelInfo = {
|
|
6432
6432
|
moduleUid,
|
|
6433
6433
|
url: previewUrl || "",
|
|
@@ -6493,7 +6493,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6493
6493
|
moduleUid,
|
|
6494
6494
|
url: tunnelInfo.url
|
|
6495
6495
|
});
|
|
6496
|
-
|
|
6496
|
+
resolve5({ success: true, url: tunnelInfo.url });
|
|
6497
6497
|
}
|
|
6498
6498
|
};
|
|
6499
6499
|
process2.stderr?.on("data", (data) => {
|
|
@@ -6520,7 +6520,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6520
6520
|
onStatusChange?.("error", errorMsg);
|
|
6521
6521
|
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
6522
6522
|
}
|
|
6523
|
-
|
|
6523
|
+
resolve5({ success: false, error: errorMsg });
|
|
6524
6524
|
} else if (wasConnected) {
|
|
6525
6525
|
if (currentState && !currentState.intentionallyStopped) {
|
|
6526
6526
|
console.log(`[Tunnel] EP948: Named tunnel ${moduleUid} crashed unexpectedly, attempting reconnect...`);
|
|
@@ -6551,7 +6551,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6551
6551
|
this.emitEvent({ type: "error", moduleUid, error: error.message });
|
|
6552
6552
|
}
|
|
6553
6553
|
if (!connected) {
|
|
6554
|
-
|
|
6554
|
+
resolve5({ success: false, error: error.message });
|
|
6555
6555
|
}
|
|
6556
6556
|
});
|
|
6557
6557
|
setTimeout(() => {
|
|
@@ -6574,7 +6574,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6574
6574
|
onStatusChange?.("error", errorMsg);
|
|
6575
6575
|
this.emitEvent({ type: "error", moduleUid, error: errorMsg });
|
|
6576
6576
|
}
|
|
6577
|
-
|
|
6577
|
+
resolve5({ success: false, error: errorMsg });
|
|
6578
6578
|
}
|
|
6579
6579
|
}, TUNNEL_TIMEOUTS.NAMED_TUNNEL_CONNECT);
|
|
6580
6580
|
});
|
|
@@ -6635,7 +6635,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6635
6635
|
if (orphanPid && this.isProcessRunning(orphanPid)) {
|
|
6636
6636
|
console.log(`[Tunnel] EP877: Killing orphaned process ${orphanPid} for ${moduleUid} before starting new tunnel`);
|
|
6637
6637
|
this.killByPid(orphanPid, "SIGTERM");
|
|
6638
|
-
await new Promise((
|
|
6638
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
6639
6639
|
if (this.isProcessRunning(orphanPid)) {
|
|
6640
6640
|
this.killByPid(orphanPid, "SIGKILL");
|
|
6641
6641
|
}
|
|
@@ -6644,7 +6644,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6644
6644
|
const killedOnPort = await this.killCloudflaredOnPort(port);
|
|
6645
6645
|
if (killedOnPort.length > 0) {
|
|
6646
6646
|
console.log(`[Tunnel] EP904: Pre-start port cleanup killed ${killedOnPort.length} process(es) on port ${port}`);
|
|
6647
|
-
await new Promise((
|
|
6647
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
6648
6648
|
}
|
|
6649
6649
|
const cleanup = await this.cleanupOrphanedProcesses();
|
|
6650
6650
|
if (cleanup.cleaned > 0) {
|
|
@@ -6684,7 +6684,7 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6684
6684
|
if (orphanPid && this.isProcessRunning(orphanPid)) {
|
|
6685
6685
|
console.log(`[Tunnel] EP877: Stopping orphaned process ${orphanPid} for ${moduleUid} via PID file`);
|
|
6686
6686
|
this.killByPid(orphanPid, "SIGTERM");
|
|
6687
|
-
await new Promise((
|
|
6687
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
6688
6688
|
if (this.isProcessRunning(orphanPid)) {
|
|
6689
6689
|
this.killByPid(orphanPid, "SIGKILL");
|
|
6690
6690
|
}
|
|
@@ -6700,16 +6700,16 @@ var TunnelManager = class extends import_events2.EventEmitter {
|
|
|
6700
6700
|
const tunnel = state.info;
|
|
6701
6701
|
if (tunnel.process && !tunnel.process.killed) {
|
|
6702
6702
|
tunnel.process.kill("SIGTERM");
|
|
6703
|
-
await new Promise((
|
|
6703
|
+
await new Promise((resolve5) => {
|
|
6704
6704
|
const timeout = setTimeout(() => {
|
|
6705
6705
|
if (tunnel.process && !tunnel.process.killed) {
|
|
6706
6706
|
tunnel.process.kill("SIGKILL");
|
|
6707
6707
|
}
|
|
6708
|
-
|
|
6708
|
+
resolve5();
|
|
6709
6709
|
}, 3e3);
|
|
6710
6710
|
tunnel.process.once("exit", () => {
|
|
6711
6711
|
clearTimeout(timeout);
|
|
6712
|
-
|
|
6712
|
+
resolve5();
|
|
6713
6713
|
});
|
|
6714
6714
|
});
|
|
6715
6715
|
}
|
|
@@ -6993,7 +6993,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
6993
6993
|
for (let attempt = 1; attempt <= MAX_TUNNEL_RETRIES; attempt++) {
|
|
6994
6994
|
if (attempt > 1) {
|
|
6995
6995
|
console.log(`[PreviewManager] Retrying tunnel for ${moduleUid} (attempt ${attempt}/${MAX_TUNNEL_RETRIES})...`);
|
|
6996
|
-
await new Promise((
|
|
6996
|
+
await new Promise((resolve5) => setTimeout(resolve5, 2e3));
|
|
6997
6997
|
}
|
|
6998
6998
|
tunnelResult = await this.tunnel.startTunnel({
|
|
6999
6999
|
moduleUid,
|
|
@@ -7114,7 +7114,7 @@ var PreviewManager = class extends import_events3.EventEmitter {
|
|
|
7114
7114
|
}
|
|
7115
7115
|
console.log(`[PreviewManager] Restarting preview for ${moduleUid}`);
|
|
7116
7116
|
await this.stopPreview(moduleUid);
|
|
7117
|
-
await new Promise((
|
|
7117
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
7118
7118
|
return this.startPreview({
|
|
7119
7119
|
moduleUid,
|
|
7120
7120
|
worktreePath: state.worktreePath,
|
|
@@ -8589,8 +8589,8 @@ var AgentManager = class {
|
|
|
8589
8589
|
async withConfigLock(fn) {
|
|
8590
8590
|
const previousLock = this.configWriteLock;
|
|
8591
8591
|
let releaseLock;
|
|
8592
|
-
this.configWriteLock = new Promise((
|
|
8593
|
-
releaseLock =
|
|
8592
|
+
this.configWriteLock = new Promise((resolve5) => {
|
|
8593
|
+
releaseLock = resolve5;
|
|
8594
8594
|
});
|
|
8595
8595
|
try {
|
|
8596
8596
|
await previousLock;
|
|
@@ -8599,6 +8599,82 @@ var AgentManager = class {
|
|
|
8599
8599
|
releaseLock();
|
|
8600
8600
|
}
|
|
8601
8601
|
}
|
|
8602
|
+
/**
|
|
8603
|
+
* EP1233: Get list of MCP servers to register for a session
|
|
8604
|
+
*
|
|
8605
|
+
* Returns MCP server configurations based on:
|
|
8606
|
+
* - mcpMode: 'full' includes dev-server, 'standard' does not
|
|
8607
|
+
* - DEV_ENVIRONMENT_ID: Required for git-server and dev-server
|
|
8608
|
+
*
|
|
8609
|
+
* MCP servers provide:
|
|
8610
|
+
* - workflow-server: Task/module/knowledge operations
|
|
8611
|
+
* - git-server: Git operations via API (requires DEV_ENVIRONMENT_ID)
|
|
8612
|
+
* - dev-server: File/exec operations via API (requires DEV_ENVIRONMENT_ID, only in 'full' mode)
|
|
8613
|
+
*/
|
|
8614
|
+
getMcpServersForSession(session) {
|
|
8615
|
+
const servers = [];
|
|
8616
|
+
const mcpDir = this.getMcpServerDir();
|
|
8617
|
+
if (!mcpDir) {
|
|
8618
|
+
console.warn("[AgentManager] EP1233: MCP server directory not found, skipping MCP registration");
|
|
8619
|
+
return servers;
|
|
8620
|
+
}
|
|
8621
|
+
const workflowServerPath = path20.join(mcpDir, "workflow-server.ts");
|
|
8622
|
+
const gitServerPath = path20.join(mcpDir, "git-server.ts");
|
|
8623
|
+
const devServerPath = path20.join(mcpDir, "dev-server.ts");
|
|
8624
|
+
const hasDevEnvId = !!process.env.DEV_ENVIRONMENT_ID || !!process.env.EPISODA_CONTAINER_ID;
|
|
8625
|
+
if (fs19.existsSync(workflowServerPath)) {
|
|
8626
|
+
servers.push({
|
|
8627
|
+
name: "workflow",
|
|
8628
|
+
command: `tsx ${workflowServerPath}`
|
|
8629
|
+
});
|
|
8630
|
+
} else {
|
|
8631
|
+
console.warn(`[AgentManager] EP1233: workflow-server.ts not found at ${workflowServerPath}`);
|
|
8632
|
+
}
|
|
8633
|
+
if (hasDevEnvId && fs19.existsSync(gitServerPath)) {
|
|
8634
|
+
servers.push({
|
|
8635
|
+
name: "git",
|
|
8636
|
+
command: `tsx ${gitServerPath}`
|
|
8637
|
+
});
|
|
8638
|
+
} else if (!hasDevEnvId) {
|
|
8639
|
+
console.log("[AgentManager] EP1233: git-server not registered (DEV_ENVIRONMENT_ID not set)");
|
|
8640
|
+
}
|
|
8641
|
+
if (session.mcpMode === "full" && hasDevEnvId && fs19.existsSync(devServerPath)) {
|
|
8642
|
+
servers.push({
|
|
8643
|
+
name: "dev",
|
|
8644
|
+
command: `tsx ${devServerPath}`
|
|
8645
|
+
});
|
|
8646
|
+
} else if (session.mcpMode !== "full") {
|
|
8647
|
+
console.log(`[AgentManager] EP1233: dev-server not registered (mcpMode=${session.mcpMode})`);
|
|
8648
|
+
}
|
|
8649
|
+
console.log(`[AgentManager] EP1233: MCP servers to register: ${servers.map((s) => s.name).join(", ") || "none"}`);
|
|
8650
|
+
return servers;
|
|
8651
|
+
}
|
|
8652
|
+
/**
|
|
8653
|
+
* EP1233: Find MCP server directory
|
|
8654
|
+
*
|
|
8655
|
+
* Searches for MCP server files in order of priority:
|
|
8656
|
+
* 1. /app/lib/mcp/ (cloud container)
|
|
8657
|
+
* 2. Project lib/mcp/ (if running from project root)
|
|
8658
|
+
* 3. Relative to this file (development)
|
|
8659
|
+
*/
|
|
8660
|
+
getMcpServerDir() {
|
|
8661
|
+
const possiblePaths = [
|
|
8662
|
+
// Cloud container path
|
|
8663
|
+
"/app/lib/mcp",
|
|
8664
|
+
// Local development - relative to project root
|
|
8665
|
+
path20.join(process.cwd(), "lib", "mcp"),
|
|
8666
|
+
// Relative to this module (development/testing)
|
|
8667
|
+
path20.resolve(__dirname, "..", "..", "..", "..", "lib", "mcp")
|
|
8668
|
+
];
|
|
8669
|
+
for (const p of possiblePaths) {
|
|
8670
|
+
if (fs19.existsSync(p) && fs19.existsSync(path20.join(p, "workflow-server.ts"))) {
|
|
8671
|
+
console.log(`[AgentManager] EP1233: Found MCP server directory: ${p}`);
|
|
8672
|
+
return p;
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
console.warn(`[AgentManager] EP1233: No MCP server directory found in: ${possiblePaths.join(", ")}`);
|
|
8676
|
+
return null;
|
|
8677
|
+
}
|
|
8602
8678
|
/**
|
|
8603
8679
|
* Initialize the agent manager
|
|
8604
8680
|
* - Ensure agent CLIs are available (Claude Code, Codex)
|
|
@@ -8638,9 +8714,25 @@ var AgentManager = class {
|
|
|
8638
8714
|
* EP1173: Added autonomousMode parameter for permission-free execution
|
|
8639
8715
|
*/
|
|
8640
8716
|
async startSession(options) {
|
|
8641
|
-
const { sessionId, moduleId, moduleUid, projectPath, provider = "claude", autonomousMode = true, canWrite = true, readOnlyReason, message, credentials, systemPrompt, onChunk, onComplete, onError } = options;
|
|
8642
|
-
|
|
8643
|
-
|
|
8717
|
+
const { sessionId, moduleId, moduleUid, projectPath, provider = "claude", autonomousMode = true, canWrite = true, readOnlyReason, mcpMode = "standard", message, credentials, systemPrompt, onChunk, onComplete, onError } = options;
|
|
8718
|
+
const existingSession = this.sessions.get(sessionId);
|
|
8719
|
+
if (existingSession) {
|
|
8720
|
+
if (existingSession.provider === provider && existingSession.moduleId === moduleId) {
|
|
8721
|
+
console.log(`[AgentManager] EP1232: Session ${sessionId} already exists, treating start as message (idempotent)`);
|
|
8722
|
+
return this.sendMessage({
|
|
8723
|
+
sessionId,
|
|
8724
|
+
message,
|
|
8725
|
+
isFirstMessage: false,
|
|
8726
|
+
// Not first since session exists
|
|
8727
|
+
canWrite,
|
|
8728
|
+
readOnlyReason,
|
|
8729
|
+
onChunk,
|
|
8730
|
+
onComplete,
|
|
8731
|
+
onError
|
|
8732
|
+
});
|
|
8733
|
+
}
|
|
8734
|
+
console.log(`[AgentManager] EP1232: Session ${sessionId} exists with incompatible config - provider: ${existingSession.provider} vs ${provider}, module: ${existingSession.moduleId} vs ${moduleId}`);
|
|
8735
|
+
return { success: false, error: "Session already exists with incompatible configuration" };
|
|
8644
8736
|
}
|
|
8645
8737
|
const oauthToken = credentials?.oauthToken;
|
|
8646
8738
|
const apiKey = credentials?.apiKey;
|
|
@@ -8681,6 +8773,8 @@ var AgentManager = class {
|
|
|
8681
8773
|
// EP1205: Store write permission
|
|
8682
8774
|
readOnlyReason,
|
|
8683
8775
|
// EP1205: Store reason for read-only mode
|
|
8776
|
+
mcpMode,
|
|
8777
|
+
// EP1233: Store MCP server mode
|
|
8684
8778
|
credentials,
|
|
8685
8779
|
systemPrompt,
|
|
8686
8780
|
status: "starting",
|
|
@@ -8840,6 +8934,11 @@ If changes are needed, explain what needs to be done.`;
|
|
|
8840
8934
|
session.agentSessionId = resumeSessionId;
|
|
8841
8935
|
session.claudeSessionId = resumeSessionId;
|
|
8842
8936
|
}
|
|
8937
|
+
const mcpServersToRegister = this.getMcpServersForSession(session);
|
|
8938
|
+
for (const mcpServer of mcpServersToRegister) {
|
|
8939
|
+
args.push("--mcp", ...mcpServer.command.split(" "));
|
|
8940
|
+
console.log(`[AgentManager] EP1233: Registering MCP server: ${mcpServer.name}`);
|
|
8941
|
+
}
|
|
8843
8942
|
args.push("--", message);
|
|
8844
8943
|
}
|
|
8845
8944
|
console.log(`[AgentManager] Spawning ${provider} CLI for session ${sessionId}`);
|
|
@@ -8952,8 +9051,25 @@ If changes are needed, explain what needs to be done.`;
|
|
|
8952
9051
|
...process.env,
|
|
8953
9052
|
// Disable color output for cleaner JSON parsing
|
|
8954
9053
|
NO_COLOR: "1",
|
|
8955
|
-
FORCE_COLOR: "0"
|
|
9054
|
+
FORCE_COLOR: "0",
|
|
9055
|
+
// EP1233: MCP server environment variables
|
|
9056
|
+
// MCP servers inherit these from the Claude Code process
|
|
9057
|
+
MODULE_UID: session.moduleUid
|
|
8956
9058
|
};
|
|
9059
|
+
if (!envVars.DEV_ENVIRONMENT_ID) {
|
|
9060
|
+
envVars.DEV_ENVIRONMENT_ID = process.env.EPISODA_CONTAINER_ID || process.env.EPISODA_MACHINE_ID || "";
|
|
9061
|
+
if (envVars.DEV_ENVIRONMENT_ID) {
|
|
9062
|
+
console.log(`[AgentManager] EP1233: Set DEV_ENVIRONMENT_ID=${envVars.DEV_ENVIRONMENT_ID}`);
|
|
9063
|
+
}
|
|
9064
|
+
}
|
|
9065
|
+
if (!envVars.EPISODA_SESSION_TOKEN) {
|
|
9066
|
+
envVars.EPISODA_SESSION_TOKEN = process.env.EPISODA_ACCESS_TOKEN || process.env.EPISODA_SESSION_TOKEN || "";
|
|
9067
|
+
if (envVars.EPISODA_SESSION_TOKEN) {
|
|
9068
|
+
console.log("[AgentManager] EP1233: Set EPISODA_SESSION_TOKEN for MCP servers");
|
|
9069
|
+
} else {
|
|
9070
|
+
console.warn("[AgentManager] EP1233: No session token available for MCP servers");
|
|
9071
|
+
}
|
|
9072
|
+
}
|
|
8957
9073
|
if (useApiKey && session.credentials.apiKey) {
|
|
8958
9074
|
if (provider === "codex") {
|
|
8959
9075
|
envVars.OPENAI_API_KEY = session.credentials.apiKey;
|
|
@@ -9152,17 +9268,17 @@ If changes are needed, explain what needs to be done.`;
|
|
|
9152
9268
|
if (agentProcess && !agentProcess.killed) {
|
|
9153
9269
|
console.log(`[AgentManager] Stopping session ${sessionId}`);
|
|
9154
9270
|
agentProcess.kill("SIGINT");
|
|
9155
|
-
await new Promise((
|
|
9271
|
+
await new Promise((resolve5) => {
|
|
9156
9272
|
const timeout = setTimeout(() => {
|
|
9157
9273
|
if (!agentProcess.killed) {
|
|
9158
9274
|
console.log(`[AgentManager] Force killing session ${sessionId}`);
|
|
9159
9275
|
agentProcess.kill("SIGTERM");
|
|
9160
9276
|
}
|
|
9161
|
-
|
|
9277
|
+
resolve5();
|
|
9162
9278
|
}, 5e3);
|
|
9163
9279
|
agentProcess.once("exit", () => {
|
|
9164
9280
|
clearTimeout(timeout);
|
|
9165
|
-
|
|
9281
|
+
resolve5();
|
|
9166
9282
|
});
|
|
9167
9283
|
});
|
|
9168
9284
|
}
|
|
@@ -9317,7 +9433,7 @@ async function killProcessOnPort(port) {
|
|
|
9317
9433
|
} catch {
|
|
9318
9434
|
}
|
|
9319
9435
|
}
|
|
9320
|
-
await new Promise((
|
|
9436
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
9321
9437
|
for (const pid of pids) {
|
|
9322
9438
|
try {
|
|
9323
9439
|
(0, import_child_process13.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
|
|
@@ -9326,7 +9442,7 @@ async function killProcessOnPort(port) {
|
|
|
9326
9442
|
} catch {
|
|
9327
9443
|
}
|
|
9328
9444
|
}
|
|
9329
|
-
await new Promise((
|
|
9445
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
9330
9446
|
const stillInUse = await isPortInUse(port);
|
|
9331
9447
|
if (stillInUse) {
|
|
9332
9448
|
console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
|
|
@@ -9346,7 +9462,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
|
|
|
9346
9462
|
if (await isPortInUse(port)) {
|
|
9347
9463
|
return true;
|
|
9348
9464
|
}
|
|
9349
|
-
await new Promise((
|
|
9465
|
+
await new Promise((resolve5) => setTimeout(resolve5, checkInterval));
|
|
9350
9466
|
}
|
|
9351
9467
|
return false;
|
|
9352
9468
|
}
|
|
@@ -9418,7 +9534,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
9418
9534
|
const delay = calculateRestartDelay(serverInfo.restartCount);
|
|
9419
9535
|
console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
|
|
9420
9536
|
writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
|
|
9421
|
-
await new Promise((
|
|
9537
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
9422
9538
|
if (!activeServers.has(moduleUid)) {
|
|
9423
9539
|
console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
|
|
9424
9540
|
return;
|
|
@@ -9543,7 +9659,7 @@ async function stopDevServer(moduleUid) {
|
|
|
9543
9659
|
writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
|
|
9544
9660
|
}
|
|
9545
9661
|
serverInfo.process.kill("SIGTERM");
|
|
9546
|
-
await new Promise((
|
|
9662
|
+
await new Promise((resolve5) => setTimeout(resolve5, 2e3));
|
|
9547
9663
|
if (!serverInfo.process.killed) {
|
|
9548
9664
|
serverInfo.process.kill("SIGKILL");
|
|
9549
9665
|
}
|
|
@@ -9561,7 +9677,7 @@ async function restartDevServer(moduleUid) {
|
|
|
9561
9677
|
writeToLog(logFile, `Manual restart requested`, false);
|
|
9562
9678
|
}
|
|
9563
9679
|
await stopDevServer(moduleUid);
|
|
9564
|
-
await new Promise((
|
|
9680
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
9565
9681
|
if (await isPortInUse(port)) {
|
|
9566
9682
|
await killProcessOnPort(port);
|
|
9567
9683
|
}
|
|
@@ -10044,7 +10160,7 @@ var Daemon = class _Daemon {
|
|
|
10044
10160
|
if (attempt < MAX_RETRIES) {
|
|
10045
10161
|
const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
|
|
10046
10162
|
console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
|
|
10047
|
-
await new Promise((
|
|
10163
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
10048
10164
|
await this.disconnectProject(projectPath);
|
|
10049
10165
|
}
|
|
10050
10166
|
}
|
|
@@ -10248,8 +10364,8 @@ var Daemon = class _Daemon {
|
|
|
10248
10364
|
}
|
|
10249
10365
|
}
|
|
10250
10366
|
let releaseLock;
|
|
10251
|
-
const lockPromise = new Promise((
|
|
10252
|
-
releaseLock =
|
|
10367
|
+
const lockPromise = new Promise((resolve5) => {
|
|
10368
|
+
releaseLock = resolve5;
|
|
10253
10369
|
});
|
|
10254
10370
|
this.tunnelOperationLocks.set(moduleUid, lockPromise);
|
|
10255
10371
|
try {
|
|
@@ -10275,7 +10391,7 @@ var Daemon = class _Daemon {
|
|
|
10275
10391
|
const maxWait = 35e3;
|
|
10276
10392
|
const startTime = Date.now();
|
|
10277
10393
|
while (this.pendingConnections.has(projectPath) && Date.now() - startTime < maxWait) {
|
|
10278
|
-
await new Promise((
|
|
10394
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
10279
10395
|
}
|
|
10280
10396
|
if (this.liveConnections.has(projectPath)) {
|
|
10281
10397
|
console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
|
|
@@ -10661,6 +10777,8 @@ var Daemon = class _Daemon {
|
|
|
10661
10777
|
// EP1205: Default to writable
|
|
10662
10778
|
readOnlyReason: cmd.readOnlyReason,
|
|
10663
10779
|
// EP1205: Pass reason for UI
|
|
10780
|
+
mcpMode: cmd.mcpMode || "standard",
|
|
10781
|
+
// EP1233: Default to standard (no dev-server)
|
|
10664
10782
|
message: cmd.message,
|
|
10665
10783
|
credentials: cmd.credentials,
|
|
10666
10784
|
systemPrompt: cmd.systemPrompt,
|
|
@@ -10929,14 +11047,14 @@ var Daemon = class _Daemon {
|
|
|
10929
11047
|
} catch (pidError) {
|
|
10930
11048
|
console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError);
|
|
10931
11049
|
}
|
|
10932
|
-
const authSuccessPromise = new Promise((
|
|
11050
|
+
const authSuccessPromise = new Promise((resolve5, reject) => {
|
|
10933
11051
|
const AUTH_TIMEOUT = 3e4;
|
|
10934
11052
|
const timeout = setTimeout(() => {
|
|
10935
11053
|
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
10936
11054
|
}, AUTH_TIMEOUT);
|
|
10937
11055
|
const authHandler = () => {
|
|
10938
11056
|
clearTimeout(timeout);
|
|
10939
|
-
|
|
11057
|
+
resolve5();
|
|
10940
11058
|
};
|
|
10941
11059
|
client.once("auth_success", authHandler);
|
|
10942
11060
|
const errorHandler = (message) => {
|