diff --git a/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts b/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts index a2ad32fabbb..791ef23e10e 100644 --- a/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts +++ b/apps/desktop/src/main/terminal-host/session-shell-ready.test.ts @@ -301,57 +301,6 @@ describe("Session shell-ready: kill/exit before readiness", () => { }); }); -/** Wait for the emulator write queue to drain (uses setImmediate internally). */ -function waitForEmulatorFlush(): Promise { - return new Promise((resolve) => setTimeout(resolve, 50)); -} - -describe("Session shell-ready: DA1 query response forwarding (#3028)", () => { - it("forwards headless emulator DA1 response to subprocess during pending state", async () => { - // Fish shell sends DA1 (ESC[c) at startup to detect terminal capabilities. - // The headless emulator generates a response (ESC[?1;2c) via xterm.js. - // This response MUST be forwarded to the subprocess even during the - // "pending" shell-ready state, otherwise fish waits 10s and then - // disables optional features like cursor shape and reflow detection. - const { session, proc } = createTestSession("/usr/local/bin/fish"); - spawnAndReady(session, proc); - - // Simulate PTY output containing a DA1 query from fish. - // When the headless emulator processes this, xterm.js generates - // a DA1 response via its onData callback. - sendData(proc, "\x1b[c"); - - // The emulator write queue processes via setImmediate, so we need - // to let the event loop tick for xterm to process the query. - await waitForEmulatorFlush(); - - // The emulator's DA1 response should have been forwarded to the - // subprocess (written to stdin) even though shell is still pending. - const writes = getWrittenData(proc); - - // The response should contain a DA1 reply (ESC[?...c format) - expect(writes.length).toBeGreaterThan(0); - const da1Response = writes.join(""); - // biome-ignore lint/suspicious/noControlCharactersInRegex: matching ESC in terminal protocol data - expect(da1Response).toMatch(/\x1b\[\?[\d;]+c/); - }); - - it("forwards DSR response to subprocess during pending state", async () => { - const { session, proc } = createTestSession("/bin/zsh"); - spawnAndReady(session, proc); - - // DSR (Device Status Report): ESC[5n → ESC[0n (terminal OK) - sendData(proc, "\x1b[5n"); - - await waitForEmulatorFlush(); - - const writes = getWrittenData(proc); - expect(writes.length).toBeGreaterThan(0); - const response = writes.join(""); - expect(response).toContain("\x1b[0n"); - }); -}); - describe("Session shell-ready: supported shells", () => { for (const shell of [ "/bin/zsh", diff --git a/apps/desktop/src/main/terminal-host/session.ts b/apps/desktop/src/main/terminal-host/session.ts index 87174902470..f04fe72014c 100644 --- a/apps/desktop/src/main/terminal-host/session.ts +++ b/apps/desktop/src/main/terminal-host/session.ts @@ -224,21 +224,18 @@ export class Session { // Set initial CWD this.emulator.setCwd(options.cwd); - // The headless emulator responds to terminal queries (e.g. DA1, - // DSR) when no renderer client is attached. These responses must - // be forwarded to the subprocess immediately — even during shell - // init — because shells like fish send DA1 at startup and wait - // up to 10 seconds for a reply before disabling optional features. - // Unlike renderer-generated responses (which go through write() - // and are correctly dropped during init to avoid appearing as - // typed text), headless emulator responses are written directly - // to the PTY and consumed by the shell as protocol data. + // The headless emulator responds to terminal queries (e.g. DA) + // when no renderer client is attached. During shell init we drop + // these — they'd land in the pre-ready stdin queue and appear as + // typed text like "?62;4;9;22c" once flushed. After a client + // attaches the renderer's xterm handles all terminal queries. this.emulator.onData((data) => { if ( this.attachedClients.size === 0 && this.subprocess && this.subprocessReady ) { + if (this.shellReadyState === "pending") return; this.sendWriteToSubprocess(data); } });