From 35f98262ebb358ddbf9ae662fd049d23b9b6c1ac Mon Sep 17 00:00:00 2001
From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Date: Mon, 27 Jul 2026 16:48:31 -0700
Subject: [PATCH] fix(native-chat): only seed single-line unsubmitted launch
drafts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The unsubmitted-delivery branch seeded on every draft delivery, which also
caught the agent-session-fork path whose prompt is multi-line scraped context.
The chat send pre-clears the TUI with Ctrl+U (kill-to-start-of-LINE), so a
multi-line prefill cannot be fully cleared and its earlier lines would glue
onto the next message. The GitHub work-item draft this feature targets is a
bare issue URL, so narrowing costs it nothing.
Also assert the composer retires the seed after a send — deleting that call
previously failed no test.
---
.../native-chat/NativeChatComposer.test.tsx | 24 ++++++++++++++++---
.../lib/agent-launch-prompt-delivery.test.ts | 15 ++++++++++++
.../src/lib/agent-launch-prompt-delivery.ts | 5 +++-
3 files changed, 40 insertions(+), 4 deletions(-)
diff --git a/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx b/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
index c35ebca2666..fe6cbdd6f58 100644
--- a/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
@@ -32,7 +32,9 @@ const mocks = vi.hoisted(() => ({
sendNativeChatMessageVerified: vi.fn(),
trackPendingSend: vi.fn(),
setDraft: vi.fn(),
- draftScopeKeys: [] as string[]
+ draftScopeKeys: [] as string[],
+ clearNativeChatLaunchDraft: vi.fn(),
+ markNativeChatLaunchDraftAdopted: vi.fn()
}))
vi.mock('../../store', () => {
@@ -40,8 +42,8 @@ vi.mock('../../store', () => {
dictationState: 'idle',
settings: { voice: { enabled: false }, nativeChatSessionOptions: {} },
updateSettings: vi.fn(),
- clearNativeChatLaunchDraft: vi.fn(),
- markNativeChatLaunchDraftAdopted: vi.fn()
+ clearNativeChatLaunchDraft: mocks.clearNativeChatLaunchDraft,
+ markNativeChatLaunchDraftAdopted: mocks.markNativeChatLaunchDraftAdopted
}
const useAppStore = (selector: (value: typeof state) => unknown) => selector(state)
useAppStore.getState = () => state
@@ -203,6 +205,22 @@ describe('NativeChatComposer', () => {
expect(mocks.trackPendingSend).toHaveBeenCalledWith(mocks.sendHandle, 'pending-1')
})
+ it('retires the launch-draft seed once a send clears the TUI input line', () => {
+ render(
+
+ )
+ expect(mocks.clearNativeChatLaunchDraft).not.toHaveBeenCalled()
+
+ act(() => mocks.fieldProps?.onSend?.())
+
+ expect(mocks.clearNativeChatLaunchDraft).toHaveBeenCalledWith('tab-1')
+ })
+
it('keeps the draft scope anchored to the pane while the PTY reconnects', () => {
const view = render(
{
expect(mocks.seedNativeChatLaunchPrompt).not.toHaveBeenCalled()
})
+ it('does not seed a launch draft for multi-line content', async () => {
+ // The chat send pre-clears the TUI with Ctrl+U (kill-to-start-of-LINE), so a
+ // multi-line prefill (e.g. scraped session-fork context) would leave earlier
+ // lines behind to glue onto the next message.
+ await deliverLaunchPromptToAgentTab({
+ tabId: 'fork-tab',
+ agent: 'codex',
+ content: 'Forked from session\n\nhttps://example.test/context',
+ submit: false,
+ forcePaste: false
+ })
+
+ expect(mocks.seedNativeChatLaunchDraft).not.toHaveBeenCalled()
+ })
+
it('does not seed a launch draft for submitted, unsupported, or empty content', async () => {
await deliverLaunchPromptToAgentTab({
tabId: 'submit-tab',
diff --git a/src/renderer/src/lib/agent-launch-prompt-delivery.ts b/src/renderer/src/lib/agent-launch-prompt-delivery.ts
index 9aedb82ba34..1536c8bede6 100644
--- a/src/renderer/src/lib/agent-launch-prompt-delivery.ts
+++ b/src/renderer/src/lib/agent-launch-prompt-delivery.ts
@@ -43,9 +43,12 @@ export function deliverLaunchPromptToAgentTab(args: {
text: content,
createdAt: Date.now()
})
- } else if (submit !== true) {
+ } else if (submit !== true && !content.includes('\n')) {
// Why: an unsubmitted draft lives only in the TUI input buffer; seed the
// chat-composer copy so the context isn't invisible in the GUI view.
+ // Single-line only: the chat send pre-clears the TUI with Ctrl+U
+ // (kill-to-start-of-LINE), so a multi-line prefill (e.g. scraped fork
+ // context) would leave earlier lines to glue onto the next message.
seedNativeChatLaunchDraftForAgentTab({ tabId, agent, text: content })
}