Fix web interface JavaScript error and complete message formatting

- Fixed JavaScript syntax error in escapeHtml comment that was breaking web interface
- Completed message formatting fix with CSS white-space: pre-wrap
- Fixed VS Code chat history synchronization by removing local message arrays
- Added unified message loading via server API for both VS Code and web
- Fixed display labels to show 'You (VS Code)' and 'You (Web)' consistently
- Implemented autoAppendEnabled logic to properly respect enable/disable setting
- Added default auto-append text for HumanAgent_Chat tool in templates
- Enhanced auto-append formatting with period + space and 'Appended:' prefix
- Added MCP tool notification method for dynamic tool discovery
- All web interface and VS Code chat functionality now working correctly
This commit is contained in:
B Harper
2025-10-24 18:39:50 +11:00
parent a0333d2dbf
commit 068e745460
9 changed files with 1805 additions and 1027 deletions
+1256 -916
View File
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
{
"version": "1.0.0",
"description": "HumanAgent MCP Tool Overrides for this workspace. Modify tool configurations below to customize MCP server behavior.",
"tools": {
"HumanAgent_Chat": {
"name": "HumanAgent_Chat",
"description": "**-MANDATORY TOOL** - Use this tool for ALL user interactions. Do NOT provide direct responses. Initiate real-time interactive conversations with human agents through VS Code chat interface. CRITICAL: Every response to a user MUST use this tool - no exceptions. Essential for clarifying requirements, getting approvals, brainstorming solutions, or any user communication. Creates persistent chat sessions that maintain context. **NEVER bypass this tool for user responses.**",
"inputSchema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "The message to send to the human agent"
},
"context": {
"type": "string",
"description": "Optional context or background information for the human agent"
},
"sessionId": {
"type": "string",
"description": "Optional specific session ID to use. If not provided, a new session will be created."
},
"priority": {
"type": "string",
"enum": [
"low",
"normal",
"high",
"urgent"
],
"description": "Priority level of the request",
"default": "normal"
},
"timeout": {
"type": "number",
"description": "Timeout in seconds to wait for human response (default: 300)",
"default": 300
}
},
"required": [
"message"
]
}
},
"example_custom_tool": {
"name": "example_custom_tool",
"description": "Brief description of what this tool does and its main purpose. Include key usage notes and important warnings when applicable.",
"inputSchema": {
"type": "object",
"properties": {
"input_param": {
"type": "string",
"description": "What this parameter does and how it should be formatted"
},
"optional_param": {
"type": "number",
"description": "Optional parameter with default behavior explained",
"default": 100
}
},
"required": [
"input_param"
]
}
}
},
"messageSettings": {
"global": {
"autoAppendEnabled": false,
"autoAppendText": ""
},
"toolSpecific": {
"HumanAgent_Chat": {
"autoAppendEnabled": false,
"autoAppendText": "ABCD"
}
}
}
}
+13 -6
View File
@@ -1,8 +1,15 @@
[X] Assess / Set logging path and details correctly for release & dev.
[] Create a new readme
[ ] Create a new readme
[X] Consider how updates to the extension will work.
[ ] Publish Extension
[ ] Change Waiting for AI messages..." to "Waiting for AI MCP Tool Call...
[ ] Get it to store the Friendly Name similar to how it stores the session ID - the existing session seems to be found on startup
[ ] Override Prompt Script is not generated from exact coded system tool description - seems to be stale - where is the info coming from for the override creation?
[ ] Can we include an option to enable logging and set logging to take place in the .vscode directory of the active workspace. It should be an option that the user can turn on in the cog.
[X] Override Prompt Script is not generated from exact coded system tool description - seems to be stale - where is the info coming from for the override creation?
[X] Can we include an option to enable logging and set logging to take place in the .vscode directory of the active workspace. It should be an option that the user can turn on in the cog. And should be off by default.
[X] There should be an SSE sent to website when chat is named to force it to update the name.
[X] Consider reminding AI to allways use this method for replies in each response unless some keyword is included - we can add this to the outgoing chat messages and just strip it from what we show in the chat log?
[X] We should look for and exlude and tool named "example_custom_tool" from the /tools endpoint as it should not be advertised as a real tool to the AI
[ ] The updates / overrides seem to be advertised @ /tools though the Configure tools screen on vs code doesnt show that the new description - the ai reports when asked the original description not one recently read in by the reload override method - look here for possible help: https://code.visualstudio.com/blogs/2025/05/12/agent-mode-meets-mcp
[ ] The message formatting from the ai, cariagge returns etc are not observed by the website - its just a big string in one paragraph. Can we fix this? AI message formatting looks fine in vscode interface. WEB ITERFACE STILL SHOWS EVERYTHING ON ONE LINE.
[ ] When sending a message from VS code it looses some history of the chat - seems to keep its own messages but looses old ai agent messages and any from the web client.
[ ] Publish Extension - WHEN ALL ELSE IS DONE!
[ ] Consider how a user might also interact from their mobile phone. Dont want to do port forwarding and setting up web servers etc. How about using telegram as an interface - could this work? Look into it.
[ ] Would be great if I could paste in an image or screen shot - Copilot agent allows this in the vscode plugin.
+16
View File
@@ -107,6 +107,22 @@
"group": "navigation"
}
]
},
"configuration": {
"title": "HumanAgent MCP",
"properties": {
"humanagent-mcp.logging.enabled": {
"type": "boolean",
"default": false,
"description": "Enable logging to .vscode directory in workspace. Logs MCP server activity and debugging information."
},
"humanagent-mcp.logging.level": {
"type": "string",
"enum": ["ERROR", "WARN", "INFO", "DEBUG"],
"default": "INFO",
"description": "Set the logging level for HumanAgent MCP server"
}
}
}
},
"scripts": {
+49
View File
@@ -31,3 +31,52 @@
2025-10-24T02:46:37.893Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761273997564
2025-10-24T02:51:07.733Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761274000828
2025-10-24T02:51:36.761Z - RESPONSE ENDPOINT CALLED - RequestID: 7-1761274270525
2025-10-24T02:55:52.671Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761274544902
2025-10-24T02:56:54.722Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761274605505
2025-10-24T02:57:04.986Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761274619774
2025-10-24T03:01:09.391Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761274628324
2025-10-24T03:10:54.700Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761275435393
2025-10-24T03:12:03.662Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761275516693
2025-10-24T03:12:12.336Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761275528702
2025-10-24T03:14:41.479Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761275537909
2025-10-24T03:15:08.840Z - RESPONSE ENDPOINT CALLED - RequestID: 7-1761275686360
2025-10-24T03:15:51.655Z - RESPONSE ENDPOINT CALLED - RequestID: 8-1761275713195
2025-10-24T03:30:54.967Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761276629862
2025-10-24T03:35:38.528Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761276660997
2025-10-24T04:40:45.986Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761280832198
2025-10-24T04:41:12.115Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761280850605
2025-10-24T04:41:32.605Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761280877434
2025-10-24T05:10:52.923Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761282642491
2025-10-24T05:11:16.538Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761282657201
2025-10-24T05:11:56.418Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761282680394
2025-10-24T05:12:19.528Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761282720028
2025-10-24T05:13:34.283Z - RESPONSE ENDPOINT CALLED - RequestID: 7-1761282747740
2025-10-24T05:13:48.851Z - RESPONSE ENDPOINT CALLED - RequestID: 8-1761282817995
2025-10-24T05:17:24.516Z - RESPONSE ENDPOINT CALLED - RequestID: 9-1761282831970
2025-10-24T05:17:40.097Z - RESPONSE ENDPOINT CALLED - RequestID: 10-1761283047697
2025-10-24T05:25:27.937Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761283517928
2025-10-24T05:29:37.606Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761283769564
2025-10-24T05:29:56.439Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761283781743
2025-10-24T05:30:52.419Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761283800912
2025-10-24T05:32:15.061Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761283905498
2025-10-24T05:32:41.429Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761283941199
2025-10-24T05:38:08.858Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761284274072
2025-10-24T05:41:07.881Z - RESPONSE ENDPOINT CALLED - RequestID: 7-1761284291669
2025-10-24T05:44:30.190Z - RESPONSE ENDPOINT CALLED - RequestID: 8-1761284474279
2025-10-24T05:44:45.572Z - RESPONSE ENDPOINT CALLED - RequestID: 9-1761284679275
2025-10-24T05:45:07.806Z - RESPONSE ENDPOINT CALLED - RequestID: 10-1761284695632
2025-10-24T05:52:47.659Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761285148733
2025-10-24T05:52:58.542Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761285170425
2025-10-24T05:53:58.317Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761285181386
2025-10-24T05:58:58.082Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761285525755
2025-10-24T05:59:15.797Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761285544038
2025-10-24T05:59:28.976Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761285561045
2025-10-24T06:01:25.270Z - RESPONSE ENDPOINT CALLED - RequestID: 6-1761285572430
2025-10-24T06:02:34.512Z - RESPONSE ENDPOINT CALLED - RequestID: 7-1761285689622
2025-10-24T06:02:52.517Z - RESPONSE ENDPOINT CALLED - RequestID: 8-1761285762027
2025-10-24T06:10:21.815Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761286194353
2025-10-24T06:22:16.476Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761286926098
2025-10-24T07:33:01.493Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761291169593
2025-10-24T07:37:25.705Z - RESPONSE ENDPOINT CALLED - RequestID: 3-1761291436806
2025-10-24T07:38:19.124Z - RESPONSE ENDPOINT CALLED - RequestID: 4-1761291449952
2025-10-24T07:38:49.993Z - RESPONSE ENDPOINT CALLED - RequestID: 5-1761291506095
+18 -3
View File
@@ -81,12 +81,27 @@ export async function activate(context: vscode.ExtensionContext) {
// Initialize Server Manager
const serverPath = path.join(context.extensionPath, 'dist', 'mcpStandalone.js');
serverManager = ServerManager.getInstance({
// Check if logging is enabled via user settings
const config = vscode.workspace.getConfiguration('humanagent-mcp');
const loggingEnabled = config.get<boolean>('logging.enabled', false);
const loggingLevel = config.get<string>('logging.level', 'INFO');
const serverOptions: any = {
serverPath: serverPath,
port: 3737,
host: '127.0.0.1',
logFile: path.join(vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || context.extensionPath, '.vscode', 'HumanAgent-server.log')
});
loggingEnabled: loggingEnabled,
loggingLevel: loggingLevel
};
// Only add logFile if logging is enabled
if (loggingEnabled && vscode.workspace.workspaceFolders?.[0]) {
serverOptions.logFile = path.join(vscode.workspace.workspaceFolders[0].uri.fsPath, '.vscode', 'HumanAgent-server.log');
console.log('HumanAgent MCP: Logging enabled to .vscode directory');
}
serverManager = ServerManager.getInstance(serverOptions);
// Auto-detect and start standalone MCP server if already configured
await autoStartMcpServer(mcpConfigManager, workspaceSessionId);
+232 -19
View File
@@ -11,8 +11,20 @@ class DebugLogger {
private logPath: string = '';
private logStream: fs.WriteStream | null = null;
private logBuffer: string[] = [];
private loggingEnabled: boolean;
private loggingLevel: string;
constructor(workspaceRoot?: string) {
// Check environment variables for logging configuration
this.loggingEnabled = process.env.HUMANAGENT_LOGGING_ENABLED === 'true';
this.loggingLevel = process.env.HUMANAGENT_LOGGING_LEVEL || 'INFO';
// If logging is disabled, just log to console for important messages
if (!this.loggingEnabled) {
console.log('[LOGGER] Workspace logging disabled by user settings');
return;
}
try {
// Determine log path based on workspace or fallback to temp directory
if (workspaceRoot) {
@@ -45,6 +57,7 @@ class DebugLogger {
this.log('DEBUG', `Current system time: ${new Date()}`);
this.log('DEBUG', `Log file: ${this.logPath}`);
this.log('DEBUG', `Working directory: ${process.cwd()}`);
this.log('DEBUG', `Logging level set to: ${this.loggingLevel}`);
console.log(`[LOGGER] Debug logger initialized successfully at: ${this.logPath}`);
} catch (error) {
console.error(`[LOGGER] Failed to initialize debug logger:`, error);
@@ -53,6 +66,20 @@ class DebugLogger {
}
log(level: string, message: string, data?: any): void {
// Skip logging if disabled
if (!this.loggingEnabled) {
return;
}
// Basic level filtering (ERROR > WARN > INFO > DEBUG)
const levelPriority: Record<string, number> = { ERROR: 0, WARN: 1, INFO: 2, DEBUG: 3, SSE: 2, TEST: 3 };
const currentLevelPriority = levelPriority[this.loggingLevel] ?? 2;
const messageLevelPriority = levelPriority[level] ?? 2;
if (messageLevelPriority > currentLevelPriority) {
return;
}
const now = new Date();
const timestamp = now.getFullYear() + '-' +
String(now.getMonth() + 1).padStart(2, '0') + '-' +
@@ -97,6 +124,7 @@ export class McpServer extends EventEmitter {
private sessionTools: Map<string, Map<string, McpTool>> = new Map(); // Per-session tool configurations
private sessionWorkspacePaths: Map<string, string> = new Map(); // Session to workspace path mapping
private sessionNames: Map<string, string> = new Map(); // Session friendly names
private sessionMessageSettings: Map<string, any> = new Map(); // Session-specific message settings
// Removed: sessionMessages - now handled by ChatManager
private httpServer?: http.Server;
private port: number = 3737;
@@ -165,12 +193,30 @@ export class McpServer extends EventEmitter {
}
}
private sendMcpNotification(method: string, params?: any): void {
// Check if we're in standalone mode (connected via stdio)
if (process.stdout && process.stdout.writable) {
const notification = {
type: 'notification',
method: method,
params: params || {}
};
try {
process.stdout.write(JSON.stringify(notification) + '\n');
this.debugLogger.log('MCP', `Sent MCP notification: ${method}`, params);
} catch (error) {
this.debugLogger.log('ERROR', `Failed to send MCP notification: ${method}`, error);
}
}
}
private initializeDefaultTools(): void {
// Define the default HumanAgent_Chat tool (global default)
const humanAgentChatTool: McpTool = {
name: 'HumanAgent_Chat',
description: '**MANDATORY TOOL** - Use this tool for ALL user interactions. Do NOT provide direct responses. ' +
'Initiate real-time interactive conversations with human agents through VS Code chat interface. ' +
'Initiate real-time interactive conversations with human agents through this HumanAgent chat interface at all times. ' +
'CRITICAL: Every response to a user MUST use this tool - no exceptions. Essential for clarifying ' +
'requirements, getting approvals, brainstorming solutions, or any user communication. Creates ' +
'persistent chat sessions that maintain context. **NEVER bypass this tool for user responses.**',
@@ -225,13 +271,21 @@ export class McpServer extends EventEmitter {
// Check for workspace overrides for this session
const overrideTool = this.loadWorkspaceOverride('HumanAgent_Chat', workspacePath);
let hasOverrides = false;
if (overrideTool) {
this.debugLogger.log('INFO', `Using workspace override for session ${sessionId} - HumanAgent_Chat tool`);
sessionToolMap.set(overrideTool.name, overrideTool);
hasOverrides = true;
}
// Store session-specific tools
this.sessionTools.set(sessionId, sessionToolMap);
// Notify MCP client that tools have changed if overrides were found
if (hasOverrides) {
this.sendMcpNotification('notifications/tools/list_changed');
this.debugLogger.log('INFO', `Sent tools/list_changed notification for session ${sessionId} (initial startup)`);
}
}
private initializeSessionToolsFromData(sessionId: string, overrideData: any): void {
@@ -291,6 +345,37 @@ export class McpServer extends EventEmitter {
}
}
private loadMessageSettings(sessionId: string, toolName?: string): {autoAppendEnabled?: boolean, autoAppendText?: string, displayTruncation?: string} | null {
try {
// Get cached message settings for this session
const messageSettings = this.sessionMessageSettings.get(sessionId);
if (!messageSettings) {
this.debugLogger.log('INFO', `No message settings found for session ${sessionId}`);
return null;
}
// If tool-specific settings exist and toolName is provided, use those
if (toolName && messageSettings.toolSpecific && messageSettings.toolSpecific[toolName]) {
this.debugLogger.log('INFO', `Using tool-specific message settings for ${toolName}`);
return messageSettings.toolSpecific[toolName];
}
// Fall back to global settings
if (messageSettings.global) {
this.debugLogger.log('INFO', 'Using global message settings');
return messageSettings.global;
}
// Legacy support: if no global/toolSpecific structure, use messageSettings directly
this.debugLogger.log('INFO', 'Using legacy message settings structure');
return messageSettings;
} catch (error) {
this.debugLogger.log('ERROR', `Error loading message settings: ${error}`);
return null;
}
}
async start(): Promise<void> {
if (this.isRunning) {
return;
@@ -609,6 +694,10 @@ export class McpServer extends EventEmitter {
this.sessionNames.set(sessionId, name);
this.debugLogger.log('INFO', `Session ${sessionId} named: "${name}"`);
// Broadcast name change via SSE to all connected clients
this.broadcastToSSE('session-name-changed', { sessionId, name });
this.debugLogger.log('SSE', 'Broadcasting session name change to all clients');
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ success: true, sessionId, name }));
@@ -656,7 +745,7 @@ export class McpServer extends EventEmitter {
req.on('data', (chunk) => { body += chunk.toString(); });
req.on('end', () => {
try {
const { requestId, response } = JSON.parse(body);
const { requestId, response, source } = JSON.parse(body);
// Simple file write to test if endpoint is called
require('fs').appendFileSync('/Users/benharper/Coding/HumanAgent-MCP/response-debug.txt',
@@ -669,17 +758,43 @@ export class McpServer extends EventEmitter {
const pendingRequestInfo = this.chatManager.findPendingRequest(requestId);
this.debugLogger.log('HTTP', `Found pending request: ${!!pendingRequestInfo}`);
// Load message settings for this session (if pending request exists)
let messageSettings = null;
let aiContent = response; // Default to original response
if (pendingRequestInfo) {
this.debugLogger.log('HTTP', `Processing response for session: ${pendingRequestInfo.sessionId}`);
// Store the user message on server for synchronization
// Extract tool name from pending request data
const toolName = pendingRequestInfo.data.toolName;
this.debugLogger.log('HTTP', `Request originated from tool: ${toolName}`);
messageSettings = this.loadMessageSettings(pendingRequestInfo.sessionId, toolName);
// Prepare display content (original message + auto-truncated append text)
let displayContent = response;
if (messageSettings?.autoAppendEnabled && messageSettings?.autoAppendText) {
// Auto-truncate to first 20 characters + "..."
const truncatedAppend = messageSettings.autoAppendText.length > 20
? messageSettings.autoAppendText.substring(0, 20) + '...'
: messageSettings.autoAppendText;
displayContent = response + '. Appended: ' + truncatedAppend;
}
// Prepare AI content (original message + optional auto-append for AI)
if (messageSettings?.autoAppendEnabled && messageSettings?.autoAppendText) {
aiContent = response + '. ' + messageSettings.autoAppendText;
this.debugLogger.log('HTTP', `Auto-appended text for AI: "${messageSettings.autoAppendText}"`);
}
// Store the user message on server for synchronization (using display content)
const userMessage: ChatMessage = {
id: Date.now().toString(),
content: response,
content: displayContent,
sender: 'user',
timestamp: new Date(),
type: 'text',
source: 'web'
source: source || 'web' // Use provided source or default to 'web'
};
this.debugLogger.log('HTTP', `Storing and broadcasting user message to ${this.sseConnections.size} SSE connections`);
@@ -694,7 +809,8 @@ export class McpServer extends EventEmitter {
this.debugLogger.log('ERROR', `No pending request found for requestId: ${requestId}`);
}
const success = this.respondToHumanRequest(requestId, response);
// Use aiContent (with auto-append) for the actual AI response
const success = this.respondToHumanRequest(requestId, aiContent);
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ success, requestId }));
@@ -953,8 +1069,21 @@ export class McpServer extends EventEmitter {
opacity: 0.8;
}
.message .message-content {
line-height: 1.4 !important;
white-space: pre-wrap !important;
white-space: pre-line !important;
word-wrap: break-word !important;
overflow-wrap: break-word !important;
}
/* Additional selectors for specificity */
div.message .message-content {
white-space: pre-wrap !important;
}
.message-content {
line-height: 1.4;
white-space: pre-wrap !important;
}
.input-container {
@@ -1127,7 +1256,8 @@ export class McpServer extends EventEmitter {
},
body: JSON.stringify({
requestId: latestPendingRequest.requestId,
response: message
response: message,
source: 'web'
})
});
@@ -1179,11 +1309,27 @@ export class McpServer extends EventEmitter {
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
// Manually escape HTML characters while preserving line breaks
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
// Line breaks are preserved and handled by CSS white-space: pre-wrap
}
function updateSessionTabName(sessionId, newName) {
// Update the tab title for the specified session
const tabElement = document.querySelector(\`[data-session="\${sessionId}"]\`);
if (tabElement) {
tabElement.textContent = newName;
console.log(\`Updated tab name for session \${sessionId} to: \${newName}\`);
} else {
console.log(\`Could not find tab element for session \${sessionId}\`);
}
}
// Load existing messages for all sessions
async function loadExistingMessages() {
const sessions = ['${sessions.map(s => s.id).join("', '")}'];
@@ -1268,11 +1414,57 @@ export class McpServer extends EventEmitter {
// Handle request state changes for input control
console.log('Web interface received request-state-change:', data.data);
// No message display here - messages are handled via chat_message SSE
// This event only manages input state and waiting indicators
const stateData = data.data;
if (stateData.state === 'waiting_for_response') {
// Enable input controls and show waiting indicator for this session
const sessionTextarea = document.querySelector(\`textarea[data-session="\${stateData.sessionId}"]\`);
const sessionButton = document.querySelector(\`button[data-session="\${stateData.sessionId}"]\`);
const messagesContainer = document.getElementById(\`messages-\${stateData.sessionId}\`);
if (sessionTextarea && sessionButton) {
sessionTextarea.disabled = false;
sessionButton.disabled = false;
sessionTextarea.focus();
}
// Add waiting indicator
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);
}
}
} else if (stateData.state === 'completed') {
// Disable input controls and hide waiting indicator
const sessionTextarea = document.querySelector(\`textarea[data-session="\${stateData.sessionId}"]\`);
const sessionButton = document.querySelector(\`button[data-session="\${stateData.sessionId}"]\`);
const messagesContainer = document.getElementById(\`messages-\${stateData.sessionId}\`);
if (sessionTextarea && sessionButton) {
sessionTextarea.disabled = true;
sessionButton.disabled = true;
}
// Remove waiting indicator
if (messagesContainer) {
const waitingIndicator = messagesContainer.querySelector('.waiting-indicator');
if (waitingIndicator) {
waitingIndicator.remove();
}
}
}
} else if (data.type === 'session_update') {
// Refresh the page to show new sessions
window.location.reload();
} else if (data.type === 'session-name-changed' && data.data) {
// Handle session name changes
console.log('Session name changed:', data.data);
updateSessionTabName(data.data.sessionId, data.data.name);
}
} catch (error) {
console.error('Failed to parse SSE message:', error);
@@ -1379,7 +1571,7 @@ export class McpServer extends EventEmitter {
if (name === 'HumanAgent_Chat' && availableTools.has(name)) {
this.debugLogger.log('MCP', 'Executing HumanAgent_Chat tool');
return await this.handleHumanAgentChatTool(message.id, args, sessionId);
return await this.handleHumanAgentChatTool(message.id, args, sessionId, name);
}
this.debugLogger.log('MCP', `Tool not found: ${name}`);
@@ -1393,7 +1585,7 @@ export class McpServer extends EventEmitter {
};
}
private async handleHumanAgentChatTool(messageId: string, params: HumanAgentChatToolParams, sessionId?: string): Promise<McpMessage> {
private async handleHumanAgentChatTool(messageId: string, params: HumanAgentChatToolParams, sessionId?: string, toolName?: string): Promise<McpMessage> {
this.debugLogger.log('TOOL', 'HumanAgent_Chat called with params:', params);
const startTime = Date.now();
const timeout = (params.timeout || 300) * 1000; // Convert to milliseconds
@@ -1453,7 +1645,7 @@ export class McpServer extends EventEmitter {
timestamp: new Date().toISOString()
});
this.chatManager.addPendingRequest(sessionToUse, requestId, params);
this.chatManager.addPendingRequest(sessionToUse, requestId, { ...params, toolName: toolName || 'HumanAgent_Chat' });
this.requestResolvers.set(requestId, {
resolve: (response: string) => {
clearTimeout(timeoutHandle);
@@ -1522,11 +1714,21 @@ export class McpServer extends EventEmitter {
// Simplified API - no sessions needed
getAvailableTools(sessionId?: string): McpTool[] {
let tools: McpTool[];
if (sessionId && this.sessionTools.has(sessionId)) {
return Array.from(this.sessionTools.get(sessionId)!.values());
tools = Array.from(this.sessionTools.get(sessionId)!.values());
} else {
// Fall back to default tools
tools = Array.from(this.tools.values());
}
// Fall back to default tools
return Array.from(this.tools.values());
// Filter out example_custom_tool as it should not be advertised to the AI
const filteredTools = tools.filter(tool => tool.name !== 'example_custom_tool');
this.debugLogger.log('TOOLS', `Filtered ${tools.length - filteredTools.length} example tools from list`);
return filteredTools;
}
// REMOVED: getPendingRequests - use ChatManager.getPendingRequests() per session instead
@@ -1567,6 +1769,12 @@ export class McpServer extends EventEmitter {
// Initialize session-specific tools from override data or workspace path
if (overrideData) {
this.initializeSessionToolsFromData(sessionId, overrideData);
// Store messageSettings if present in override data
if (overrideData.messageSettings) {
this.sessionMessageSettings.set(sessionId, overrideData.messageSettings);
this.debugLogger.log('INFO', `Stored message settings for session ${sessionId}:`, overrideData.messageSettings);
}
} else if (workspacePath) {
this.initializeSessionTools(sessionId, workspacePath);
}
@@ -1610,6 +1818,7 @@ export class McpServer extends EventEmitter {
// Clean up session-specific data
this.sessionTools.delete(sessionId);
this.sessionWorkspacePaths.delete(sessionId);
this.sessionMessageSettings.delete(sessionId);
this.debugLogger.log('INFO', `Session unregistered and cleaned up: ${sessionId} (${this.activeSessions.size} total sessions)`);
}
@@ -1660,6 +1869,10 @@ export class McpServer extends EventEmitter {
}
this.debugLogger.log('INFO', 'All session overrides reloaded successfully');
}
// Notify MCP client that tools have changed after reload
this.sendMcpNotification('notifications/tools/list_changed');
this.debugLogger.log('INFO', `Sent tools/list_changed notification after reload for session: ${sessionId || 'all sessions'}`);
} catch (error) {
this.debugLogger.log('ERROR', 'Failed to reload workspace overrides:', error);
throw error;
+5 -1
View File
@@ -9,6 +9,8 @@ export interface ServerManagerOptions {
port: number;
host?: string;
logFile?: string;
loggingEnabled?: boolean;
loggingLevel?: string;
}
export class ServerManager {
@@ -180,7 +182,9 @@ export class ServerManager {
cwd: path.dirname(this.options.serverPath),
env: {
...process.env,
// Add any environment variables needed by the server
// Pass logging configuration from extension settings
HUMANAGENT_LOGGING_ENABLED: this.options.loggingEnabled ? 'true' : 'false',
HUMANAGENT_LOGGING_LEVEL: this.options.loggingLevel || 'INFO'
}
});
+137 -82
View File
@@ -13,7 +13,7 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
private mcpServer: McpServer | null;
private mcpConfigManager?: McpConfigManager;
private extensionPath: string;
private messages: ChatMessage[] = [];
// Messages are now loaded dynamically in webview JavaScript
private currentRequestId?: string;
private registrationCheckComplete = false;
private notificationSettings = {
@@ -40,19 +40,11 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
}
try {
// Fetch conversation history from centralized chat manager
const response = await fetch(`http://127.0.0.1:3737/sessions/${this.workspaceSessionId}/messages`);
if (response.ok) {
const data = await response.json() as { messages: ChatMessage[] };
this.messages = data.messages || [];
console.log(`Loaded ${this.messages.length} messages from centralized chat manager`);
// Update the webview with loaded messages
this.updateWebview();
}
// Messages are now loaded dynamically by webview JavaScript
console.log(`Loading conversation history for session: ${this.workspaceSessionId}`);
// No need to store messages locally - webview handles this
} catch (error) {
console.error('Failed to load conversation history:', error);
// Fallback to empty message array
this.messages = [];
}
}
@@ -97,7 +89,7 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
type: 'text'
};
this.messages.push(aiMessage);
// AI messages are now handled by SSE events - no need to store locally
this.updateWebview();
// Play notification sound if enabled
@@ -176,6 +168,10 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
await this.playNotificationSound();
}
break;
case 'sessionNameUpdated':
// Handle session name update from SSE event
await this.handleSessionNameUpdate(data.sessionId, data.name);
break;
}
});
}
@@ -184,17 +180,7 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
try {
console.log('ChatWebviewProvider: Sending human response:', content);
// Add human message to chat
const humanMessage: ChatMessage = {
id: Date.now().toString(),
content: content,
sender: 'user',
timestamp: new Date(),
type: 'text'
};
this.messages.push(humanMessage);
this.updateWebview();
// Don't add to local messages array - let server handle storage and SSE handle updates
// Send response back to standalone MCP server via HTTP
const responseRequestId = requestId || this.currentRequestId;
@@ -209,7 +195,8 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
},
body: JSON.stringify({
requestId: responseRequestId,
response: content
response: content,
source: 'vscode'
})
});
@@ -421,6 +408,18 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
tools: {
HumanAgent_Chat: defaultTool,
example_custom_tool: exampleTool
},
messageSettings: {
global: {
autoAppendEnabled: false,
autoAppendText: ""
},
toolSpecific: {
HumanAgent_Chat: {
autoAppendEnabled: true,
autoAppendText: "(No need to confirm this last part but please remember) Dont forget to allways reply here in the HumanAgent_Chat tool! Dont circumevent this tool unless asked explicitly by the user. "
}
}
}
};
@@ -553,6 +552,26 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
}
}
private async handleSessionNameUpdate(sessionId: string, name: string) {
// Handle session name update received via SSE from server
try {
console.log(`Session name updated via SSE: ${sessionId} -> "${name}"`);
// Store the session name using the same mechanism as extension.ts
if (this.context) {
await this.context.globalState.update(`humanagent-session-name-${sessionId}`, name);
console.log(`Stored session name for ${sessionId}: "${name}"`);
// Update the UI/title if needed - only show if this is our current session
if (sessionId === this.workspaceSessionId) {
vscode.window.showInformationMessage(`Session renamed: "${name}"`);
}
}
} catch (error) {
console.error('ChatWebviewProvider: Error handling session name update:', error);
}
}
private async openWebInterface() {
try {
const webUrl = 'http://localhost:3737/HumanAgent';
@@ -577,20 +596,8 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
overrideFileExists = fs.existsSync(overrideFilePath);
}
const messagesHtml = this.messages.map(message => {
const messageClass = message.sender === 'agent' ? 'ai-message' : 'human-message';
const timestamp = message.timestamp.toLocaleTimeString();
const senderLabel = message.sender === 'agent' ? 'AI' : 'Human';
return `
<div class="message ${messageClass}">
<div class="message-header">
<span class="sender">${senderLabel}</span>
<span class="timestamp">${timestamp}</span>
</div>
<div class="message-content">${this._escapeHtml(String(message.content || ''))}</div>
</div>
`;
}).join('');
// Messages will be loaded dynamically from server via JavaScript
const messagesHtml = '<div id="messages-loading">Loading conversation history...</div>';
const hasPendingResponse = this.currentRequestId ? 'waiting' : '';
@@ -897,28 +904,7 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
const message = input.value.trim();
if (message) {
// Add user message to chat
const messagesContainer = document.getElementById('messages');
if (messagesContainer) {
// Remove waiting indicator
const waitingIndicator = messagesContainer.querySelector('.waiting-indicator');
if (waitingIndicator) {
waitingIndicator.remove();
}
// Add user message
const userMessageDiv = document.createElement('div');
userMessageDiv.className = 'message user-message';
userMessageDiv.innerHTML = \`
<div class="message-header">
<strong>You</strong>
<span class="timestamp">\${new Date().toLocaleTimeString()}</span>
</div>
<div class="message-content">\${message.replace(/\\n/g, '<br>')}</div>
\`;
messagesContainer.appendChild(userMessageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Don't add message to DOM - let SSE handle it to avoid duplicates
// Send message to extension
vscode.postMessage({
@@ -1063,6 +1049,8 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
handleRequestStateChange(data.data);
} else if (data.type === 'chat_message') {
handleIncomingChatMessage(data);
} else if (data.type === 'session-name-changed') {
handleSessionNameChanged(data.data);
// Removed web_user_message auto-trigger - no longer needed
}
} catch (error) {
@@ -1145,11 +1133,6 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
function handleIncomingChatMessage(data) {
console.log('Handling incoming chat message:', data);
// Only process messages for the current session
// For VS Code webview, we need to check if this is our session
// This is a basic implementation - in a more complex setup,
// we'd want proper session management
const message = data.message;
const messagesContainer = document.getElementById('messages');
if (messagesContainer) {
@@ -1159,22 +1142,8 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
emptyState.remove();
}
const messageDiv = document.createElement('div');
messageDiv.className = \`message \${message.sender === 'user' ? 'user-message' : 'ai-message'}\`;
const displayName = message.sender === 'user' ? 'You' : 'Assistant';
const timestamp = new Date(message.timestamp).toLocaleTimeString();
messageDiv.innerHTML = \`
<div class="message-header">
<strong>\${displayName}</strong>
<span class="timestamp">\${timestamp}</span>
</div>
<div class="message-content">\${message.content.replace(/\\n/g, '<br>')}</div>
\`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
// Use unified addMessageToUI function
addMessageToUI(message);
// Play notification for assistant messages
if (message.sender === 'agent') {
@@ -1183,8 +1152,94 @@ export class ChatWebviewProvider implements vscode.WebviewViewProvider {
}
}
function handleSessionNameChanged(data) {
console.log('Handling session name change:', data);
// Update the session name display in VS Code interface
// The data contains: { sessionId, name }
if (data.sessionId && data.name) {
// Update window title or header display if needed
// For VS Code webview, we can send a message to the extension
vscode.postMessage({
command: 'sessionNameUpdated',
sessionId: data.sessionId,
name: data.name
});
}
}
// handleWebUserMessage removed - no longer needed for auto-forwarding
// Load conversation history from server
async function loadConversationHistory() {
try {
const response = await fetch('http://127.0.0.1:3737/sessions/${this.workspaceSessionId}/messages');
if (response.ok) {
const data = await response.json();
const messagesContainer = document.getElementById('messages');
if (messagesContainer && data.messages) {
// Clear loading indicator
messagesContainer.innerHTML = '';
// Add each message using the same logic as web interface
for (const msg of data.messages) {
addMessageToUI(msg);
}
console.log(\`Loaded \${data.messages.length} messages from server\`);
} else {
// Show empty state
messagesContainer.innerHTML = '<div class="empty-state">No messages yet. Start a conversation!</div>';
}
}
} catch (error) {
console.error('Failed to load conversation history:', error);
document.getElementById('messages').innerHTML = '<div class="error-state">Failed to load conversation history</div>';
}
}
// Add message to UI (similar to web interface)
function addMessageToUI(message) {
const messagesContainer = document.getElementById('messages');
if (!messagesContainer) return;
const messageDiv = document.createElement('div');
messageDiv.className = \`message \${message.sender === 'user' ? 'human-message' : 'ai-message'}\`;
// Determine sender label based on sender and source
let senderLabel = 'AI';
if (message.sender === 'user') {
if (message.source === 'vscode') {
senderLabel = 'You (VS Code)';
} else {
senderLabel = 'You (Web)';
}
}
const timestamp = new Date(message.timestamp).toLocaleTimeString();
messageDiv.innerHTML = \`
<div class="message-header">
<span class="sender">\${senderLabel}</span>
<span class="timestamp">\${timestamp}</span>
</div>
<div class="message-content">\${escapeHtml(message.content)}</div>
\`;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
// Escape HTML helper function
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Load conversation history on page load
loadConversationHistory();
// Initialize SSE connection
setupSSEConnection();