From 9174e56973926c22c3c9b9037440022bddfd36ba Mon Sep 17 00:00:00 2001 From: marks Date: Thu, 16 Jul 2026 19:20:32 -0500 Subject: [PATCH 1/4] Rename Agent Mode chats and sessions to tasks --- frontend/src-tauri/src/agent.rs | 145 +++++++++--- frontend/src/components/AgentMode.tsx | 224 +++++++++++------- frontend/src/components/DeleteChatDialog.tsx | 6 +- frontend/src/components/Sidebar.tsx | 23 +- .../src/components/UpgradePromptDialog.tsx | 2 +- .../src/components/agent/AgentMcpControls.tsx | 12 +- frontend/src/components/chat/ChatTurn.tsx | 4 +- .../settings/DeleteAccountSettings.tsx | 4 +- .../components/settings/HistorySettings.tsx | 20 +- .../components/settings/SettingsLayout.tsx | 2 +- frontend/src/services/agentMcpErrors.test.ts | 24 +- frontend/src/services/agentMcpErrors.ts | 33 ++- 12 files changed, 349 insertions(+), 150 deletions(-) diff --git a/frontend/src-tauri/src/agent.rs b/frontend/src-tauri/src/agent.rs index 00ace89d..64b6a7cc 100644 --- a/frontend/src-tauri/src/agent.rs +++ b/frontend/src-tauri/src/agent.rs @@ -59,7 +59,7 @@ const MAPLE_GOOSE_PERMISSION_CONFIG: &str = r#"user: never_allow: [] "#; const RUN_SHUTDOWN_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); -const DEFAULT_AGENT_SESSION_TITLE: &str = "New agent session"; +const DEFAULT_AGENT_TASK_TITLE: &str = "New task"; const DEFAULT_MCP_TIMEOUT_SECONDS: u64 = 300; const MAX_AGENT_SESSION_TITLE_CHARS: usize = 80; const MAX_AGENT_ERROR_CHARS: usize = 1_200; @@ -84,7 +84,7 @@ fn validate_session_model_lock( return Ok(()); } Err(format!( - "This Agent session is locked to model {persisted_model}. Start a new session to use {requested_model}." + "This task is locked to model {persisted_model}. Start a new task to use {requested_model}." )) } @@ -474,10 +474,28 @@ fn session_title_from_prompt(prompt: &str) -> String { title } +fn agent_task_framework_notice(message: &str) -> String { + let is_compaction_failure = message.starts_with("Ran into this error trying to compact:") + && message.ends_with("Please try again or create a new session"); + let is_context_limit = message + .starts_with("Unable to continue: Context limit still exceeded after compaction.") + && message.ends_with("or start a new session."); + let is_provider_refusal = message.starts_with("The provider refused this request.") + && message.ends_with( + "Please start a new session to continue — resending this conversation is likely to be refused again.", + ); + + if !is_compaction_failure && !is_context_limit && !is_provider_refusal { + return message.to_string(); + } + + message + .replace("create a new session", "create a new task") + .replace("start a new session", "start a new task") +} + fn should_name_session_from_prompt(session: &Session) -> bool { - session.message_count == 0 - && !session.user_set_name - && session.name == DEFAULT_AGENT_SESSION_TITLE + session.message_count == 0 && !session.user_set_name && session.name == DEFAULT_AGENT_TASK_TITLE } async fn pending_permissions_for_sessions( @@ -1008,7 +1026,7 @@ pub async fn agent_create_session( let title = request .title .filter(|value| !value.trim().is_empty()) - .unwrap_or_else(|| DEFAULT_AGENT_SESSION_TITLE.to_string()); + .unwrap_or_else(|| DEFAULT_AGENT_TASK_TITLE.to_string()); let mode = request.mode.unwrap_or(runtime_mode); let permission_mode = parse_user_permission_mode(&mode)?; let model = request.model.unwrap_or(runtime_model); @@ -1026,7 +1044,7 @@ pub async fn agent_create_session( let session = session_manager .create_session(root.clone(), title, SessionType::User, permission_mode) .await - .map_err(|e| format!("Failed to create Goose session: {e}"))?; + .map_err(|e| format!("Failed to create Agent task: {e}"))?; permission_modes .lock() @@ -1109,7 +1127,7 @@ pub async fn agent_list_sessions( let mut sessions = session_manager .list_all_sessions() .await - .map_err(|e| format!("Failed to list Goose sessions: {e}"))? + .map_err(|e| format!("Failed to list Agent tasks: {e}"))? .into_iter() .filter(|session| { if let Some(root) = filter_root.as_ref() { @@ -1148,11 +1166,11 @@ pub async fn agent_load_session( let session = session_manager .get_session(&session_id, true) .await - .map_err(|e| format!("Failed to load Goose session: {e}"))?; + .map_err(|e| format!("Failed to load Agent task: {e}"))?; let conversation = session .conversation .as_ref() - .ok_or_else(|| "Goose session history was not loaded".to_string())?; + .ok_or_else(|| "Agent task history was not loaded".to_string())?; let timeline = conversation_to_timeline_items(conversation); let timeline = overlay_live_timeline(&state.live_timelines, &session_id, conversation, timeline).await; @@ -1188,7 +1206,7 @@ pub async fn agent_list_session_mcp_servers( let session = session_manager .get_session(session_id.trim(), false) .await - .map_err(|error| format!("Failed to load Goose session: {error}"))?; + .map_err(|error| format!("Failed to load Agent task: {error}"))?; let configured = normalize_mcp_servers( load_agent_config_inner(&app_handle, &user_id) .map_err(|error| format!("Failed to load MCP servers: {error}"))? @@ -1212,7 +1230,7 @@ pub async fn agent_set_session_mcp_server_enabled( let session_id = request.session_id.trim().to_string(); let requested_key = goose::config::extensions::name_to_key(request.name.trim()); if session_id.is_empty() { - return Err("Agent session ID cannot be empty".to_string()); + return Err("Agent task ID cannot be empty".to_string()); } if requested_key.is_empty() || requested_key == "developer" { return Err("That MCP server cannot be changed".to_string()); @@ -1240,7 +1258,7 @@ pub async fn agent_set_session_mcp_server_enabled( let session = session_manager .get_session(&session_id, false) .await - .map_err(|error| format!("Failed to load Goose session: {error}"))?; + .map_err(|error| format!("Failed to load Agent task: {error}"))?; let session_mcp_keys = session_mcp_extension_keys(&session); let manager_result = agent_manager .get_or_create_agent_with_runtime_context( @@ -1300,13 +1318,13 @@ pub async fn agent_set_session_mcp_server_enabled( agent .persist_extension_state(&session_id) .await - .map_err(|error| format!("Failed to save MCP session state: {error}"))?; + .map_err(|error| format!("Failed to save task MCP settings: {error}"))?; } let refreshed = session_manager .get_session(&session_id, false) .await - .map_err(|error| format!("Failed to reload Goose session: {error}"))?; + .map_err(|error| format!("Failed to reload Agent task: {error}"))?; Ok(session_mcp_servers(&configured, &refreshed)) } @@ -1323,7 +1341,7 @@ pub async fn agent_delete_session( ensure_account_generation(&state, &account_scope, generation).await?; let session_id = session_id.trim().to_string(); if session_id.is_empty() { - return Err("Agent session ID cannot be empty".to_string()); + return Err("Agent task ID cannot be empty".to_string()); } let _session_lifecycle_guard = state.session_lifecycle.lock().await; @@ -1333,7 +1351,7 @@ pub async fn agent_delete_session( Some(current) => { ensure_runtime_account(current, &account_scope)?; if has_active_session_run(¤t.active_runs, &session_id) { - return Err("Stop the running agent before deleting this chat".to_string()); + return Err("Stop the running agent before deleting this task".to_string()); } ( Some(Arc::clone(¤t.agent_manager)), @@ -1375,11 +1393,11 @@ async fn delete_persisted_agent_session( session_manager .get_session(session_id, false) .await - .map_err(|e| format!("Failed to find Goose session {session_id}: {e}"))?; + .map_err(|e| format!("Failed to find Agent task {session_id}: {e}"))?; session_manager .delete_session(session_id) .await - .map_err(|e| format!("Failed to delete Goose session {session_id}: {e}"))?; + .map_err(|e| format!("Failed to delete Agent task {session_id}: {e}"))?; live_timelines.lock().await.remove(session_id); pending_permissions @@ -1411,7 +1429,7 @@ async fn rollback_cancelled_agent_turn( let session = session_manager .get_session(session_id, false) .await - .map_err(|error| format!("Failed to inspect cancelled Agent session: {error}"))?; + .map_err(|error| format!("Failed to inspect cancelled Agent task: {error}"))?; if !session.user_set_name { session_manager .update(session_id) @@ -1419,7 +1437,7 @@ async fn rollback_cancelled_agent_turn( .apply() .await .map_err(|error| { - format!("Failed to restore cancelled Agent session title: {error}") + format!("Failed to restore cancelled Agent task title: {error}") })?; } } @@ -1492,7 +1510,7 @@ pub async fn agent_send_message( agent_manager .try_register_cancel_token(&request.session_id, cancel_token.clone()) .await - .map_err(|e| format!("Agent session is already running: {e}"))?; + .map_err(|e| format!("Agent task is already running: {e}"))?; // A rejected or delayed send must not be able to change a live policy that // the mode command already made authoritative. Seed only sessions that do @@ -1510,7 +1528,7 @@ pub async fn agent_send_message( let mut session = session_manager .get_session(&request.session_id, true) .await - .map_err(|e| format!("Failed to load Goose session: {e}"))?; + .map_err(|e| format!("Failed to load Agent task: {e}"))?; validate_session_model_lock( session.message_count, session @@ -1534,11 +1552,11 @@ pub async fn agent_send_message( .system_generated_name(prompt_title) .apply() .await - .map_err(|e| format!("Failed to name Agent session: {e}"))?; + .map_err(|e| format!("Failed to name Agent task: {e}"))?; session = session_manager .get_session(&session.id, false) .await - .map_err(|e| format!("Failed to load named Goose session: {e}"))?; + .map_err(|e| format!("Failed to load named Agent task: {e}"))?; emit_agent_event( &app_handle, AgentEventEnvelope { @@ -1820,7 +1838,7 @@ pub async fn agent_set_permission_mode( let session_id = request.session_id.trim().to_string(); if session_id.is_empty() { - return Err("Agent permission mode update requires a session ID".to_string()); + return Err("Agent permission mode update requires a task ID".to_string()); } let goose_mode = parse_user_permission_mode(&request.mode)?; let (agent_manager, session_manager, permission_modes) = { @@ -1986,12 +2004,12 @@ pub async fn agent_permission_respond( ensure_runtime_account(current, &account_scope)?; let session_id = response.session_id.trim().to_string(); if session_id.is_empty() { - return Err("Agent permission response requires a session ID".to_string()); + return Err("Agent permission response requires a task ID".to_string()); } let key = (session_id.clone(), response.request_id.clone()); if !state.pending_permissions.lock().await.contains_key(&key) { return Err(format!( - "No pending Agent Mode permission request found for {} in session {}", + "No pending Agent Mode permission request found for {} in task {}", response.request_id, session_id )); } @@ -2325,7 +2343,7 @@ async fn run_agent_prompt(run: AgentPromptRun) -> Result>(() => new Set()); const [pendingSessionSelectionId, setPendingSessionSelectionId] = useState(null); + const [isNewTaskRequested, setIsNewTaskRequested] = useState(false); const [activeRunsBySession, setActiveRunsBySession] = useState>({}); const [completedUnreadSessionIds, setCompletedUnreadSessionIds] = useState>( () => new Set() @@ -401,7 +403,7 @@ export function AgentMode({ userId }: { userId: string }) { }, [projectRoot, visibleProjectRoots]); const activeSessionTitle = useMemo(() => { const activeSession = sessions.find((session) => session.id === activeSessionId); - return activeSession ? sessionTitle(activeSession) : "Agent session"; + return activeSession ? sessionTitle(activeSession) : "New task"; }, [activeSessionId, sessions]); const activeRunId = activeSessionId ? (activeRunsBySession[activeSessionId] ?? null) : null; const activePendingSendKey = activeSessionId || NEW_SESSION_PENDING_KEY; @@ -980,10 +982,10 @@ export function AgentMode({ userId }: { userId: string }) { userId ]); - const chooseProjectRoot = useCallback(async () => { - if (!isTauriDesktop()) return; + const chooseProjectRoot = useCallback(async (): Promise => { + if (!isTauriDesktop()) return null; try { - await trackAgentWorkflow(async () => { + return await trackAgentWorkflow(async () => { const { open } = await import("@tauri-apps/plugin-dialog"); const selected = await open({ directory: true, @@ -1005,10 +1007,13 @@ export function AgentMode({ userId }: { userId: string }) { } finally { setIsProjectRootRegistrationPending(false); } + return selected; } + return null; }); } catch (chooseError) { setError(errorMessage(chooseError)); + return null; } }, [ agentSessionSelection, @@ -1114,7 +1119,7 @@ export function AgentMode({ userId }: { userId: string }) { ); const startRuntime = useCallback( - async (restart = false) => { + async (restart = false, requestedProjectRoot = projectRoot) => { const requestGeneration = startRequestGenerationRef.current + 1; startRequestGenerationRef.current = requestGeneration; const interactionGeneration = interactionGenerationRef.current; @@ -1122,12 +1127,16 @@ export function AgentMode({ userId }: { userId: string }) { setIsStarting(true); try { return await trackAgentWorkflow(async () => { - if (!projectRoot) { + if (!requestedProjectRoot) { throw new Error("Select a project folder first"); } await ensureMapleProxyReady(); const requestedMode = selectedModeRef.current; - const request = { projectRoot, model: model || DEFAULT_MODEL, mode: requestedMode }; + const request = { + projectRoot: requestedProjectRoot, + model: model || DEFAULT_MODEL, + mode: requestedMode + }; const runStateGeneration = runStateGenerationRef.current; const status = restart ? await agentRuntimeService.restartRuntime(userId, request) @@ -1139,7 +1148,7 @@ export function AgentMode({ userId }: { userId: string }) { return status; } applyRuntimeStatus(status, runStateGeneration); - setProjectRoot(status.projectRoot || projectRoot); + setProjectRoot(status.projectRoot || requestedProjectRoot); setModel(status.model || model || DEFAULT_MODEL); applyAuthoritativeMode(normalizeAgentPermissionMode(status.mode || requestedMode)); await refreshSessions(); @@ -1198,7 +1207,7 @@ export function AgentMode({ userId }: { userId: string }) { setHasManualProxyConflict(true); } else if (replaceError instanceof AgentProxyReplacementSetupError) { setHasManualProxyConflict(false); - setError(replaceError.message); + setError(errorMessage(replaceError)); } else { setError(errorMessage(replaceError)); } @@ -1234,7 +1243,7 @@ export function AgentMode({ userId }: { userId: string }) { if (!sessionId) { const detail = await agentRuntimeService.createSession(userId, { projectRoot, - title: "New agent session", + title: "New task", model: model || DEFAULT_MODEL, mode: selectedModeRef.current, mcpServerNames: selectedNewChatMcpServerNames @@ -1282,71 +1291,102 @@ export function AgentMode({ userId }: { userId: string }) { ] ); - const createSession = useCallback(async () => { - if (pendingSessionSelectionIdRef.current === NEW_SESSION_PENDING_KEY) return; - const selectionGeneration = beginSessionSelection(NEW_SESSION_PENDING_KEY); - const interactionGeneration = interactionGenerationRef.current; - setError(null); - try { - const detail = await trackAgentWorkflow(async () => { - if (!runtimeStatus?.running) { - await startRuntime(false); - } - return await agentRuntimeService.createSession(userId, { - projectRoot, - title: "New agent session", - model: model || DEFAULT_MODEL, - mode: selectedModeRef.current, - mcpServerNames: selectedNewChatMcpServerNames + const createSession = useCallback( + async (root = projectRoot) => { + if (pendingSessionSelectionIdRef.current === NEW_SESSION_PENDING_KEY) return; + const selectionGeneration = beginSessionSelection(NEW_SESSION_PENDING_KEY); + const interactionGeneration = interactionGenerationRef.current; + setError(null); + try { + const detail = await trackAgentWorkflow(async () => { + if (!runtimeStatus?.running) { + await startRuntime(false, root); + } + return await agentRuntimeService.createSession(userId, { + projectRoot: root, + title: "New task", + model: model || DEFAULT_MODEL, + mode: selectedModeRef.current, + mcpServerNames: selectedNewChatMcpServerNames + }); }); - }); - deletedSessionIdsRef.current.delete(detail.session.id); - setSessions((current) => [ - detail.session, - ...current.filter((session) => session.id !== detail.session.id) - ]); - replaceSessionTimeline(detail.session.id, detail.timeline); - - if ( - isAgentModeMountedRef.current && - sessionSelectionGenerationRef.current === selectionGeneration && - interactionGenerationRef.current === interactionGeneration - ) { - shouldAutoScrollRef.current = true; - activeSessionIdRef.current = detail.session.id; - setActiveSessionId(detail.session.id); - agentSessionSelection.remember(userId, detail.session.id); - applyAuthoritativeMode(normalizeAgentPermissionMode(detail.session.mode)); + deletedSessionIdsRef.current.delete(detail.session.id); + setSessions((current) => [ + detail.session, + ...current.filter((session) => session.id !== detail.session.id) + ]); replaceSessionTimeline(detail.session.id, detail.timeline); - const mcpError = mcpConnectionErrorMessage(detail.mcpErrors); - if (mcpError) setError(mcpError); - } - } catch (createError) { - if ( - isAgentModeMountedRef.current && - sessionSelectionGenerationRef.current === selectionGeneration && - interactionGenerationRef.current === interactionGeneration - ) { - setError(errorMessage(createError)); - } - } finally { - if (isAgentModeMountedRef.current) { - finishSessionSelection(selectionGeneration); + + if ( + isAgentModeMountedRef.current && + sessionSelectionGenerationRef.current === selectionGeneration && + interactionGenerationRef.current === interactionGeneration + ) { + shouldAutoScrollRef.current = true; + activeSessionIdRef.current = detail.session.id; + setActiveSessionId(detail.session.id); + agentSessionSelection.remember(userId, detail.session.id); + applyAuthoritativeMode(normalizeAgentPermissionMode(detail.session.mode)); + replaceSessionTimeline(detail.session.id, detail.timeline); + const mcpError = mcpConnectionErrorMessage(detail.mcpErrors); + if (mcpError) setError(mcpError); + } + } catch (createError) { + if ( + isAgentModeMountedRef.current && + sessionSelectionGenerationRef.current === selectionGeneration && + interactionGenerationRef.current === interactionGeneration + ) { + setError(errorMessage(createError)); + } + } finally { + if (isAgentModeMountedRef.current) { + finishSessionSelection(selectionGeneration); + } } + }, + [ + applyAuthoritativeMode, + agentSessionSelection, + beginSessionSelection, + finishSessionSelection, + model, + projectRoot, + replaceSessionTimeline, + runtimeStatus?.running, + selectedNewChatMcpServerNames, + startRuntime, + trackAgentWorkflow, + userId + ] + ); + + const requestNewTask = useCallback(() => setIsNewTaskRequested(true), []); + + useEffect(() => { + if (!isNewTaskRequested || isInitializing) return; + + setIsNewTaskRequested(false); + if (areAgentSettingsLocked) return; + hasAttemptedSessionRestoreRef.current = true; + + if (projectRoot) { + void createSession(); + } else { + void (async () => { + const selectedProjectRoot = await chooseProjectRoot(); + if (selectedProjectRoot) { + await createSession(selectedProjectRoot); + } + })(); } }, [ - applyAuthoritativeMode, - agentSessionSelection, - beginSessionSelection, - finishSessionSelection, - model, - projectRoot, - replaceSessionTimeline, - runtimeStatus?.running, - selectedNewChatMcpServerNames, - startRuntime, - trackAgentWorkflow, - userId + areAgentSettingsLocked, + chooseProjectRoot, + createSession, + isInitializing, + isNewTaskRequested, + projectRoot ]); const loadSession = useCallback( @@ -1364,7 +1404,7 @@ export function AgentMode({ userId }: { userId: string }) { return { detail, timelineRevision }; } } - throw new Error("This Agent session is still updating. Try selecting it again shortly."); + throw new Error("This task is still updating. Try selecting it again shortly."); }); const { detail, timelineRevision } = loaded; if ( @@ -1381,7 +1421,7 @@ export function AgentMode({ userId }: { userId: string }) { // that case leave the previous chat intact instead of overwriting the // newer timeline with a stale snapshot. if (!replaceSessionTimeline(detail.session.id, detail.timeline, timelineRevision)) { - throw new Error("This Agent session changed while loading. Try selecting it again."); + throw new Error("This task changed while loading. Try selecting it again."); } // Commit the selected session and all of its settings together. Until @@ -1604,7 +1644,7 @@ export function AgentMode({ userId }: { userId: string }) { async (item: AgentTimelineItem, decision: AgentPermissionDecision) => { const sessionId = activeSessionIdRef.current; try { - if (!sessionId) throw new Error("No active Agent session for this permission request"); + if (!sessionId) throw new Error("No active task for this permission request"); await agentRuntimeService.respondToPermission( userId, sessionId, @@ -1870,6 +1910,7 @@ export function AgentMode({ userId }: { userId: string }) { void loadSession(sessionId)} /> } + onNewItem={requestNewTask} onToggle={toggleSidebar} /> @@ -1902,7 +1944,8 @@ export function AgentMode({ userId }: { userId: string }) { if (!open) setSessionToDelete(null); }} chatTitle={sessionTitle(sessionToDelete)} - description={`This will delete "${sessionTitle(sessionToDelete)}" from Agent Mode. This action cannot be undone.`} + itemLabel="task" + description={`This will permanently delete the task "${sessionTitle(sessionToDelete)}". This action cannot be undone.`} onConfirm={() => void deleteSession(sessionToDelete.id)} /> ) : null} @@ -1931,6 +1974,7 @@ export function AgentMode({ userId }: { userId: string }) { title={activeSessionTitle} isSidebarOpen={isSidebarOpen} onNewChat={() => void createSession()} + newItemLabel="New Task" /> ) : null} @@ -1944,7 +1988,7 @@ export function AgentMode({ userId }: { userId: string }) {

Maple cannot verify that this existing Local OpenAI Proxy key belongs to the signed-in account. This can happen once after upgrading from an older Agent Mode - build. Your chats remain available; replace the saved local setup before sending + build. Your tasks remain available; replace the saved local setup before sending another message. The existing backend key will remain in API Management.

@@ -2629,9 +2673,7 @@ function AgentSidebarContent({ size="icon" className="h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground" onClick={() => toggleProjectCollapsed(root.path)} - aria-label={ - isCollapsed ? "Expand project sessions" : "Collapse project sessions" - } + aria-label={isCollapsed ? "Expand project tasks" : "Collapse project tasks"} > {isCollapsed ? ( @@ -2648,7 +2690,7 @@ function AgentSidebarContent({ className="h-7 w-7 shrink-0 text-muted-foreground hover:text-foreground" onClick={onCreateSession} disabled={disabled || !projectRoot} - aria-label="New agent session" + aria-label="New Task" > @@ -2659,7 +2701,7 @@ function AgentSidebarContent({
{projectSessions.length === 0 ? ( isActive ? ( -

No sessions yet

+

No tasks yet

) : null ) : ( projectSessions.map((session) => { @@ -2726,7 +2768,7 @@ function AgentSidebarContent({ event.preventDefault(); event.stopPropagation(); }} - aria-label={`Open chat menu for ${title}`} + aria-label={`Open task menu for ${title}`} > - {isRunning ? "Stop Agent Before Deleting" : "Delete Chat"} + {isRunning + ? "Stop Agent Before Deleting Task" + : "Delete Task"} @@ -2788,10 +2832,10 @@ function AgentSidebarContent({

- Chats + Tasks

- Folderless agent chats are not available yet. + Folderless Agent tasks are not available yet.

@@ -3751,7 +3795,7 @@ function permissionRequestId(item: AgentTimelineItem): string { } function sessionTitle(session: AgentSessionSummary): string { - return session.title || "Agent session"; + return session.title || "New task"; } function toolTitle(item: AgentTimelineItem): string { @@ -3798,7 +3842,11 @@ function basename(path: string): string { } function errorMessage(error: unknown): string { - if (error instanceof Error) return error.message; - if (typeof error === "string") return error; - return "Agent Mode failed"; + const message = + error instanceof Error + ? error.message + : typeof error === "string" + ? error + : "Agent Mode failed"; + return agentTaskErrorMessage(message); } diff --git a/frontend/src/components/DeleteChatDialog.tsx b/frontend/src/components/DeleteChatDialog.tsx index b7a71f70..d1bfca61 100644 --- a/frontend/src/components/DeleteChatDialog.tsx +++ b/frontend/src/components/DeleteChatDialog.tsx @@ -15,6 +15,7 @@ interface DeleteChatDialogProps { onConfirm: () => void; chatTitle: string; description?: React.ReactNode; + itemLabel?: "chat" | "task"; } export function DeleteChatDialog({ @@ -22,7 +23,8 @@ export function DeleteChatDialog({ onOpenChange, onConfirm, chatTitle, - description + description, + itemLabel = "chat" }: DeleteChatDialogProps) { const handleConfirm = (e: React.MouseEvent) => { e.preventDefault(); @@ -34,7 +36,7 @@ export function DeleteChatDialog({ - Are you sure you want to delete this chat? + Are you sure you want to delete this {itemLabel}? {description ?? ( <>This will permanently delete "{chatTitle}". This action cannot be undone. diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 722032c3..48cf56d4 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -44,12 +44,16 @@ export function Sidebar({ isOpen, mode = "chat", navigationContent, + newItemDisabled = false, + onNewItem, onToggle }: { chatId?: string; isOpen: boolean; mode?: WorkspaceMode; navigationContent?: ReactNode; + newItemDisabled?: boolean; + onNewItem?: () => void; onToggle: () => void; }) { const router = useRouter(); @@ -226,6 +230,18 @@ export function Sidebar({ const showAgentMode = agentModeAvailable && billingStatus !== null && agentModeEnabled === true; const isAgentMode = mode === "agent"; + async function addItem() { + if (!isAgentMode) { + await addChat(); + return; + } + + if (isOpen && isCompactLayout) { + onToggle(); + } + onNewItem?.(); + } + useEffect(() => { if (!agentModeAvailable || !userId) return; @@ -349,11 +365,12 @@ export function Sidebar({
{!isAgentMode && ( diff --git a/frontend/src/components/settings/DeleteAccountSettings.tsx b/frontend/src/components/settings/DeleteAccountSettings.tsx index 498dad84..96ee2aaf 100644 --- a/frontend/src/components/settings/DeleteAccountSettings.tsx +++ b/frontend/src/components/settings/DeleteAccountSettings.tsx @@ -129,8 +129,8 @@ export function DeleteAccountSettings() { - This action cannot be undone. You will lose access to paid features, chat history, and - settings. + This action cannot be undone. You will lose access to paid features, chat and task + history, and settings. diff --git a/frontend/src/components/settings/HistorySettings.tsx b/frontend/src/components/settings/HistorySettings.tsx index 21e41529..6472ebd2 100644 --- a/frontend/src/components/settings/HistorySettings.tsx +++ b/frontend/src/components/settings/HistorySettings.tsx @@ -44,14 +44,14 @@ export function HistorySettings() { try { await navigate({ to: "/", ignoreBlocker: true }); } catch (navigationError) { - console.error("Chat history was deleted, but navigation failed:", navigationError); + console.error("History was deleted, but navigation failed:", navigationError); window.location.href = "/"; return; } window.dispatchEvent(new CustomEvent("newchat", { detail: { projectId: null } })); } catch (deleteError) { - console.error("Error deleting chat history:", deleteError); - setError("Maple could not delete all chat history. Please try again."); + console.error("Error deleting chat and task history:", deleteError); + setError("Maple could not delete all chat and task history. Please try again."); } finally { operationBlock?.release(); setIsDeleting(false); @@ -60,19 +60,21 @@ export function HistorySettings() { return (
- {error && } + {error && ( + + )} {isConfirming ? (
-

Delete your entire chat history?

+

Delete your entire chat and task history?

This action cannot be undone.