diff --git a/packages/agent-python/agent.py b/packages/agent-python/agent.py index 21cdacb4e..17286ebcb 100644 --- a/packages/agent-python/agent.py +++ b/packages/agent-python/agent.py @@ -18,6 +18,7 @@ from livekit.agents import ( cli, llm, voice, + inference, ) from livekit.agents.llm.mcp import MCPServerHTTP @@ -67,7 +68,11 @@ async def entrypoint(ctx: JobContext): session = voice.AgentSession( stt="assemblyai/universal-streaming:en", llm="openai/gpt-4.1-mini", - tts="cartesia/sonic-2:9626c31c-bec5-4cca-baa8-f8ba9e84c8bc", + tts= inference.TTS( + model="elevenlabs/eleven_turbo_v2_5", + voice="Xb7hH8MSUJpSbSDYk0k2", + language="en" + ), ) # Start the session diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index 65685f078..1e84afa0e 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -11,7 +11,7 @@ import * as tmux from "./tmux.js"; import { startHttpServer } from "./http-server.js"; import os from "node:os"; -const DEFAULT_SESSION = "voice-dev"; +const DEFAULT_SESSION = "__voice-dev"; // Create MCP server const server = new McpServer( @@ -47,7 +47,7 @@ async function ensureDefaultSession(): Promise { // List terminals - Tool server.tool( "list-terminals", - "List all terminals (isolated shell environments). Returns terminal ID, name, and current working directory.", + "List all terminals (isolated shell environments). Returns terminal ID, name, current working directory, and currently running command.", {}, async () => { try { @@ -61,7 +61,7 @@ server.tool( const windows = await tmux.listWindows(session.id); - // For each window, get the pane and its working directory + // For each window, get the pane and its info const terminals = await Promise.all( windows.map(async (window) => { const panes = await tmux.listPanes(window.id); @@ -69,13 +69,16 @@ server.tool( const workingDirectory = pane ? await tmux.getCurrentWorkingDirectory(pane.id) : "unknown"; + const currentCommand = pane + ? await tmux.getCurrentCommand(pane.id) + : "unknown"; return { id: window.id, name: window.name, active: window.active, workingDirectory, - paneId: pane?.id || "", + currentCommand, }; }) ); @@ -130,8 +133,11 @@ server.tool( throw new Error(`Default session not found: ${DEFAULT_SESSION}`); } - // Create window in default session - const window = await tmux.createWindow(session.id, name); + // Create window in default session with working directory and optional command + const window = await tmux.createWindow(session.id, name, { + workingDirectory, + command: initialCommand, + }); if (!window) { return { content: [ @@ -143,38 +149,13 @@ server.tool( }; } - // Change to working directory - await tmux.sendText({ - paneId: window.paneId, - text: `cd "${workingDirectory}"`, - pressEnter: true, - }); - - // Wait a bit for cd to complete - await new Promise((resolve) => setTimeout(resolve, 300)); - - // Execute initial command if provided - let commandOutput: string | undefined; - if (initialCommand) { - await tmux.sendText({ - paneId: window.paneId, - text: initialCommand, - pressEnter: true, - }); - - // Wait for command to execute - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // Capture output - commandOutput = await tmux.capturePaneContent(window.paneId, 200); - } + const commandOutput = window.output; let text = `Terminal created: ${JSON.stringify( { id: window.id, name: window.name, workingDirectory, - paneId: window.paneId, }, null, 2 @@ -525,12 +506,16 @@ server.resource("Terminals", "tmux://terminals", async () => { const workingDirectory = pane ? await tmux.getCurrentWorkingDirectory(pane.id) : "unknown"; + const currentCommand = pane + ? await tmux.getCurrentCommand(pane.id) + : "unknown"; return { id: window.id, name: window.name, active: window.active, workingDirectory, + currentCommand, }; }) ); diff --git a/packages/mcp-server/src/tmux.ts b/packages/mcp-server/src/tmux.ts index d89f7683e..9584ead1b 100644 --- a/packages/mcp-server/src/tmux.ts +++ b/packages/mcp-server/src/tmux.ts @@ -175,31 +175,94 @@ export async function capturePaneContent( export async function getCurrentWorkingDirectory( paneId: string ): Promise { - return executeTmux(`display-message -p -t '${paneId}' '#{pane_current_path}'`); + try { + const tmuxPath = await executeTmux(`display-message -p -t '${paneId}' '#{pane_current_path}'`); + + // If tmux returns a valid path, use it + if (tmuxPath && tmuxPath.trim()) { + return tmuxPath; + } + + // Fallback: get the PID and use lsof to find the actual CWD + const shellPid = await executeTmux(`display-message -p -t '${paneId}' '#{pane_pid}'`); + const { stdout } = await exec(`lsof -a -p ${shellPid.trim()} -d cwd -Fn | grep '^n' | cut -c2-`); + return stdout.trim() || tmuxPath; + } catch (error) { + // If all else fails, return empty string + return ""; + } } /** - * Create a new tmux session + * Get the current command running in a pane (full command line with arguments) + * Gets the immediate child process of the shell, not the shell itself + */ +export async function getCurrentCommand(paneId: string): Promise { + try { + // Get the shell PID (the pane's main process) + const shellPid = await executeTmux(`display-message -p -t '${paneId}' '#{pane_pid}'`); + + // First, check if there's a child process using comm= (works for all programs including top) + // Use 'ax' flags to see all processes + const { stdout: childPid } = await exec(`ps ax -o pid=,ppid=,comm= | awk '$2 == ${shellPid.trim()} { print $1; exit }'`); + + if (childPid.trim()) { + // Found a child process, get its full command with args + const { stdout: fullCmd } = await exec(`ps -p ${childPid.trim()} -o args= | sed 's/\\\\012.*//'`); + const command = fullCmd.trim(); + if (command) { + return command; + } + } + + // No child process, just return the shell name + const { stdout: shellCmd } = await exec(`ps -p ${shellPid} -o comm=`); + return shellCmd.trim(); + } catch (error) { + // Fallback to just the command name if ps fails + return executeTmux(`display-message -p -t '${paneId}' '#{pane_current_command}'`); + } +} + +/** + * Create a new tmux session with a default window named "default" in home directory */ export async function createSession(name: string): Promise { - await executeTmux(`new-session -d -s "${name}"`); + const homeDir = process.env.HOME || "~"; + await executeTmux(`new-session -d -s "${name}" -n "default" -c "${homeDir}"`); + + // Disable automatic window renaming for all windows in the session + await executeTmux(`set-window-option -t "${name}" automatic-rename off`); + return findSessionByName(name); } /** - * Create a new window in a session + * Create a new window in a session with optional working directory and initial command */ export async function createWindow( sessionId: string, name: string, - command?: string + options?: { + workingDirectory?: string; + command?: string; + } ): Promise<(TmuxWindow & { paneId: string; output?: string }) | null> { - const output = await executeTmux(`new-window -t '${sessionId}' -n '${name}'`); + // Build new-window command with optional working directory + let newWindowCmd = `new-window -t '${sessionId}' -n '${name}'`; + if (options?.workingDirectory) { + newWindowCmd += ` -c '${options.workingDirectory}'`; + } + + await executeTmux(newWindowCmd); const windows = await listWindows(sessionId); const window = windows.find((window) => window.name === name); if (!window) return null; + // Disable automatic window renaming + await executeTmux(`set-window-option -t '${window.id}' automatic-rename off`); + // Get the default pane created with the window const panes = await listPanes(window.id); const defaultPane = panes[0]; @@ -207,10 +270,10 @@ export async function createWindow( let commandOutput: string | undefined; // If command is provided, execute it in the new pane - if (command && defaultPane) { + if (options?.command && defaultPane) { await sendText({ paneId: defaultPane.id, - text: command, + text: options.command, pressEnter: true, }); @@ -257,6 +320,8 @@ export async function renameWindow( name: string ): Promise { await executeTmux(`rename-window -t '${windowId}' '${name}'`); + // Disable automatic renaming to preserve the manual name + await executeTmux(`set-window-option -t '${windowId}' automatic-rename off`); } /**