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((resolve4, reject) => {
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
- resolve4();
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((resolve4) => {
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
- resolve4(false);
2332
+ resolve5(false);
2333
2333
  } else {
2334
- resolve4(true);
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.111",
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((resolve4, reject) => {
3121
+ return new Promise((resolve5, reject) => {
3122
3122
  this.server.listen(socketPath, () => {
3123
3123
  fs3.chmodSync(socketPath, 384);
3124
- resolve4();
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((resolve4) => {
3135
+ return new Promise((resolve5) => {
3136
3136
  this.server.close(() => {
3137
3137
  if (fs3.existsSync(socketPath)) {
3138
3138
  fs3.unlinkSync(socketPath);
3139
3139
  }
3140
- resolve4();
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((resolve4) => {
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
- resolve4(result);
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((resolve4) => setTimeout(resolve4, retryInterval));
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((resolve4) => setTimeout(resolve4, retryInterval));
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((resolve4) => {
4893
+ return new Promise((resolve5) => {
4894
4894
  const server = net2.createServer();
4895
4895
  server.once("error", (err) => {
4896
4896
  if (err.code === "EADDRINUSE") {
4897
- resolve4(true);
4897
+ resolve5(true);
4898
4898
  } else {
4899
- resolve4(false);
4899
+ resolve5(false);
4900
4900
  }
4901
4901
  });
4902
4902
  server.once("listening", () => {
4903
4903
  server.close();
4904
- resolve4(false);
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((resolve4) => setTimeout(resolve4, ms));
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((resolve4) => {
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
- () => resolve4(true)
5725
+ () => resolve5(true)
5726
5726
  );
5727
- req.on("error", () => resolve4(false));
5727
+ req.on("error", () => resolve5(false));
5728
5728
  req.on("timeout", () => {
5729
5729
  req.destroy();
5730
- resolve4(false);
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((resolve4) => setTimeout(resolve4, ms));
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((resolve4, reject) => {
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
- resolve4();
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((resolve4) => setTimeout(resolve4, 500));
6288
+ await new Promise((resolve5) => setTimeout(resolve5, 500));
6289
6289
  if (this.isProcessRunning(pid)) {
6290
6290
  this.killByPid(pid, "SIGKILL");
6291
- await new Promise((resolve4) => setTimeout(resolve4, 200));
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, 500));
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((resolve4) => {
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
- resolve4({ success: true, url: tunnelInfo.url });
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
- resolve4({ success: false, error: errorMsg });
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
- resolve4({ success: false, error: error.message });
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
- resolve4({ success: false, error: errorMsg });
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((resolve4) => setTimeout(resolve4, 500));
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => {
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
- resolve4();
6708
+ resolve5();
6709
6709
  }, 3e3);
6710
6710
  tunnel.process.once("exit", () => {
6711
6711
  clearTimeout(timeout);
6712
- resolve4();
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((resolve4) => setTimeout(resolve4, 2e3));
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => {
8593
- releaseLock = resolve4;
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
- if (this.sessions.has(sessionId)) {
8643
- return { success: false, error: "Session already exists" };
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((resolve4) => {
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
- resolve4();
9277
+ resolve5();
9162
9278
  }, 5e3);
9163
9279
  agentProcess.once("exit", () => {
9164
9280
  clearTimeout(timeout);
9165
- resolve4();
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, 500));
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((resolve4) => setTimeout(resolve4, checkInterval));
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((resolve4) => setTimeout(resolve4, delay));
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((resolve4) => setTimeout(resolve4, 2e3));
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, delay));
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((resolve4) => {
10252
- releaseLock = resolve4;
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((resolve4) => setTimeout(resolve4, 500));
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((resolve4, reject) => {
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
- resolve4();
11057
+ resolve5();
10940
11058
  };
10941
11059
  client.once("auth_success", authHandler);
10942
11060
  const errorHandler = (message) => {