From 958969e8324664cb5a438a18d0b9b4b93b7fc4cc Mon Sep 17 00:00:00 2001 From: SAPHID Date: Mon, 12 Oct 2026 23:18:42 +0000 Subject: [PATCH 1/3] fix(web): preserve thread context when copying or stashing prompts Co-Authored-By: GPT-6.1 Sol --- apps/web/src/components/chat/ChatComposer.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 9162c4b67e0d0..931f848b33ae9 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -3160,6 +3160,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) } } const records: ComposerContextRecord[] = [ + ...composerThreadContexts.filter((record) => wanted.has(record.contextId)), ...composerTerminalContexts .filter((c) => wanted.has(terminalContextReference(c).contextId)) .map(terminalContextRecord), @@ -3201,6 +3202,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) composerPreviewAnnotations, composerReviewComments, composerTerminalContexts, + composerThreadContexts, environmentId, uploadsByImageId, ], @@ -4951,6 +4953,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) // Context chips keep their links in the prompt; the payloads behind them travel as // records so the restore can resolve every chip. const stashedRecords: ComposerContextRecord[] = [ + ...composerThreadContexts, ...composerTerminalContextsRef.current.map(terminalContextRecord), ...composerReviewComments.map(reviewCommentContextRecord), ...composerPreviewAnnotations.map((annotation) => @@ -5062,6 +5065,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) promptRef.current = ""; clearComposerDraftPromptAndImages(stashTarget); clearComposerDraftTerminalContexts(stashTarget); + setComposerDraftThreadContexts(stashTarget, []); for (const comment of composerReviewComments) { removeComposerDraftReviewComment(stashTarget, comment.id); } @@ -5163,12 +5167,14 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) }, [ clearComposerDraftPromptAndImages, clearComposerDraftTerminalContexts, + setComposerDraftThreadContexts, setComposerDraftPrompt, setComposerTrigger, composerDraftTarget, composerFilesRef, composerImagesRef, composerTerminalContextsRef, + composerThreadContexts, composerReviewComments, composerPreviewAnnotations, removeComposerDraftReviewComment, From 1add15195b7818d38cc7bf623a31d7a9339ce5d1 Mon Sep 17 00:00:00 2001 From: SAPHID Date: Wed, 14 Oct 2026 17:16:24 +0000 Subject: [PATCH 2/3] refactor(web): narrow to keeping thread context when stashing The copy half already landed on main in #18789; drop the duplicate thread-record line the merge left in the clipboard fragment. Co-Authored-By: Claude Opus 5.5 --- apps/web/src/components/chat/ChatComposer.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index 33dfe7af4021f..b73c1de8a2f4f 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -3183,7 +3183,6 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) } } const records: ComposerContextRecord[] = [ - ...composerThreadContexts.filter((record) => wanted.has(record.contextId)), ...composerTerminalContexts .filter((c) => wanted.has(terminalContextReference(c).contextId)) .map(terminalContextRecord), From 1df4c32662bb2afcb0ababb83e8f9b5a69385fcb Mon Sep 17 00:00:00 2001 From: SAPHID Date: Wed, 14 Oct 2026 21:20:57 +0000 Subject: [PATCH 3/3] fix(web): restore stashed thread context to the prompt draft Keep the restored prompt and its records together during a pending question. Clipboard imports keep targeting the active answer draft. Co-Authored-By: GPT-6.1 Sol --- apps/web/src/components/chat/ChatComposer.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index b73c1de8a2f4f..025a3b6a8b448 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -3337,6 +3337,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) ( records: ReadonlyArray, sourceEnvironmentId: EnvironmentId | null, + threadDraftTarget = attachmentDraftTarget, ): ReadonlyMap => { const rewritten = new Map(); const dependentAttachmentLocalIds = new Map(); @@ -3372,7 +3373,11 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) : existing ? (uploadedContextRecordFromDraft(existing) ?? undefined) : undefined; - if (existingRecord && isSameComposerContextPayload(existingRecord, record)) { + if ( + existingRecord && + isSameComposerContextPayload(existingRecord, record) && + (record.kind !== "thread" || threadDraftTarget === attachmentDraftTarget) + ) { if (record.kind === "preview-annotation" && record.screenshotContextId) { skippedDependentAttachmentIds.add(record.screenshotContextId); } @@ -3419,7 +3424,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) case "thread": { // The agent can only read threads on its own server; a pasted foreign one is dropped. if (record.environmentId !== environmentId) break; - addComposerDraftThreadContexts(attachmentDraftTarget, [record], { + addComposerDraftThreadContexts(threadDraftTarget, [record], { appendReference: false, }); rewritten.set(record.contextId, record.contextId); @@ -4695,7 +4700,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) } const rewrittenContextIds = entry.records - ? importContextRecords(entry.records, null) + ? importContextRecords(entry.records, null, composerDraftTarget) : new Map(); const restoredPrompt = replaceComposerContextReferences(entry.prompt, (reference) => { const contextId = rewrittenContextIds.get(reference.contextId);