Fix double /reset in agent mode (#5516)

fix double /reset in agent mode

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
This commit is contained in:
Sean Hatfield 2026-04-28 09:50:22 -07:00 committed by GitHub
parent ae9960b8ba
commit 5eb9842533
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 62 additions and 53 deletions

View File

@ -7,7 +7,6 @@ import AddPresetModal from "./SlashPresets/AddPresetModal";
import EditPresetModal from "./SlashPresets/EditPresetModal"; import EditPresetModal from "./SlashPresets/EditPresetModal";
import PublishEntityModal from "@/components/CommunityHub/PublishEntityModal"; import PublishEntityModal from "@/components/CommunityHub/PublishEntityModal";
import showToast from "@/utils/toast"; import showToast from "@/utils/toast";
import { useIsAgentSessionActive } from "@/utils/chat/agent";
import { PROMPT_INPUT_EVENT } from "@/components/WorkspaceChat/ChatContainer/PromptInput"; import { PROMPT_INPUT_EVENT } from "@/components/WorkspaceChat/ChatContainer/PromptInput";
import useToolsMenuItems from "../../useToolsMenuItems"; import useToolsMenuItems from "../../useToolsMenuItems";
import SlashCommandRow from "./SlashCommandRow"; import SlashCommandRow from "./SlashCommandRow";
@ -20,7 +19,6 @@ export default function SlashCommandsTab({
registerItemCount, registerItemCount,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const isActiveAgentSession = useIsAgentSessionActive();
const { const {
isOpen: isAddModalOpen, isOpen: isAddModalOpen,
openModal: openAddModal, openModal: openAddModal,
@ -49,38 +47,31 @@ export default function SlashCommandsTab({
setPresets(presets); setPresets(presets);
}; };
// Build the list of selectable items for keyboard navigation and rendering // Build the list of selectable items for keyboard navigation and rendering.
// Command names must stay as static English strings since the backend // /reset is a static English string since the backend matches it exactly.
// matches against exact "/reset" and "/exit" commands. // During an agent session it ends the session AND clears the chat.
const items = useMemo(() => { const items = useMemo(
const builtIn = isActiveAgentSession () => [
? { {
command: "/exit",
description: t("chat_window.preset_exit_description"),
autoSubmit: true,
}
: {
command: "/reset", command: "/reset",
description: t("chat_window.preset_reset_description"), description: t("chat_window.preset_reset_description"),
autoSubmit: true, autoSubmit: true,
}; },
return [
builtIn,
...presets.map((preset) => ({ ...presets.map((preset) => ({
command: preset.command, command: preset.command,
description: preset.description, description: preset.description,
autoSubmit: false, autoSubmit: false,
preset, preset,
})), })),
]; ],
}, [isActiveAgentSession, presets]); [presets, t]
);
const handleUseCommand = useCallback( const handleUseCommand = useCallback(
(command, autoSubmit = false) => { (command, autoSubmit = false) => {
setShowing(false); setShowing(false);
// Auto-submit commands (/reset, /exit) fire immediately // Auto-submit commands (/reset) fire immediately
if (autoSubmit) { if (autoSubmit) {
sendCommand({ text: command, autoSubmit: true }); sendCommand({ text: command, autoSubmit: true });
promptRef?.current?.focus(); promptRef?.current?.focus();
@ -189,7 +180,6 @@ export default function SlashCommandsTab({
))} ))}
{/* Add new */} {/* Add new */}
{!isActiveAgentSession && (
<div <div
onClick={openAddModal} onClick={openAddModal}
className="flex items-center gap-1.5 px-2 py-1 rounded cursor-pointer hover:bg-zinc-700/50 light:hover:bg-slate-100" className="flex items-center gap-1.5 px-2 py-1 rounded cursor-pointer hover:bg-zinc-700/50 light:hover:bg-slate-100"
@ -203,7 +193,6 @@ export default function SlashCommandsTab({
{t("chat_window.add_new")} {t("chat_window.add_new")}
</span> </span>
</div> </div>
)}
{/* Modals */} {/* Modals */}
<AddPresetModal <AddPresetModal

View File

@ -46,6 +46,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
const { files, parseAttachments } = useContext(DndUploaderContext); const { files, parseAttachments } = useContext(DndUploaderContext);
const { chatHistoryRef } = useChatContainerQuickScroll(); const { chatHistoryRef } = useChatContainerQuickScroll();
const pendingMessageChecked = useRef(false); const pendingMessageChecked = useRef(false);
const pendingResetRef = useRef(false);
const { listening, resetTranscript } = useSpeechRecognition({ const { listening, resetTranscript } = useSpeechRecognition({
clearTranscriptOnListen: true, clearTranscriptOnListen: true,
@ -240,7 +241,13 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
attachments, attachments,
}) })
); );
return;
// /reset during an active agent session should end the session AND
// clear the chat in a single action. The send above triggers the
// server to abort the agent and close the socket; fall through to the
// /reset flow below which resets memory + clears chat history.
if (promptMessage.userMessage.trim() !== "/reset") return;
pendingResetRef.current = true;
} }
if (!promptMessage || !promptMessage?.userMessage) return false; if (!promptMessage || !promptMessage?.userMessage) return false;
@ -304,6 +311,11 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
socket.addEventListener("close", (_event) => { socket.addEventListener("close", (_event) => {
setAgentSessionActive(false); setAgentSessionActive(false);
window.dispatchEvent(new CustomEvent(AGENT_SESSION_END)); window.dispatchEvent(new CustomEvent(AGENT_SESSION_END));
// When the close was triggered by /reset, skip the "Agent session
// complete." status - the pending /reset flow will clear history.
if (pendingResetRef.current) {
pendingResetRef.current = false;
} else {
setChatHistory((prev) => [ setChatHistory((prev) => [
...prev.filter((msg) => !!msg.content), ...prev.filter((msg) => !!msg.content),
{ {
@ -318,6 +330,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
pending: false, pending: false,
}, },
]); ]);
}
setLoadingResponse(false); setLoadingResponse(false);
setWebsocket(null); setWebsocket(null);
setSocketId(null); setSocketId(null);

View File

@ -12,6 +12,12 @@ const chatHistory = {
return { return {
name: this.name, name: this.name,
setup: function (aibitat) { setup: function (aibitat) {
// If the agent is aborted (e.g. user sent /reset mid-response), skip
// the pending save so a completing in-flight response doesn't reappear.
aibitat.onAbort(() => {
aibitat._aborted = true;
});
// pre-register a workspace chat ID to secure it in the DB // pre-register a workspace chat ID to secure it in the DB
aibitat.onMessage(async (message) => { aibitat.onMessage(async (message) => {
if (message.from !== "USER") return; if (message.from !== "USER") return;
@ -54,6 +60,7 @@ const chatHistory = {
aibitat.onMessage(async () => { aibitat.onMessage(async () => {
try { try {
if (aibitat._aborted) return;
const lastResponses = aibitat.chats.slice(-2); const lastResponses = aibitat.chats.slice(-2);
if (lastResponses.length !== 2) return; if (lastResponses.length !== 2) return;
const [prev, last] = lastResponses; const [prev, last] = lastResponses;