Skip to main content
Glama
double-prompt-concatenation-reproduction.test.ts10.5 kB
/** * REPRODUCTION TEST FOR CRITICAL TERMINAL OUTPUT BUGS * * This test uses the Terminal History Testing Framework to reproduce the exact * issues observed in the browser terminal: * * BUG 1: Double prompts * ``` * [jsbattig@localhost ~]$ [jsbattig@localhost ~]$ ls * ``` * * BUG 2: Command/output concatenation * ``` * [jsbattig@localhost ~]$ lsApplications package.json * ``` * * BUG 3: Missing CRLF separation * Commands and outputs get concatenated without proper line breaks * * EXPECTED BEHAVIOR: * ``` * [jsbattig@localhost ~]$ ls * Applications package.json * bun.lock package-lock.json * [jsbattig@localhost ~]$ echo "hello" * hello * [jsbattig@localhost ~]$ * ``` */ import { JestTestUtilities } from './integration/terminal-history-framework/jest-test-utilities'; // Extend Jest matchers for terminal validation JestTestUtilities.extendJestMatchers(); // Type declarations for custom Jest matchers declare global { namespace jest { interface Matchers<R> { toHaveValidTerminalHistory(): R; toContainCRLFLineEndings(): R; } } } describe("Double Prompt and Concatenation Bug Reproduction", () => { const testUtils = JestTestUtilities.setupJestEnvironment('double-prompt-repro'); /** * REPRODUCTION TEST 1: Double prompt detection * This test reproduces the exact scenario where double prompts appear */ test("REPRODUCE BUG: Double prompt issue with WebSocket capture", async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "double-prompt-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "double-prompt-test", "command": "echo \\"test1\\""}'}, {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "double-prompt-test", "command": "echo \\"test2\\""}'}, {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "double-prompt-test", "command": "ls | head -3"}'} ], workflowTimeout: 30000, sessionName: 'double-prompt-test' }; const result = await testUtils.runTerminalHistoryTest(config); console.log("=== RAW WEBSOCKET MESSAGES FOR BUG REPRODUCTION ==="); console.log("Concatenated responses length:", result.concatenatedResponses.length); console.log("Raw content (first 1000 chars):", JSON.stringify(result.concatenatedResponses.substring(0, 1000))); // CRITICAL ASSERTION: Detect double prompt pattern const doublePromptPattern = /\[[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\s+[^\]]+\]\$\s*\[[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\s+[^\]]+\]\$/g; const doublePromptMatches = result.concatenatedResponses.match(doublePromptPattern); console.log("=== DOUBLE PROMPT DETECTION ==="); console.log("Double prompt matches found:", doublePromptMatches?.length || 0); if (doublePromptMatches) { doublePromptMatches.forEach((match, index) => { console.log(`Double prompt ${index + 1}:`, JSON.stringify(match)); }); } // FAILING ASSERTION: This should fail if double prompts exist (reproducing the bug) expect(doublePromptMatches).toBeNull(); // This will fail and show us the double prompts }, 45000); /** * REPRODUCTION TEST 2: Command/output concatenation detection * This test captures the exact concatenation behavior seen in browser */ test("REPRODUCE BUG: Command/output concatenation with ls command", async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "concat-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "concat-test", "command": "ls | head -2"}'} ], workflowTimeout: 30000, sessionName: 'concat-test' }; const result = await testUtils.runTerminalHistoryTest(config); console.log("=== COMMAND/OUTPUT CONCATENATION ANALYSIS ==="); console.log("Raw WebSocket output (first 500 chars):", JSON.stringify(result.concatenatedResponses.substring(0, 500))); // DETECT CONCATENATION: Look for patterns like "lsApplications" or "echo "hello"hello" const concatenationPattern = /(ls[A-Za-z]|echo\s+"[^"]*"[^"\r\n])/g; const concatenationMatches = result.concatenatedResponses.match(concatenationPattern); console.log("Concatenation matches found:", concatenationMatches?.length || 0); if (concatenationMatches) { concatenationMatches.forEach((match, index) => { console.log(`Concatenation ${index + 1}:`, JSON.stringify(match)); }); } // LOOK FOR PROPER SEPARATION: Commands should be followed by CRLF before output const hasProperSeparation = /ls\r\n[A-Za-z]/.test(result.concatenatedResponses); console.log("Has proper command/output separation:", hasProperSeparation); // FAILING ASSERTION: This should pass if concatenation is fixed expect(concatenationMatches).toBeNull(); // This will fail if concatenation exists expect(hasProperSeparation).toBe(true); // This will fail if no proper separation }, 45000); /** * REPRODUCTION TEST 3: CRLF line ending validation * This test ensures WebSocket messages have proper CRLF for xterm.js compatibility */ test("REPRODUCE BUG: Missing CRLF line endings validation", async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "crlf-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "crlf-test", "command": "echo \\"line1\\""}'}, {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "crlf-test", "command": "echo \\"line2\\""}'} ], workflowTimeout: 30000, sessionName: 'crlf-test' }; const result = await testUtils.runTerminalHistoryTest(config); console.log("=== CRLF LINE ENDING ANALYSIS ==="); // DETAILED CRLF ANALYSIS const hasCRLF = result.concatenatedResponses.includes('\r\n'); const hasLFOnly = result.concatenatedResponses.includes('\n') && !result.concatenatedResponses.includes('\r\n'); const hasCROnly = result.concatenatedResponses.includes('\r') && !result.concatenatedResponses.includes('\n'); console.log("Has CRLF (\\r\\n):", hasCRLF); console.log("Has LF only (\\n):", hasLFOnly); console.log("Has CR only (\\r):", hasCROnly); // Count line ending types const crlfCount = (result.concatenatedResponses.match(/\r\n/g) || []).length; const lfOnlyCount = (result.concatenatedResponses.match(/(?<!\r)\n/g) || []).length; console.log("CRLF count:", crlfCount); console.log("LF-only count:", lfOnlyCount); // CRITICAL ASSERTION: Should have CRLF for xterm.js compatibility expect(hasCRLF).toBe(true); expect(crlfCount).toBeGreaterThan(0); // Use framework assertion helpers testUtils.expectWebSocketMessages(result.concatenatedResponses) .toContainCRLF() .validate(); }, 45000); /** * COMPREHENSIVE BUG REPRODUCTION TEST * This test captures all issues in one comprehensive scenario */ test("COMPREHENSIVE: All terminal display bugs in one scenario", async () => { const config = { preWebSocketCommands: [ 'ssh_connect {"name": "comprehensive-bug-test", "host": "localhost", "username": "jsbattig", "keyFilePath": "~/.ssh/id_ed25519"}', 'ssh_exec {"sessionName": "comprehensive-bug-test", "command": "pwd"}' ], postWebSocketCommands: [ {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "comprehensive-bug-test", "command": "echo \\"hello\\""}'}, {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "comprehensive-bug-test", "command": "ls | head -2"}'}, {initiator: 'mcp-client', command: 'ssh_exec {"sessionName": "comprehensive-bug-test", "command": "whoami"}'} ], workflowTimeout: 30000, sessionName: 'comprehensive-bug-test' }; const result = await testUtils.runTerminalHistoryTest(config); console.log("=== COMPREHENSIVE BUG ANALYSIS ==="); console.log("Full WebSocket response length:", result.concatenatedResponses.length); // Save full output for detailed analysis console.log("=== FIRST 1500 CHARACTERS OF WEBSOCKET OUTPUT ==="); console.log(JSON.stringify(result.concatenatedResponses.substring(0, 1500))); // VALIDATION 1: Double prompt detection const doublePromptMatches = result.concatenatedResponses.match(/\[[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\s+[^\]]+\]\$\s*\[[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+\s+[^\]]+\]\$/g); console.log("🐛 Double prompt instances found:", doublePromptMatches?.length || 0); // VALIDATION 2: Command concatenation detection const concatenationMatches = result.concatenatedResponses.match(/(echo\s+"[^"]*"[^"\r\n]|ls[A-Za-z]|pwd[A-Za-z]|whoami[A-Za-z])/g); console.log("🐛 Command concatenation instances found:", concatenationMatches?.length || 0); // VALIDATION 3: CRLF presence const crlfCount = (result.concatenatedResponses.match(/\r\n/g) || []).length; console.log("✅ CRLF line endings found:", crlfCount); // Create comprehensive validation report const bugReport = { doublePrompts: doublePromptMatches?.length || 0, concatenationIssues: concatenationMatches?.length || 0, crlfLineEndings: crlfCount, totalOutputLength: result.concatenatedResponses.length, hasProperLineEndings: result.concatenatedResponses.includes('\r\n') }; console.log("=== COMPREHENSIVE BUG REPORT ===", bugReport); // EXPECTED BEHAVIOR ASSERTION: This defines what should happen after fix // These assertions will fail initially (reproducing bugs) and pass after fix expect(bugReport.doublePrompts).toBe(0); // No double prompts expect(bugReport.concatenationIssues).toBe(0); // No command/output concatenation expect(bugReport.crlfLineEndings).toBeGreaterThan(0); // Proper CRLF endings expect(bugReport.hasProperLineEndings).toBe(true); // CRLF compatibility // Framework validation using custom matchers expect(result.concatenatedResponses).toHaveValidTerminalHistory(); expect(result.concatenatedResponses).toContainCRLFLineEndings(); }, 45000); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LightspeedDMS/ssh-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server