From cbd877e178af57ca9ceda0a840727b5025fa8753 Mon Sep 17 00:00:00 2001 From: claude code agent 227 Date: Fri, 26 Jun 2026 17:19:12 +0300 Subject: [PATCH] fix(ai-chat): address PR #211 review (i18n keys, dead export, flag leak) - Register the new AI-chat keys "Send now" and "Interrupt and send now" in both en-US and ru-RU catalogs so the UI never renders mixed-language tooltip/aria-label (i18n policy). - Make INTERRUPT_NOTE module-private (drop the unused re-export), matching the module's private DEFAULT_PROMPT/SAFETY_FRAMEWORK siblings. - Reset interruptNextSendRef in the flush-on-abort branch when nothing is actually sent, so a stuck one-shot interrupt flag cannot tag the next unrelated send; flushNext now reports whether it sent. - Add a CHANGELOG [Unreleased]/Added entry for #198. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 7 +++++++ apps/client/public/locales/en-US/translation.json | 2 ++ apps/client/public/locales/ru-RU/translation.json | 2 ++ .../src/features/ai-chat/components/chat-thread.tsx | 11 +++++++++-- apps/server/src/core/ai-chat/ai-chat.prompt.ts | 2 -- 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2aa9c9..5230a799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **Interrupt the AI agent and send a queued message now.** A queued AI-chat + message gains a "send now" action that interrupts the streaming turn and + immediately sends that message, keeping the agent's partial output. The + follow-up turn is tagged as an interrupt so the model is told its previous + answer was cut off and builds on it instead of restarting; the rest of the + queue still flushes normally afterward. (#198) + - **Persistent AI-chat history as the source of truth + server-side export.** An assistant turn is now persisted to the database step by step: the row is inserted upfront as `streaming` and updated as each agent step finishes, then diff --git a/apps/client/public/locales/en-US/translation.json b/apps/client/public/locales/en-US/translation.json index bd8c4ed3..96fd0a0c 100644 --- a/apps/client/public/locales/en-US/translation.json +++ b/apps/client/public/locales/en-US/translation.json @@ -1177,6 +1177,8 @@ "Send when the agent finishes": "Send when the agent finishes", "Queue message": "Queue message", "Remove queued message": "Remove queued message", + "Send now": "Send now", + "Interrupt and send now": "Interrupt and send now", "Stop": "Stop", "Response stopped.": "Response stopped.", "Connection lost — the answer was interrupted.": "Connection lost — the answer was interrupted.", diff --git a/apps/client/public/locales/ru-RU/translation.json b/apps/client/public/locales/ru-RU/translation.json index f8c59436..362d8848 100644 --- a/apps/client/public/locales/ru-RU/translation.json +++ b/apps/client/public/locales/ru-RU/translation.json @@ -717,6 +717,8 @@ "Send when the agent finishes": "Отправить, когда агент закончит", "Queue message": "Поставить в очередь", "Remove queued message": "Убрать из очереди", + "Send now": "Отправить сейчас", + "Interrupt and send now": "Прервать и отправить сейчас", "Something went wrong": "Что-то пошло не так", "Stop": "Стоп", "The AI agent could not respond. Please try again.": "AI-агент не смог ответить. Попробуйте ещё раз.", diff --git a/apps/client/src/features/ai-chat/components/chat-thread.tsx b/apps/client/src/features/ai-chat/components/chat-thread.tsx index e0947a0b..940c789c 100644 --- a/apps/client/src/features/ai-chat/components/chat-thread.tsx +++ b/apps/client/src/features/ai-chat/components/chat-thread.tsx @@ -209,11 +209,14 @@ export default function ChatThread({ const interruptNextSendRef = useRef(false); // FIFO dequeue + send the next queued message (no-op when the queue is empty). + // Returns whether a message was actually sent, so callers can tell an empty + // dequeue (nothing to flush) from a real send. const flushNext = useCallback(() => { const { head, rest } = dequeue(queuedRef.current); - if (!head) return; + if (!head) return false; setQueue(rest); sendMessageRef.current?.({ text: head.text }); + return true; }, [setQueue]); const enqueue = useCallback( @@ -297,7 +300,11 @@ export default function ChatThread({ flushOnAbortRef.current = false; // Suppress the "Response stopped." flash for an intentional interrupt. setStopNotice(null); - flushNext(); + // If the promoted head vanished (e.g. the user removed it before the + // abort landed) flushNext sends nothing — clear the one-shot interrupt + // tag so it can't leak onto the next unrelated send. On a real send the + // tag is consumed by prepareSendMessagesRequest and stays untouched. + if (!flushNext()) interruptNextSendRef.current = false; return; } if (isAbort || isDisconnect || isError) return; diff --git a/apps/server/src/core/ai-chat/ai-chat.prompt.ts b/apps/server/src/core/ai-chat/ai-chat.prompt.ts index ba7ff326..f0a9c2d0 100644 --- a/apps/server/src/core/ai-chat/ai-chat.prompt.ts +++ b/apps/server/src/core/ai-chat/ai-chat.prompt.ts @@ -72,8 +72,6 @@ const INTERRUPT_NOTE = 'assume your previous response was complete, and do not silently restart the ' + 'partial work — build on it or follow the new instruction.'; -export { INTERRUPT_NOTE }; - export interface BuildSystemPromptInput { workspace: Workspace; /**