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((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.112",
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((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,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((resolve4) => {
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
- resolve4();
9294
+ resolve5();
9178
9295
  }, 5e3);
9179
9296
  agentProcess.once("exit", () => {
9180
9297
  clearTimeout(timeout);
9181
- resolve4();
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, 500));
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((resolve4) => setTimeout(resolve4, checkInterval));
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((resolve4) => setTimeout(resolve4, delay));
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((resolve4) => setTimeout(resolve4, 2e3));
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((resolve4) => setTimeout(resolve4, 1e3));
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((resolve4) => setTimeout(resolve4, delay));
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((resolve4) => {
10268
- releaseLock = resolve4;
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((resolve4) => setTimeout(resolve4, 500));
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((resolve4, reject) => {
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
- resolve4();
11094
+ resolve5();
10956
11095
  };
10957
11096
  client.once("auth_success", authHandler);
10958
11097
  const errorHandler = (message) => {