From 6d9ca54d6ba6ed6ddb2287494b6ace2086cfc83c Mon Sep 17 00:00:00 2001 From: Amos Date: Sat, 22 Aug 2026 09:16:02 +0300 Subject: [PATCH] =?UTF-8?q?Gate=20sending=20while=20a=20background=20job?= =?UTF-8?q?=20part=20is=20pending=20(protocol=20=C2=A75)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/chat.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/components/chat.tsx b/components/chat.tsx index 9eb5bf7..29fa6de 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -22,7 +22,7 @@ import { SidebarToggle } from "@/components/sidebar"; import type { TeacherUIMessage } from "@/src/agent/ui-message"; import type { ArtifactPartProps } from "@/src/core/contracts"; import type { QuestionAnswerData } from "@/src/core/question-core"; -import { findPendingToolCall } from "@/src/core/pending"; +import { findPendingJobCall, findPendingToolCall } from "@/src/core/pending"; /** One width for everything: transcript, cards, and the bottom blob share this column. */ const COLUMN = "mx-auto w-full max-w-3xl px-4"; @@ -92,7 +92,11 @@ export function Chat({ (messages.at(-1)?.parts.some((p) => isToolUIPart(p) && p.state === "input-streaming") ?? false); const [text, setText] = useState(""); - const canSend = text.trim().length > 0 && !pending; + // Jobs gate sending too (an unanswered tool call in the outbound list would + // break provider conversion — contracts.ts "THE ASYNC PROTOCOL" §5), but + // they don't dock: the running job's activity card sits in the transcript. + const pendingJob = findPendingJobCall(messages); + const canSend = text.trim().length > 0 && !pending && !pendingJob; const syncFromServer = useCallback(async () => { const res = await fetch(`/api/chats/${chatId}`); @@ -103,7 +107,7 @@ export function Chat({ const onSend = () => { const t = text.trim(); - if (!t || busy || pending) return; + if (!t || busy || pending || pendingJob) return; setText(""); void sendMessage({ text: t }); };