mirror of
https://github.com/wassname/HumanAgent-MCP.git
synced 2026-09-17 11:50:48 +08:00
Implement clean request state management system
MAJOR IMPROVEMENT: Replace confusing 'human-agent-request' event with clean 'request-state-change' SSE system ✅ NEW: request-state-change events for both web and VS Code interfaces ✅ CLEAN: Separate message display (via ChatManager) from input control (via request state) ✅ CONSISTENT: Both interfaces get same state updates via SSE ✅ ELIMINATED: Duplicate message display issues ✅ PROPER: Input enabling/disabling and 'waiting for response' indicators States: 'waiting_for_response' → 'completed' Benefits: Clean separation of concerns, no more mixed event types causing confusion Ready for testing: VS Code input should enable when AI asks questions, disable when complete
This commit is contained in:
Vendored
+2596
-368
File diff suppressed because it is too large
Load Diff
@@ -22,3 +22,7 @@
|
||||
2025-10-24T02:22:43.114Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761272315744
|
||||
2025-10-24T02:23:14.737Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761272566830
|
||||
2025-10-24T02:24:18.799Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761272641413
|
||||
2025-10-24T02:29:47.107Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761272965017
|
||||
2025-10-24T02:33:40.664Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761273090722
|
||||
2025-10-24T02:34:46.816Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761273266913
|
||||
2025-10-24T02:39:08.715Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761273289660
|
||||
|
||||
+27
-19
@@ -138,9 +138,9 @@ export class McpServer extends EventEmitter {
|
||||
}
|
||||
|
||||
private setupEventForwarding(): void {
|
||||
this.on('human-agent-request', (data) => {
|
||||
this.debugLogger.log('SSE', 'Forwarding human-agent-request to SSE connections');
|
||||
this.broadcastToSSE('human-agent-request', data);
|
||||
this.on('request-state-change', (data) => {
|
||||
this.debugLogger.log('SSE', 'Forwarding request-state-change to SSE connections');
|
||||
this.broadcastToSSE('request-state-change', data);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1261,14 +1261,12 @@ export class McpServer extends EventEmitter {
|
||||
addMessageToUI(data.sessionId, data.message.sender, data.message.content, data.message.source);
|
||||
} else if (data.type === 'message' && data.sessionId) {
|
||||
addMessageToUI(data.sessionId, data.role || 'assistant', data.content);
|
||||
} else if (data.type === 'human-agent-request' && data.data) {
|
||||
// Handle human-agent-request messages (AI questions to user)
|
||||
console.log('Web interface received human-agent-request:', data.data);
|
||||
} else if (data.type === 'request-state-change' && data.data) {
|
||||
// Handle request state changes for input control
|
||||
console.log('Web interface received request-state-change:', data.data);
|
||||
|
||||
// Add AI message to current session (no need to track requestId - we get it from server state)
|
||||
const sessionId = activeSessionId || 'default';
|
||||
const displayMessage = data.data.context ? \`\${data.data.context}\\n\\n\${data.data.message}\` : data.data.message;
|
||||
addMessageToUI(sessionId, 'agent', displayMessage);
|
||||
// No message display here - messages are handled via chat_message SSE
|
||||
// This event only manages input state and waiting indicators
|
||||
} else if (data.type === 'session_update') {
|
||||
// Refresh the page to show new sessions
|
||||
window.location.reload();
|
||||
@@ -1406,15 +1404,6 @@ export class McpServer extends EventEmitter {
|
||||
const displayMessage = params.context ? `${params.context}\n\n${params.message}` : params.message;
|
||||
this.debugLogger.log('TOOL', 'Displaying message in chat UI:', displayMessage);
|
||||
|
||||
// Emit event to show message in chat UI immediately
|
||||
this.emit('human-agent-request', {
|
||||
requestId,
|
||||
message: params.message,
|
||||
context: params.context,
|
||||
priority: params.priority || 'normal',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Wait for human response
|
||||
return new Promise((resolve) => {
|
||||
// Set up timeout
|
||||
@@ -1451,6 +1440,16 @@ export class McpServer extends EventEmitter {
|
||||
this.debugLogger.log('CHAT', `Stored AI message in ChatManager for session ${sessionToUse}: ${aiMessage.content.substring(0, 50)}...`);
|
||||
this.broadcastMessageToClients(sessionToUse, aiMessage);
|
||||
|
||||
// Emit request state to enable input controls and show waiting indicator
|
||||
this.emit('request-state-change', {
|
||||
requestId,
|
||||
sessionId: sessionToUse,
|
||||
state: 'waiting_for_response',
|
||||
message: params.message,
|
||||
context: params.context,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
this.chatManager.addPendingRequest(sessionToUse, requestId, params);
|
||||
this.requestResolvers.set(requestId, {
|
||||
resolve: (response: string) => {
|
||||
@@ -1458,6 +1457,15 @@ export class McpServer extends EventEmitter {
|
||||
const responseTime = Date.now() - startTime;
|
||||
this.debugLogger.log('TOOL', `Request ${requestId} completed with response:`, response);
|
||||
|
||||
// Emit request completed state to disable input controls and hide waiting indicator
|
||||
this.emit('request-state-change', {
|
||||
requestId,
|
||||
sessionId: sessionToUse,
|
||||
state: 'completed',
|
||||
response: response,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Don't store human response as assistant message
|
||||
// The 'response' here is the human's answer to AI's question
|
||||
// The AI will generate its own response separately after receiving this
|
||||
|
||||
@@ -1051,8 +1051,8 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('SSE event received:', data);
|
||||
|
||||
if (data.type === 'human-agent-request') {
|
||||
handleHumanAgentRequest(data.data);
|
||||
if (data.type === 'request-state-change') {
|
||||
handleRequestStateChange(data.data);
|
||||
} else if (data.type === 'chat_message') {
|
||||
handleIncomingChatMessage(data);
|
||||
// Removed web_user_message auto-trigger - no longer needed
|
||||
@@ -1076,39 +1076,18 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
// Global variable to store current request ID for responses
|
||||
let currentPendingRequestId = null;
|
||||
|
||||
function handleHumanAgentRequest(data) {
|
||||
console.log('Handling human agent request:', data);
|
||||
function handleRequestStateChange(data) {
|
||||
console.log('Handling request state change:', data);
|
||||
|
||||
// Store the request ID for sending response
|
||||
currentPendingRequestId = data.requestId;
|
||||
|
||||
// Add the AI message to chat
|
||||
const messagesContainer = document.getElementById('messages');
|
||||
if (messagesContainer) {
|
||||
// Remove empty state if it exists
|
||||
const emptyState = messagesContainer.querySelector('.empty-state');
|
||||
if (emptyState) {
|
||||
emptyState.remove();
|
||||
}
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = 'message ai-message';
|
||||
|
||||
const displayMessage = data.context ? \`\${data.context}\\n\\n\${data.message}\` : data.message;
|
||||
messageDiv.innerHTML = \`
|
||||
<div class="message-header">
|
||||
<strong>AI Agent</strong>
|
||||
<span class="timestamp">\${new Date(data.timestamp).toLocaleTimeString()}</span>
|
||||
</div>
|
||||
<div class="message-content">\${displayMessage.replace(/\\n/g, '<br>')}</div>
|
||||
\`;
|
||||
|
||||
messagesContainer.appendChild(messageDiv);
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const sendButton = document.getElementById('sendButton');
|
||||
|
||||
if (data.state === 'waiting_for_response') {
|
||||
// Store the request ID for sending response
|
||||
currentPendingRequestId = data.requestId;
|
||||
|
||||
// Enable input controls for response
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const sendButton = document.getElementById('sendButton');
|
||||
if (messageInput && sendButton) {
|
||||
messageInput.disabled = false;
|
||||
sendButton.disabled = false;
|
||||
@@ -1116,12 +1095,14 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
}
|
||||
|
||||
// Add waiting indicator if not present
|
||||
const existingWaiting = messagesContainer.querySelector('.waiting-indicator');
|
||||
if (!existingWaiting) {
|
||||
const waitingDiv = document.createElement('div');
|
||||
waitingDiv.className = 'waiting-indicator';
|
||||
waitingDiv.textContent = '⏳ Waiting for your response...';
|
||||
messagesContainer.appendChild(waitingDiv);
|
||||
if (messagesContainer) {
|
||||
const existingWaiting = messagesContainer.querySelector('.waiting-indicator');
|
||||
if (!existingWaiting) {
|
||||
const waitingDiv = document.createElement('div');
|
||||
waitingDiv.className = 'waiting-indicator';
|
||||
waitingDiv.textContent = '⏳ Waiting for your response...';
|
||||
messagesContainer.appendChild(waitingDiv);
|
||||
}
|
||||
}
|
||||
|
||||
// Play notification
|
||||
@@ -1132,6 +1113,24 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
|
||||
setTimeout(() => {
|
||||
document.body.classList.remove('flashing');
|
||||
}, 2000);
|
||||
|
||||
} else if (data.state === 'completed') {
|
||||
// Clear pending request
|
||||
currentPendingRequestId = null;
|
||||
|
||||
// Disable input controls
|
||||
if (messageInput && sendButton) {
|
||||
messageInput.disabled = true;
|
||||
sendButton.disabled = true;
|
||||
}
|
||||
|
||||
// Remove waiting indicator
|
||||
if (messagesContainer) {
|
||||
const waitingIndicator = messagesContainer.querySelector('.waiting-indicator');
|
||||
if (waitingIndicator) {
|
||||
waitingIndicator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user