refactor: implement per-conversation tmux sessions and command-based MCP tools

This commit implements isolated tmux sessions for each conversation and
refines the terminal MCP tools to use consistent command terminology.

**Per-Conversation Tmux Sessions:**
- Each Session now creates its own Terminal MCP client with conversation ID as tmux session name
- Complete isolation: commands from different conversations don't interfere
- Automatic cleanup: tmux session killed when conversation ends
- Updated getAllTools() to accept custom terminal tools per session
- Session.cleanup() now async to properly await tmux session termination

**Command-Based MCP Tools:**
- Removed old terminal-based tools (list_terminals, create_terminal, etc.)
- Kept only command-based tools (execute_command, list_commands, etc.)
- Updated kill_command to accept multiple command IDs for batch cleanup
- Fixed bug where all commands showed same metadata (working directory, command)
- Added window-specific option storage using set-window-option/show-window-options

**Bug Fixes:**
- Fixed listCommands() showing wrong working directory and command for dead commands
- Stored command metadata as tmux window options for proper retrieval
- Enhanced test to verify multiple commands retain unique metadata

**Testing:**
- Added comprehensive test suite with 26 passing tests
- All tests verify command execution, capture, interaction, and cleanup
- TypeScript: no errors
- Backward compatibility maintained with global singleton fallback

Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
Mohamed Boudra
2025-10-21 12:54:39 +02:00
co-authored by Claude Happy
parent b267ca3958
commit 37a363da6d
8 changed files with 1329 additions and 191 deletions
@@ -152,8 +152,28 @@ const manualTools = {
/**
* Get all tools (MCP + manual) for LLM
* @param terminalTools - Optional custom terminal tools (per-session). If not provided, uses global singleton.
*/
export async function getAllTools(): Promise<Record<string, any>> {
export async function getAllTools(terminalTools?: Record<string, any>): Promise<Record<string, any>> {
if (terminalTools) {
// Use provided terminal tools (per-session) and merge with Playwright
const playwrightClient = await getPlaywrightMcpClient().catch((error) => {
console.error("Failed to initialize Playwright MCP:", error);
return null;
});
const playwrightTools = playwrightClient
? await playwrightClient.tools()
: {};
return {
...terminalTools,
...playwrightTools,
...manualTools,
};
}
// Fallback to global singleton tools
const mcpTools = await getMcpTools();
return {
...mcpTools,