From 4eb92f2852bc67b02f6ff7416888f90985e1c184 Mon Sep 17 00:00:00 2001 From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Date: Thu, 13 Aug 2026 22:44:18 -0700 Subject: [PATCH] fix(terminal): key agent-paste bracketing on live evidence, not shellForeground Measured against a real pane: shellForeground is republished only at OSC 133 boundaries, so a shell without 133 integration leaves it latched true while an agent owns the foreground. Vetoing on it silently reinstated the submit bug the parent commit fixes - the parked draft was sent on paste with the gate in place. Prefer process-confirmed agent identity when present, keep the restart-rehydrated veto, and drop the shellForeground veto. Erring toward bracketing costs a literal ESC[200~ in a non-2004 program; erring the other way sends the user's draft. Refs STA-4294 --- .../terminal-agent-paste-bracketing.test.ts | 16 +++++++++------- .../terminal-agent-paste-bracketing.ts | 19 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.test.ts b/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.test.ts index 9dd82fb972b..003ab987f7d 100644 --- a/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.test.ts @@ -76,16 +76,15 @@ describe('shouldForceBracketedMultilinePasteForPane', () => { ).toBe(false) }) - // Staleness: the agent row is retained on purpose and is not lifecycle-driven, so it - // can outlive the agent. Only evidence the pane is no longer the agent's may veto. - it('vetoes when OSC 133;D proved the foreground is back at the shell', () => { + it('brackets on a process-confirmed agent even with no status row', () => { expect( decide({ + agentStatusByPaneKey: {}, paneForegroundAgentByPaneKey: { - [AGENT_PANE_KEY]: foregroundEntry({ shellForeground: true }) + [AGENT_PANE_KEY]: foregroundEntry({ agent: 'codex', shellForeground: false }) } }) - ).toBe(false) + ).toBe(true) }) it('vetoes a row rehydrated from disk across an app restart', () => { @@ -111,11 +110,14 @@ describe('shouldForceBracketedMultilinePasteForPane', () => { ).toBe(true) }) - it('still brackets when foreground evidence exists but has not seen a shell', () => { + it('still brackets when shellForeground is latched true while an agent owns the pane', () => { + // Why: shellForeground is republished only at OSC 133 boundaries, so a shell with + // no 133 integration leaves it true while an agent runs. Measured live: vetoing on + // it reinstated the submit bug. expect( decide({ paneForegroundAgentByPaneKey: { - [AGENT_PANE_KEY]: foregroundEntry({ agent: 'codex', shellForeground: false }) + [AGENT_PANE_KEY]: foregroundEntry({ shellForeground: true }) } }) ).toBe(true) diff --git a/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.ts b/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.ts index 48a3efea906..027d5ea6928 100644 --- a/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.ts +++ b/src/renderer/src/components/terminal-pane/terminal-agent-paste-bracketing.ts @@ -8,11 +8,6 @@ import type { PaneForegroundAgentEntry } from '../../store/slices/pane-foregroun * ConPTY never forwards it and that ConPTY can be on a remote host — so the client's * platform proves nothing about the PTY. Unbracketed, xterm rewrites the paste's * newlines to CR and the agent submits whatever draft was parked in its composer. - * - * An agent row is retained on purpose (an idle agent sits at `done`), so neither the - * 30-minute freshness TTL nor `state !== 'done'` can gate this — both would strip - * bracketing from a live but quiet agent. Only evidence that the pane is *no longer* - * the agent's vetoes it. */ export function shouldForceBracketedMultilinePasteForPane({ isWindowsClient, @@ -31,10 +26,9 @@ export function shouldForceBracketedMultilinePasteForPane({ return true } const paneKey = makePaneKey(tabId, leafId) - // Why: OSC 133;D proved the foreground is back at the shell — process-grade evidence - // that outranks an agent row nothing has cleared yet. - if (paneForegroundAgentByPaneKey[paneKey]?.shellForeground === true) { - return false + // Why: a process-table confirmed agent is the strongest evidence there is. + if (isTuiAgent(paneForegroundAgentByPaneKey[paneKey]?.agent)) { + return true } const entry = agentStatusByPaneKey[paneKey] // Why: a row rehydrated from disk across a restart describes the previous process, @@ -42,5 +36,12 @@ export function shouldForceBracketedMultilinePasteForPane({ if (entry?.restoredUnconfirmed === true) { return false } + // Why NOT vetoed on shellForeground: that flag is republished only at OSC 133 + // boundaries, so a shell without 133 integration leaves it latched true while an + // agent owns the foreground. Vetoing on it silently reinstates the submit bug — + // measured live. An idle agent also sits at `done` past the 30-minute freshness + // TTL, so neither state nor TTL can gate this either. Erring toward bracketing + // costs a literal ESC[200~ in a non-2004 program; erring the other way sends the + // user's parked draft. return isTuiAgent(entry?.agentType) }