From 571f6c3ef350882a2f9d5e360f4ce2716cc7b6cc Mon Sep 17 00:00:00 2001 From: Vaayne Date: Thu, 31 Jul 2025 23:53:41 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20implement=20session-based?= =?UTF-8?q?=20message=20isolation=20and=20improve=20agent=20creation=20UX?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add sessionId field to PocMessage interface for session isolation - Filter messages by current session to prevent cross-session pollution - Remove automatic agent creation - only manual creation by users - Add beautiful empty state when no agents exist - Enhance sidebar UX with prominent "Create Agent" button when empty - Update message hooks to handle session-specific messaging - Improve user control over agent lifecycle management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/renderer/src/hooks/usePocMessages.ts | 39 ++-- .../pages/cherry-agent/CherryAgentPage.tsx | 169 ++++++++++++------ src/renderer/src/types/cherryAgent.ts | 1 + 3 files changed, 138 insertions(+), 71 deletions(-) diff --git a/src/renderer/src/hooks/usePocMessages.ts b/src/renderer/src/hooks/usePocMessages.ts index 9b71c90d1..0ff5bd0c6 100644 --- a/src/renderer/src/hooks/usePocMessages.ts +++ b/src/renderer/src/hooks/usePocMessages.ts @@ -15,31 +15,32 @@ const logger = loggerService.withContext('UsePocMessages') * - Handles message completion status * - Real-time updates from AgentCommandService events */ -export function usePocMessages() { +export function usePocMessages(currentSessionId?: string) { const [messages, setMessages] = useState([]) /** * Adds a user command message to the list */ - const addUserCommand = useCallback((command: string, commandId: string) => { + const addUserCommand = useCallback((command: string, commandId: string, sessionId?: string) => { const message: PocMessage = { id: `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, type: 'user-command', content: command, timestamp: Date.now(), commandId, + sessionId, isComplete: true } setMessages((prev) => [...prev, message]) - logger.debug('Added user command message', { commandId, command: command.substring(0, 50) }) + logger.debug('Added user command message', { commandId, sessionId, command: command.substring(0, 50) }) }, []) /** * Appends streaming output to existing message or creates new one */ const appendOutput = useCallback( - (commandId: string, content: string, type: 'output' | 'error' = 'output', isComplete: boolean = false) => { + (commandId: string, content: string, type: 'output' | 'error' = 'output', isComplete: boolean = false, sessionId?: string) => { setMessages((prev) => { // Find existing output message for this command const existingIndex = prev.findIndex( @@ -64,6 +65,7 @@ export function usePocMessages() { content, timestamp: Date.now(), commandId, + sessionId, isComplete } return [...prev, message] @@ -74,7 +76,8 @@ export function usePocMessages() { commandId, type, contentLength: content.length, - isComplete + isComplete, + sessionId }) }, [] @@ -95,18 +98,19 @@ export function usePocMessages() { /** * Adds a system message (e.g., command completion notifications) */ - const addSystemMessage = useCallback((content: string, commandId?: string) => { + const addSystemMessage = useCallback((content: string, commandId?: string, sessionId?: string) => { const message: PocMessage = { id: `system_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, type: 'system', content, timestamp: Date.now(), commandId, + sessionId, isComplete: true } setMessages((prev) => [...prev, message]) - logger.debug('Added system message', { content, commandId }) + logger.debug('Added system message', { content, commandId, sessionId }) }, []) /** @@ -127,6 +131,16 @@ export function usePocMessages() { [messages] ) + /** + * Gets messages for a specific session + */ + const getMessagesForSession = useCallback( + (sessionId: string): PocMessage[] => { + return messages.filter((msg) => msg.sessionId === sessionId) + }, + [messages] + ) + /** * Gets the latest incomplete output message for a command */ @@ -147,7 +161,7 @@ export function usePocMessages() { if (type === 'stdout' || type === 'stderr') { const messageType = type === 'stderr' ? 'error' : 'output' - appendOutput(commandId, data, messageType, false) + appendOutput(commandId, data, messageType, false, currentSessionId) } else if (type === 'exit') { // Complete any streaming messages when command exits completeMessage(commandId, 'output') @@ -156,15 +170,15 @@ export function usePocMessages() { // Add system message for command completion const exitCode = output.exitCode ?? 0 const statusMessage = exitCode === 0 ? 'Command completed successfully' : `Command exited with code ${exitCode}` - addSystemMessage(statusMessage, commandId) + addSystemMessage(statusMessage, commandId, currentSessionId) } else if (type === 'error') { - appendOutput(commandId, data, 'error', true) + appendOutput(commandId, data, 'error', true, currentSessionId) } }) // Handle command errors const unsubscribeError = agentCommandService.on('commandError', ({ commandId, error }) => { - addSystemMessage(`Command error: ${error}`, commandId) + addSystemMessage(`Command error: ${error}`, commandId, currentSessionId) }) // Cleanup on unmount @@ -173,7 +187,7 @@ export function usePocMessages() { unsubscribeOutput() unsubscribeError() } - }, [appendOutput, completeMessage, addSystemMessage]) + }, [appendOutput, completeMessage, addSystemMessage, currentSessionId]) return { messages, @@ -183,6 +197,7 @@ export function usePocMessages() { addSystemMessage, clearMessages, getMessagesForCommand, + getMessagesForSession, getStreamingMessage } } diff --git a/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx b/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx index 5288523f1..aef8ac509 100644 --- a/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx +++ b/src/renderer/src/pages/cherry-agent/CherryAgentPage.tsx @@ -52,10 +52,10 @@ const CherryAgentPage: React.FC = () => { const { isLeftNavbar } = useNavbarPosition() // Initialize hooks - const messagesHook = usePocMessages() + const agentManagement = useAgentManagement() + const messagesHook = usePocMessages(agentManagement.currentSession?.id) const commandHook = usePocCommand() const historyHook = useCommandHistory() - const agentManagement = useAgentManagement() // Local state for command count tracking const [commandCount, setCommandCount] = useState(0) @@ -80,7 +80,7 @@ const CherryAgentPage: React.FC = () => { const commandId = await commandHook.executeCommand(command, workingDirectory) if (commandId) { // Add user command message - messagesHook.addUserCommand(command, commandId) + messagesHook.addUserCommand(command, commandId, agentManagement.currentSession?.id) setCommandCount((prev) => prev + 1) } }, @@ -94,10 +94,10 @@ const CherryAgentPage: React.FC = () => { // Note: The type determination (stdout vs stderr) is handled in usePocMessages // based on the command output type from AgentCommandService if (isBuffered && output) { - messagesHook.appendOutput(commandId, output, 'output', false) + messagesHook.appendOutput(commandId, output, 'output', false, agentManagement.currentSession?.id) } }) - }, [commandHook, messagesHook]) + }, [commandHook, messagesHook, agentManagement.currentSession?.id]) // Determine current status for status bar const getCommandStatus = useCallback(() => { @@ -267,10 +267,10 @@ const CherryAgentPage: React.FC = () => { if (commandHook.currentCommandId) { const success = await commandHook.interruptCommand(commandHook.currentCommandId) if (success) { - messagesHook.appendOutput(commandHook.currentCommandId, '\n[Command cancelled by user]', 'error', true) + messagesHook.appendOutput(commandHook.currentCommandId, '\n[Command cancelled by user]', 'error', true, agentManagement.currentSession?.id) } } - }, [commandHook, messagesHook]) + }, [commandHook, messagesHook, agentManagement.currentSession?.id]) const toggleSidebar = useCallback(() => { setSidebarCollapsed((prev) => !prev) @@ -302,17 +302,6 @@ const CherryAgentPage: React.FC = () => { (session) => agentManagement.currentAgent && session.agent_ids.includes(agentManagement.currentAgent.id) ) - // Create a default agent if none exists - useEffect(() => { - if (!agentManagement.loadingAgents && agentManagement.agents.length === 0) { - agentManagement.createAgent({ - name: 'Assistant', - model: '', - description: 'General-purpose AI assistant', - instructions: 'You are a helpful assistant that can execute commands and help with tasks.' - }) - } - }, [agentManagement]) // Set the first agent as current if none is selected useEffect(() => { @@ -350,13 +339,24 @@ const CherryAgentPage: React.FC = () => { agents - } - size="small" - title="Agent Actions" - onClick={handleCreateAgent} - /> + {agentManagement.agents.length === 0 ? ( + + ) : ( + } + size="small" + title="Create New Agent" + onClick={handleCreateAgent} + /> + )} } onClick={toggleSidebar} size="small" /> @@ -461,42 +461,64 @@ const CherryAgentPage: React.FC = () => { {/* Main Content Area */} - {/* Agent Info Header */} - - {sidebarCollapsed && ( - } onClick={toggleSidebar} size="small" /> - )} - {agentManagement.currentAgent && ( - - {agentManagement.currentAgent.name.charAt(0).toUpperCase()} - - )} - - handleAgentNameChange(e.target.value)} - onBlur={(e) => handleAgentNameChange(e.target.value)} - placeholder="Agent Name" - /> - {agentManagement.currentAgent?.description || 'No description provided'} - - + {agentManagement.currentAgent ? ( + <> + {/* Agent Info Header */} + + {sidebarCollapsed && ( + } onClick={toggleSidebar} size="small" /> + )} + + {agentManagement.currentAgent.name.charAt(0).toUpperCase()} + + + handleAgentNameChange(e.target.value)} + onBlur={(e) => handleAgentNameChange(e.target.value)} + placeholder="Agent Name" + /> + {agentManagement.currentAgent.description || 'No description provided'} + + - - - + + + - - - + + + + + ) : ( + + {sidebarCollapsed && ( + } onClick={toggleSidebar} size="small" style={{ position: 'absolute', top: '20px', left: '20px' }} /> + )} + + No Agent Selected + + Create an agent to get started with Cherry Agent. You can create different agents with specific roles and capabilities. + + + + + )}