diff --git a/apps/web/src/components/CommandPalette.tsx b/apps/web/src/components/CommandPalette.tsx
index 7862191d40b..d2642589a29 100644
--- a/apps/web/src/components/CommandPalette.tsx
+++ b/apps/web/src/components/CommandPalette.tsx
@@ -18,6 +18,7 @@ import {
ArrowDownIcon,
ArrowLeftIcon,
ArrowUpIcon,
+ ArchiveIcon,
CornerLeftUpIcon,
FolderIcon,
FolderPlusIcon,
@@ -47,6 +48,7 @@ import {
} from "../environments/runtime";
import { useHandleNewThread } from "../hooks/useHandleNewThread";
import { useSettings } from "../hooks/useSettings";
+import { useThreadActions } from "../hooks/useThreadActions";
import { readLocalApi } from "../localApi";
import {
getSourceControlDiscoverySnapshot,
@@ -404,6 +406,7 @@ function OpenCommandPaletteDialog() {
const settings = useSettings();
const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread } =
useHandleNewThread();
+ const { archiveThread } = useThreadActions();
const projects = useStore(useShallow(selectProjectsAcrossEnvironments));
const threads = useStore(useShallow(selectSidebarThreadsAcrossEnvironments));
const keybindings = useServerKeybindings();
@@ -1021,6 +1024,31 @@ function OpenCommandPaletteDialog() {
});
}
+ if (activeThread) {
+ actionItems.push({
+ kind: "action",
+ value: "action:archive-current-thread",
+ searchTerms: ["archive", "current thread", "hide thread"],
+ title: "Archive current thread",
+ description: activeThread.title,
+ icon: ,
+ shortcutCommand: "thread.archiveCurrent",
+ run: async () => {
+ await archiveThread(scopeThreadRef(activeThread.environmentId, activeThread.id)).catch(
+ (error: unknown) => {
+ toastManager.add(
+ stackedThreadToast({
+ type: "error",
+ title: "Failed to archive thread",
+ description: error instanceof Error ? error.message : "An error occurred.",
+ }),
+ );
+ },
+ );
+ },
+ });
+ }
+
actionItems.push({
kind: "action",
value: "action:add-project",
diff --git a/apps/web/src/routes/_chat.tsx b/apps/web/src/routes/_chat.tsx
index da22d7e6028..8a5692d4237 100644
--- a/apps/web/src/routes/_chat.tsx
+++ b/apps/web/src/routes/_chat.tsx
@@ -3,6 +3,7 @@ import { useEffect } from "react";
import { useCommandPaletteStore } from "../commandPaletteStore";
import { useHandleNewThread } from "../hooks/useHandleNewThread";
+import { useThreadActions } from "../hooks/useThreadActions";
import {
startNewLocalThreadFromContext,
startNewThreadFromContext,
@@ -11,6 +12,7 @@ import { isTerminalFocused } from "../lib/terminalFocus";
import { resolveShortcutCommand } from "../keybindings";
import { selectThreadTerminalState, useTerminalStateStore } from "../terminalStateStore";
import { useThreadSelectionStore } from "../threadSelectionStore";
+import { stackedThreadToast, toastManager } from "~/components/ui/toast";
import { resolveSidebarNewThreadEnvMode } from "~/components/Sidebar.logic";
import { useSettings } from "~/hooks/useSettings";
import { useServerKeybindings } from "~/rpc/serverState";
@@ -20,6 +22,7 @@ function ChatRouteGlobalShortcuts() {
const selectedThreadKeysSize = useThreadSelectionStore((state) => state.selectedThreadKeys.size);
const { activeDraftThread, activeThread, defaultProjectRef, handleNewThread, routeThreadRef } =
useHandleNewThread();
+ const { archiveThread } = useThreadActions();
const keybindings = useServerKeybindings();
const terminalOpen = useTerminalStateStore((state) =>
routeThreadRef
@@ -75,6 +78,22 @@ function ChatRouteGlobalShortcuts() {
}),
handleNewThread,
});
+ return;
+ }
+
+ if (command === "thread.archiveCurrent") {
+ if (!routeThreadRef) return;
+ event.preventDefault();
+ event.stopPropagation();
+ void archiveThread(routeThreadRef).catch((error: unknown) => {
+ toastManager.add(
+ stackedThreadToast({
+ type: "error",
+ title: "Failed to archive thread",
+ description: error instanceof Error ? error.message : "An error occurred.",
+ }),
+ );
+ });
}
};
@@ -85,10 +104,12 @@ function ChatRouteGlobalShortcuts() {
}, [
activeDraftThread,
activeThread,
+ archiveThread,
clearSelection,
handleNewThread,
keybindings,
defaultProjectRef,
+ routeThreadRef,
selectedThreadKeysSize,
terminalOpen,
appSettings.defaultThreadEnvMode,
diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts
index 502e564fb82..eff3f67d4f0 100644
--- a/packages/contracts/src/keybindings.ts
+++ b/packages/contracts/src/keybindings.ts
@@ -37,6 +37,7 @@ export type ModelPickerJumpKeybindingCommand =
export const THREAD_KEYBINDING_COMMANDS = [
"thread.previous",
"thread.next",
+ "thread.archiveCurrent",
...THREAD_JUMP_KEYBINDING_COMMANDS,
] as const;
export type ThreadKeybindingCommand = (typeof THREAD_KEYBINDING_COMMANDS)[number];
diff --git a/packages/shared/src/keybindings.ts b/packages/shared/src/keybindings.ts
index 3cc2e913621..6f8d075b8f2 100644
--- a/packages/shared/src/keybindings.ts
+++ b/packages/shared/src/keybindings.ts
@@ -32,6 +32,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray = [
{ key: "mod+o", command: "editor.openFavorite" },
{ key: "mod+shift+[", command: "thread.previous" },
{ key: "mod+shift+]", command: "thread.next" },
+ { key: "mod+shift+a", command: "thread.archiveCurrent", when: "!terminalFocus" },
...THREAD_JUMP_KEYBINDING_COMMANDS.map((command, index) => ({
key: `mod+${index + 1}`,
command,