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

View File

@ -46,6 +46,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
const { files, parseAttachments } = useContext(DndUploaderContext);
const { chatHistoryRef } = useChatContainerQuickScroll();
const pendingMessageChecked = useRef(false);
const pendingResetRef = useRef(false);
const { listening, resetTranscript } = useSpeechRecognition({
clearTranscriptOnListen: true,
@ -240,7 +241,13 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
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;
@ -304,6 +311,11 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
socket.addEventListener("close", (_event) => {
setAgentSessionActive(false);
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) => [
...prev.filter((msg) => !!msg.content),
{
@ -318,6 +330,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
pending: false,
},
]);
}
setLoadingResponse(false);
setWebsocket(null);
setSocketId(null);

View File

@ -12,6 +12,12 @@ const chatHistory = {
return {
name: this.name,
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
aibitat.onMessage(async (message) => {
if (message.from !== "USER") return;
@ -54,6 +60,7 @@ const chatHistory = {
aibitat.onMessage(async () => {
try {
if (aibitat._aborted) return;
const lastResponses = aibitat.chats.slice(-2);
if (lastResponses.length !== 2) return;
const [prev, last] = lastResponses;