episoda 0.2.113 → 0.2.115
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.114",
|
|
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,7 +8714,7 @@ 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;
|
|
8717
|
+
const { sessionId, moduleId, moduleUid, projectPath, provider = "claude", autonomousMode = true, canWrite = true, readOnlyReason, mcpMode = "standard", message, credentials, systemPrompt, onChunk, onToolUse, onComplete, onError } = options;
|
|
8642
8718
|
const existingSession = this.sessions.get(sessionId);
|
|
8643
8719
|
if (existingSession) {
|
|
8644
8720
|
if (existingSession.provider === provider && existingSession.moduleId === moduleId) {
|
|
@@ -8651,6 +8727,8 @@ var AgentManager = class {
|
|
|
8651
8727
|
canWrite,
|
|
8652
8728
|
readOnlyReason,
|
|
8653
8729
|
onChunk,
|
|
8730
|
+
onToolUse,
|
|
8731
|
+
// EP1236: Pass tool use callback
|
|
8654
8732
|
onComplete,
|
|
8655
8733
|
onError
|
|
8656
8734
|
});
|
|
@@ -8697,6 +8775,8 @@ var AgentManager = class {
|
|
|
8697
8775
|
// EP1205: Store write permission
|
|
8698
8776
|
readOnlyReason,
|
|
8699
8777
|
// EP1205: Store reason for read-only mode
|
|
8778
|
+
mcpMode,
|
|
8779
|
+
// EP1233: Store MCP server mode
|
|
8700
8780
|
credentials,
|
|
8701
8781
|
systemPrompt,
|
|
8702
8782
|
status: "starting",
|
|
@@ -8710,6 +8790,8 @@ var AgentManager = class {
|
|
|
8710
8790
|
message,
|
|
8711
8791
|
isFirstMessage: true,
|
|
8712
8792
|
onChunk,
|
|
8793
|
+
onToolUse,
|
|
8794
|
+
// EP1236: Pass tool use callback
|
|
8713
8795
|
onComplete,
|
|
8714
8796
|
onError
|
|
8715
8797
|
});
|
|
@@ -8721,7 +8803,7 @@ var AgentManager = class {
|
|
|
8721
8803
|
* EP1133: Supports both Claude Code and Codex CLI with provider-specific handling.
|
|
8722
8804
|
*/
|
|
8723
8805
|
async sendMessage(options) {
|
|
8724
|
-
const { sessionId, message, isFirstMessage, canWrite, readOnlyReason, agentSessionId, claudeSessionId, onChunk, onComplete, onError } = options;
|
|
8806
|
+
const { sessionId, message, isFirstMessage, canWrite, readOnlyReason, agentSessionId, claudeSessionId, onChunk, onToolUse, onComplete, onError } = options;
|
|
8725
8807
|
const session = this.sessions.get(sessionId);
|
|
8726
8808
|
if (!session) {
|
|
8727
8809
|
return { success: false, error: "Session not found" };
|
|
@@ -8856,6 +8938,11 @@ If changes are needed, explain what needs to be done.`;
|
|
|
8856
8938
|
session.agentSessionId = resumeSessionId;
|
|
8857
8939
|
session.claudeSessionId = resumeSessionId;
|
|
8858
8940
|
}
|
|
8941
|
+
const mcpServersToRegister = this.getMcpServersForSession(session);
|
|
8942
|
+
for (const mcpServer of mcpServersToRegister) {
|
|
8943
|
+
args.push("--mcp", ...mcpServer.command.split(" "));
|
|
8944
|
+
console.log(`[AgentManager] EP1233: Registering MCP server: ${mcpServer.name}`);
|
|
8945
|
+
}
|
|
8859
8946
|
args.push("--", message);
|
|
8860
8947
|
}
|
|
8861
8948
|
console.log(`[AgentManager] Spawning ${provider} CLI for session ${sessionId}`);
|
|
@@ -8968,8 +9055,25 @@ If changes are needed, explain what needs to be done.`;
|
|
|
8968
9055
|
...process.env,
|
|
8969
9056
|
// Disable color output for cleaner JSON parsing
|
|
8970
9057
|
NO_COLOR: "1",
|
|
8971
|
-
FORCE_COLOR: "0"
|
|
9058
|
+
FORCE_COLOR: "0",
|
|
9059
|
+
// EP1233: MCP server environment variables
|
|
9060
|
+
// MCP servers inherit these from the Claude Code process
|
|
9061
|
+
MODULE_UID: session.moduleUid
|
|
8972
9062
|
};
|
|
9063
|
+
if (!envVars.DEV_ENVIRONMENT_ID) {
|
|
9064
|
+
envVars.DEV_ENVIRONMENT_ID = process.env.EPISODA_CONTAINER_ID || process.env.EPISODA_MACHINE_ID || "";
|
|
9065
|
+
if (envVars.DEV_ENVIRONMENT_ID) {
|
|
9066
|
+
console.log(`[AgentManager] EP1233: Set DEV_ENVIRONMENT_ID=${envVars.DEV_ENVIRONMENT_ID}`);
|
|
9067
|
+
}
|
|
9068
|
+
}
|
|
9069
|
+
if (!envVars.EPISODA_SESSION_TOKEN) {
|
|
9070
|
+
envVars.EPISODA_SESSION_TOKEN = process.env.EPISODA_ACCESS_TOKEN || process.env.EPISODA_SESSION_TOKEN || "";
|
|
9071
|
+
if (envVars.EPISODA_SESSION_TOKEN) {
|
|
9072
|
+
console.log("[AgentManager] EP1233: Set EPISODA_SESSION_TOKEN for MCP servers");
|
|
9073
|
+
} else {
|
|
9074
|
+
console.warn("[AgentManager] EP1233: No session token available for MCP servers");
|
|
9075
|
+
}
|
|
9076
|
+
}
|
|
8973
9077
|
if (useApiKey && session.credentials.apiKey) {
|
|
8974
9078
|
if (provider === "codex") {
|
|
8975
9079
|
envVars.OPENAI_API_KEY = session.credentials.apiKey;
|
|
@@ -8999,6 +9103,7 @@ If changes are needed, explain what needs to be done.`;
|
|
|
8999
9103
|
let stdoutEventCount = 0;
|
|
9000
9104
|
let chunksSent = 0;
|
|
9001
9105
|
const streamStartTime = Date.now();
|
|
9106
|
+
let hasContent = false;
|
|
9002
9107
|
childProcess.stdout?.on("data", (data) => {
|
|
9003
9108
|
const rawData = data.toString();
|
|
9004
9109
|
stdoutBuffer += rawData;
|
|
@@ -9048,13 +9153,25 @@ If changes are needed, explain what needs to be done.`;
|
|
|
9048
9153
|
switch (parsed.type) {
|
|
9049
9154
|
case "assistant":
|
|
9050
9155
|
if (parsed.message?.content) {
|
|
9156
|
+
if (hasContent) {
|
|
9157
|
+
onChunk("\n\n");
|
|
9158
|
+
}
|
|
9051
9159
|
for (const block of parsed.message.content) {
|
|
9052
9160
|
if (block.type === "text" && block.text) {
|
|
9161
|
+
hasContent = true;
|
|
9053
9162
|
chunksSent++;
|
|
9054
9163
|
if (chunksSent <= 5) {
|
|
9055
9164
|
console.log(`[AgentManager] EP1191: Sending chunk ${chunksSent} via assistant event - ${block.text.length} chars`);
|
|
9056
9165
|
}
|
|
9057
9166
|
onChunk(block.text);
|
|
9167
|
+
} else if (block.type === "tool_use" && options.onToolUse) {
|
|
9168
|
+
const toolEvent = {
|
|
9169
|
+
id: block.id,
|
|
9170
|
+
name: block.name,
|
|
9171
|
+
input: block.input || {}
|
|
9172
|
+
};
|
|
9173
|
+
console.log(`[AgentManager] EP1236: Tool invoked - ${toolEvent.name} (${toolEvent.id})`);
|
|
9174
|
+
options.onToolUse(toolEvent);
|
|
9058
9175
|
}
|
|
9059
9176
|
}
|
|
9060
9177
|
}
|
|
@@ -9168,17 +9285,17 @@ If changes are needed, explain what needs to be done.`;
|
|
|
9168
9285
|
if (agentProcess && !agentProcess.killed) {
|
|
9169
9286
|
console.log(`[AgentManager] Stopping session ${sessionId}`);
|
|
9170
9287
|
agentProcess.kill("SIGINT");
|
|
9171
|
-
await new Promise((
|
|
9288
|
+
await new Promise((resolve5) => {
|
|
9172
9289
|
const timeout = setTimeout(() => {
|
|
9173
9290
|
if (!agentProcess.killed) {
|
|
9174
9291
|
console.log(`[AgentManager] Force killing session ${sessionId}`);
|
|
9175
9292
|
agentProcess.kill("SIGTERM");
|
|
9176
9293
|
}
|
|
9177
|
-
|
|
9294
|
+
resolve5();
|
|
9178
9295
|
}, 5e3);
|
|
9179
9296
|
agentProcess.once("exit", () => {
|
|
9180
9297
|
clearTimeout(timeout);
|
|
9181
|
-
|
|
9298
|
+
resolve5();
|
|
9182
9299
|
});
|
|
9183
9300
|
});
|
|
9184
9301
|
}
|
|
@@ -9333,7 +9450,7 @@ async function killProcessOnPort(port) {
|
|
|
9333
9450
|
} catch {
|
|
9334
9451
|
}
|
|
9335
9452
|
}
|
|
9336
|
-
await new Promise((
|
|
9453
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
9337
9454
|
for (const pid of pids) {
|
|
9338
9455
|
try {
|
|
9339
9456
|
(0, import_child_process13.execSync)(`kill -0 ${pid} 2>/dev/null`, { encoding: "utf8" });
|
|
@@ -9342,7 +9459,7 @@ async function killProcessOnPort(port) {
|
|
|
9342
9459
|
} catch {
|
|
9343
9460
|
}
|
|
9344
9461
|
}
|
|
9345
|
-
await new Promise((
|
|
9462
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
9346
9463
|
const stillInUse = await isPortInUse(port);
|
|
9347
9464
|
if (stillInUse) {
|
|
9348
9465
|
console.error(`[DevServer] EP929: Port ${port} still in use after kill attempts`);
|
|
@@ -9362,7 +9479,7 @@ async function waitForPort(port, timeoutMs = 3e4) {
|
|
|
9362
9479
|
if (await isPortInUse(port)) {
|
|
9363
9480
|
return true;
|
|
9364
9481
|
}
|
|
9365
|
-
await new Promise((
|
|
9482
|
+
await new Promise((resolve5) => setTimeout(resolve5, checkInterval));
|
|
9366
9483
|
}
|
|
9367
9484
|
return false;
|
|
9368
9485
|
}
|
|
@@ -9434,7 +9551,7 @@ async function handleProcessExit(moduleUid, code, signal) {
|
|
|
9434
9551
|
const delay = calculateRestartDelay(serverInfo.restartCount);
|
|
9435
9552
|
console.log(`[DevServer] EP932: Restarting ${moduleUid} in ${delay}ms (attempt ${serverInfo.restartCount + 1}/${MAX_RESTART_ATTEMPTS})`);
|
|
9436
9553
|
writeToLog(serverInfo.logFile || "", `Scheduling restart in ${delay}ms (attempt ${serverInfo.restartCount + 1})`, false);
|
|
9437
|
-
await new Promise((
|
|
9554
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
9438
9555
|
if (!activeServers.has(moduleUid)) {
|
|
9439
9556
|
console.log(`[DevServer] EP932: Server ${moduleUid} was removed during restart delay, aborting restart`);
|
|
9440
9557
|
return;
|
|
@@ -9559,7 +9676,7 @@ async function stopDevServer(moduleUid) {
|
|
|
9559
9676
|
writeToLog(serverInfo.logFile, `Stopping server (manual stop)`, false);
|
|
9560
9677
|
}
|
|
9561
9678
|
serverInfo.process.kill("SIGTERM");
|
|
9562
|
-
await new Promise((
|
|
9679
|
+
await new Promise((resolve5) => setTimeout(resolve5, 2e3));
|
|
9563
9680
|
if (!serverInfo.process.killed) {
|
|
9564
9681
|
serverInfo.process.kill("SIGKILL");
|
|
9565
9682
|
}
|
|
@@ -9577,7 +9694,7 @@ async function restartDevServer(moduleUid) {
|
|
|
9577
9694
|
writeToLog(logFile, `Manual restart requested`, false);
|
|
9578
9695
|
}
|
|
9579
9696
|
await stopDevServer(moduleUid);
|
|
9580
|
-
await new Promise((
|
|
9697
|
+
await new Promise((resolve5) => setTimeout(resolve5, 1e3));
|
|
9581
9698
|
if (await isPortInUse(port)) {
|
|
9582
9699
|
await killProcessOnPort(port);
|
|
9583
9700
|
}
|
|
@@ -10060,7 +10177,7 @@ var Daemon = class _Daemon {
|
|
|
10060
10177
|
if (attempt < MAX_RETRIES) {
|
|
10061
10178
|
const delay = INITIAL_DELAY * Math.pow(2, attempt - 1);
|
|
10062
10179
|
console.log(`[Daemon] Retrying in ${delay / 1e3}s...`);
|
|
10063
|
-
await new Promise((
|
|
10180
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
10064
10181
|
await this.disconnectProject(projectPath);
|
|
10065
10182
|
}
|
|
10066
10183
|
}
|
|
@@ -10264,8 +10381,8 @@ var Daemon = class _Daemon {
|
|
|
10264
10381
|
}
|
|
10265
10382
|
}
|
|
10266
10383
|
let releaseLock;
|
|
10267
|
-
const lockPromise = new Promise((
|
|
10268
|
-
releaseLock =
|
|
10384
|
+
const lockPromise = new Promise((resolve5) => {
|
|
10385
|
+
releaseLock = resolve5;
|
|
10269
10386
|
});
|
|
10270
10387
|
this.tunnelOperationLocks.set(moduleUid, lockPromise);
|
|
10271
10388
|
try {
|
|
@@ -10291,7 +10408,7 @@ var Daemon = class _Daemon {
|
|
|
10291
10408
|
const maxWait = 35e3;
|
|
10292
10409
|
const startTime = Date.now();
|
|
10293
10410
|
while (this.pendingConnections.has(projectPath) && Date.now() - startTime < maxWait) {
|
|
10294
|
-
await new Promise((
|
|
10411
|
+
await new Promise((resolve5) => setTimeout(resolve5, 500));
|
|
10295
10412
|
}
|
|
10296
10413
|
if (this.liveConnections.has(projectPath)) {
|
|
10297
10414
|
console.log(`[Daemon] Pending connection succeeded for ${projectPath}`);
|
|
@@ -10581,6 +10698,7 @@ var Daemon = class _Daemon {
|
|
|
10581
10698
|
console.log(`[Daemon] EP912: Received agent command for ${projectId}:`, cmd.action);
|
|
10582
10699
|
client.updateActivity();
|
|
10583
10700
|
let daemonChunkCount = 0;
|
|
10701
|
+
let daemonToolUseCount = 0;
|
|
10584
10702
|
const daemonStreamStart = Date.now();
|
|
10585
10703
|
const createStreamingCallbacks = (sessionId, commandId) => ({
|
|
10586
10704
|
onChunk: async (chunk) => {
|
|
@@ -10598,9 +10716,28 @@ var Daemon = class _Daemon {
|
|
|
10598
10716
|
console.error(`[Daemon] EP912: Failed to send chunk (WebSocket may be disconnected):`, sendError);
|
|
10599
10717
|
}
|
|
10600
10718
|
},
|
|
10719
|
+
// EP1236: Forward tool invocations to platform for chat UI visibility
|
|
10720
|
+
onToolUse: async (event) => {
|
|
10721
|
+
daemonToolUseCount++;
|
|
10722
|
+
console.log(`[Daemon] EP1236: Forwarding tool_use ${daemonToolUseCount} via WebSocket - ${event.name}`);
|
|
10723
|
+
try {
|
|
10724
|
+
await client.send({
|
|
10725
|
+
type: "agent_result",
|
|
10726
|
+
commandId,
|
|
10727
|
+
result: {
|
|
10728
|
+
success: true,
|
|
10729
|
+
status: "tool_use",
|
|
10730
|
+
sessionId,
|
|
10731
|
+
toolUse: event
|
|
10732
|
+
}
|
|
10733
|
+
});
|
|
10734
|
+
} catch (sendError) {
|
|
10735
|
+
console.error(`[Daemon] EP1236: Failed to send tool_use (WebSocket may be disconnected):`, sendError);
|
|
10736
|
+
}
|
|
10737
|
+
},
|
|
10601
10738
|
onComplete: async (claudeSessionId) => {
|
|
10602
10739
|
const duration = Date.now() - daemonStreamStart;
|
|
10603
|
-
console.log(`[Daemon] EP1191: Stream complete - ${daemonChunkCount} chunks forwarded in ${duration}ms`);
|
|
10740
|
+
console.log(`[Daemon] EP1191: Stream complete - ${daemonChunkCount} chunks, ${daemonToolUseCount} tool uses forwarded in ${duration}ms`);
|
|
10604
10741
|
try {
|
|
10605
10742
|
await client.send({
|
|
10606
10743
|
type: "agent_result",
|
|
@@ -10677,6 +10814,8 @@ var Daemon = class _Daemon {
|
|
|
10677
10814
|
// EP1205: Default to writable
|
|
10678
10815
|
readOnlyReason: cmd.readOnlyReason,
|
|
10679
10816
|
// EP1205: Pass reason for UI
|
|
10817
|
+
mcpMode: cmd.mcpMode || "standard",
|
|
10818
|
+
// EP1233: Default to standard (no dev-server)
|
|
10680
10819
|
message: cmd.message,
|
|
10681
10820
|
credentials: cmd.credentials,
|
|
10682
10821
|
systemPrompt: cmd.systemPrompt,
|
|
@@ -10945,14 +11084,14 @@ var Daemon = class _Daemon {
|
|
|
10945
11084
|
} catch (pidError) {
|
|
10946
11085
|
console.warn(`[Daemon] Could not read daemon PID:`, pidError instanceof Error ? pidError.message : pidError);
|
|
10947
11086
|
}
|
|
10948
|
-
const authSuccessPromise = new Promise((
|
|
11087
|
+
const authSuccessPromise = new Promise((resolve5, reject) => {
|
|
10949
11088
|
const AUTH_TIMEOUT = 3e4;
|
|
10950
11089
|
const timeout = setTimeout(() => {
|
|
10951
11090
|
reject(new Error("Authentication timeout after 30s - server may be under heavy load. Try again in a few seconds."));
|
|
10952
11091
|
}, AUTH_TIMEOUT);
|
|
10953
11092
|
const authHandler = () => {
|
|
10954
11093
|
clearTimeout(timeout);
|
|
10955
|
-
|
|
11094
|
+
resolve5();
|
|
10956
11095
|
};
|
|
10957
11096
|
client.once("auth_success", authHandler);
|
|
10958
11097
|
const errorHandler = (message) => {
|