Files
SuperCharged-Claude-Code-Up…/public/claude-ide/semantic-validator.js
uroma 153e365c7b Fix command truncation and prepare for approval UI
- Fix regex pattern in semantic-validator.js that was truncating
  domain names (google.com -> google)
- Remove unnecessary 'info' type error reporting to bug tracker
- Only add bug tracker activity when errorId is valid
- Add terminal approval UI design document

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-21 13:14:43 +00:00

523 lines
19 KiB
JavaScript

/**
* Semantic Error Detection Layer
* Detects intent/behavior mismatches, conversational messages executed as commands,
* and confusing UX messages in the chat interface.
*/
(function() {
'use strict';
// ============================================================
// CONVERSATIONAL PATTERN DETECTION
// ============================================================
const CONVERSATIONAL_PATTERNS = [
// Question words (strong indicators)
/^(if|when|where|what|how|why|who|which|whose|can|could|would|should|will|do|does|did|is|are|was|were|am|have|has|had)\s/i,
// Pronouns (very strong conversational indicators)
/^(i|you|he|she|it|we|they)\s/i,
// Agreement/disagreement phrases
/^(yes|no|yeah|yep|nope|maybe|ok|okay|sure|alright|fine|go ahead)\s/i,
// Politeness markers
/^(please|thank|thanks|hey|hello|hi|greetings)\s/i,
// Ellipsis (conversational pause)
/^\.\.\./,
// Conversational transitions with commas
/^[a-z]+,\s/i,
// Thinking/believing/wanting verbs (indicate opinion, not command)
/\b(think|believe|want|need|like|prefer|mean|wish|hope|feel)\b/i,
// Context markers (explanatory, not imperative)
/\b(because|although|however|therefore|unless|until|since|while)\b/i,
// Second-person references to AI's actions
/\byou\b.*(ask|tell|want|need|like|prefer|said|mentioned)/i,
// First-person expressions
/\b(I'm|i am|i'd|i will|i can|i should|i would)\s/i
];
const APPROVAL_PATTERNS = [
// Direct approval
/^(yes|yeah|yep|sure|okay|ok|go ahead|please do|do it)\b/i,
// Direct rejection
/^(no|nope|don't|do not|stop|wait)\b/i,
// Polite approval
/^please\s+(go ahead|proceed|continue|do it)/i,
// Casual approval
/^(cool|great|awesome|perfect|good|fine)\b/i
];
const CONTEXTUAL_CONVERSATIONAL_PATTERNS = [
// Conditional statements about commands
/if (i|you|we) asked/i,
/when (i|you|we) said/i,
/as (i|you|we) mentioned/i,
/since (i|you|we) asked/i,
// References to previous conversation
/like (i|you) said/i,
/as (i|you) requested/i
];
// ============================================================
// COMMAND PATTERN DETECTION (Existing patterns + enhancements)
// ============================================================
const COMMAND_PATTERNS = [
// Shell built-ins
/^(cd|ls|pwd|echo|cat|grep|find|rm|cp|mv|mkdir|rmdir|touch|chmod|chown|ln|head|tail|less|more|sort|uniq|wc|tar|zip|unzip|gzip|gunzip|df|du|ps|top|kill|killall|nohup|bg|fg|jobs|export|unset|env|source|\.|alias|unalias|history|clear|reset|ping|ssh|scp|rsync|curl|wget|file|which|whereis|man|info|traceroute|nslookup|dig|host|netstat|ifconfig|ip)(\s|$)/,
// Package managers
/^(npm|yarn|pnpm|pip|pip3|conda|brew|apt|apt-get|yum|dnf|pacman)(\s|$)/,
// Node.js commands
/^(node|npx)(\s|$)/,
// Python commands
/^(python|python3|pip|pip3|python3-m)(\s|$)/,
// Git commands
/^git(\s|$)/,
// Docker commands
/^docker(\s|$)/,
// File operations with pipes or redirects (must look technical)
/^[a-z0-9_\-./]+\s*[\|>]/,
// Build tools
/^(make|cmake|cargo|go|rust|ruby|gem|java|javac|gradle|maven|mvn|gcc|g\+\+|clang)(\s|$)/,
// Absolute paths
/^\//,
// Scripts
/^(sh|bash|zsh|fish|powershell|pwsh)(\s|$)/
];
// ============================================================
// CONFUSING UX MESSAGE PATTERNS
// ============================================================
const CONFUSING_OUTPUT_PATTERNS = [
{
pattern: /exited with code (undefined|null)/i,
issue: 'Exit code is undefined/null',
suggestion: 'Show actual exit code or success/failure message'
},
{
pattern: /command (succeeded|failed) but output is (empty|undefined|null)/i,
issue: 'No output shown to user',
suggestion: 'Always show command output or "No output" message'
},
{
pattern: /error:.*undefined/i,
issue: 'Generic undefined error',
suggestion: 'Provide specific error message'
},
{
pattern: /null.*error/i,
issue: 'Null error reference',
suggestion: 'Provide meaningful error details'
},
{
pattern: /cannot (read|access).*undefined/i,
issue: 'Undefined property access',
suggestion: 'Validate properties before access'
}
];
// ============================================================
// CONVERSATION STATE TRACKING
// ============================================================
const conversationState = {
lastAssistantMessages: [],
lastUserMessage: null,
intentState: 'IDLE', // IDLE, AWAITING_APPROVAL, EXECUTING_COMMAND, CONVERSING
addAssistantMessage(message) {
this.lastAssistantMessages.push({
content: message,
timestamp: Date.now()
});
// Keep only last 5 messages
if (this.lastAssistantMessages.length > 5) {
this.lastAssistantMessages.shift();
}
this.updateIntentState();
},
addUserMessage(message) {
this.lastUserMessage = {
content: message,
timestamp: Date.now()
};
},
updateIntentState() {
const lastMsg = this.getLastAssistantMessage();
if (!lastMsg) {
this.intentState = 'IDLE';
return;
}
// Check if AI is asking for approval
const approvalRequest = /\byou want me to|shall i|should i|do you want|would you like|shall i proceed|should i run/i.test(lastMsg.content);
if (approvalRequest) {
this.intentState = 'AWAITING_APPROVAL';
return;
}
this.intentState = 'CONVERSING';
},
getLastAssistantMessage() {
return this.lastAssistantMessages[this.lastAssistantMessages.length - 1];
},
getLastNMessages(n) {
return this.lastAssistantMessages.slice(-n);
}
};
// ============================================================
// PUBLIC API
// ============================================================
/**
* Check if a message appears to be conversational (not a shell command)
* @param {string} message - User's message
* @returns {boolean} - True if conversational
*/
function isConversational(message) {
const trimmed = message.trim();
if (!trimmed) return false;
const lower = trimmed.toLowerCase();
// Check all conversational patterns
return CONVERSATIONAL_PATTERNS.some(pattern => pattern.test(lower));
}
/**
* Check if a message is an approval response (yes/no/sure/etc)
* @param {string} message - User's message
* @returns {boolean} - True if approval response
*/
function isApprovalResponse(message) {
const trimmed = message.trim();
if (!trimmed) return false;
const lower = trimmed.toLowerCase();
return APPROVAL_PATTERNS.some(pattern => pattern.test(lower));
}
/**
* Check if a message looks like a contextual conversational message
* (e.g., "if I asked you to ping google.com...")
* @param {string} message - User's message
* @returns {boolean} - True if contextual conversational
*/
function isContextualConversational(message) {
const trimmed = message.trim();
if (!trimmed) return false;
const lower = trimmed.toLowerCase();
return CONTEXTUAL_CONVERSATIONAL_PATTERNS.some(pattern => pattern.test(lower));
}
/**
* Enhanced shell command detection
* @param {string} message - User's message
* @returns {boolean} - True if it looks like a shell command
*/
function isShellCommand(message) {
const trimmed = message.trim();
if (!trimmed) return false;
const lower = trimmed.toLowerCase();
// FIRST: Check for command requests in conversational language
// Pattern: "run ping google.com", "execute ls -la", "can you run npm install?"
const commandRequestPatterns = [
/\b(run|execute|exec|do|can you run|please run|execute this|run this)\s+(.+?)\b/i,
/\b(start|launch|begin|kick off)\s+(.+?)\b/i,
/\b(go ahead and\s+)?(run|execute)\b/i
];
for (const pattern of commandRequestPatterns) {
const match = lower.match(pattern);
if (match && match[2]) {
// Extract the command and check if it's valid
const extractedCommand = match[2].trim();
if (extractedCommand && COMMAND_PATTERNS.some(p => p.test(extractedCommand))) {
console.log('[SemanticValidator] Detected command request:', extractedCommand);
return true;
}
}
}
// SECOND: Check if it's conversational (catch intent errors)
if (isConversational(lower)) {
return false;
}
// THIRD: Check for contextual conversational patterns
if (isContextualConversational(lower)) {
return false;
}
// FOURTH: Check if it matches command patterns
return COMMAND_PATTERNS.some(pattern => pattern.test(lower));
}
/**
* Extract actual command from conversational request
* @param {string} message - User's message
* @returns {string|null} - Extracted command or null
*/
function extractCommand(message) {
const trimmed = message.trim();
if (!trimmed) return null;
const lower = trimmed.toLowerCase();
// Pattern: "run ping google.com" → "ping google.com"
// Pattern: "execute ls -la" → "ls -la"
// FIXED: Capture everything after command verb, then trim only trailing sentence punctuation
const commandRequestPatterns = [
// Match "run/execute [command]" - capture everything to end, trim punctuation later
/\b(run|execute|exec|do|can you run|please run|execute this|run this)\s+(.+)/i,
/\b(start|launch|begin|kick off)\s+(.+)/i
];
for (const pattern of commandRequestPatterns) {
const match = lower.match(pattern);
if (match && match[2]) {
let extractedCommand = match[2].trim();
// Remove trailing sentence punctuation (. ! ?) ONLY if at end (not in middle like .com)
// Only remove if the punctuation is at the very end after trimming
extractedCommand = extractedCommand.replace(/[.!?]+$/g, '').trim();
// Validate that it's actually a command (check first word)
const firstWord = extractedCommand.split(/\s+/)[0];
if (firstWord && COMMAND_PATTERNS.some(p => p.test(firstWord))) {
// Preserve original case for the command
const originalMatch = trimmed.match(pattern);
if (originalMatch && originalMatch[2]) {
let originalCommand = originalMatch[2].trim();
// Remove trailing punctuation from original too
originalCommand = originalCommand.replace(/[.!?]+$/, '').trim();
console.log('[SemanticValidator] Extracted command:', originalCommand, 'from:', trimmed);
return originalCommand;
}
}
}
}
// If no command request pattern found, return original message
return trimmed;
}
/**
* Detect intent mismatch when user responds to approval request
* @param {string} userMessage - User's message
* @param {string} currentMode - Current chat mode
* @returns {object|null} - Error object if mismatch detected, null otherwise
*/
function detectApprovalIntentMismatch(userMessage, currentMode) {
// Only check in Terminal/WebContainer mode
if (currentMode !== 'webcontainer') {
return null;
}
// Check if we're awaiting approval
if (conversationState.intentState !== 'AWAITING_APPROVAL') {
return null;
}
// Check if user message is an approval response
if (isApprovalResponse(userMessage)) {
return {
type: 'intent_error',
subtype: 'approval_loop',
message: 'User responded with approval in Terminal mode, but system may execute it as a command',
details: {
lastAssistantMessage: conversationState.getLastAssistantMessage()?.content?.substring(0, 100) || '',
userMessage: userMessage,
mode: currentMode,
suggestedAction: 'Interpret approval responses contextually instead of executing as commands'
}
};
}
return null;
}
/**
* Detect conversational messages being sent to Terminal mode
* @param {string} message - User's message
* @param {string} mode - Current chat mode
* @returns {object|null} - Error object if detected, null otherwise
*/
function detectConversationalCommand(message, mode) {
// Only check in Terminal/WebContainer mode
if (mode !== 'webcontainer') {
return null;
}
if (isConversational(message) || isContextualConversational(message)) {
return {
type: 'intent_error',
subtype: 'conversational_as_command',
message: 'Conversational message sent to Terminal mode',
details: {
userMessage: message,
mode: mode,
suggestedMode: 'chat',
suggestedAction: 'Switch to Chat mode or rephrase as shell command'
}
};
}
return null;
}
/**
* Detect confusing UX messages in command output
* @param {string} output - Command output
* @returns {object|null} - Error object if detected, null otherwise
*/
function detectConfusingOutput(output) {
if (!output || typeof output !== 'string') {
return null;
}
for (const confusing of CONFUSING_OUTPUT_PATTERNS) {
if (confusing.pattern.test(output)) {
return {
type: 'ux_issue',
subtype: 'confusing_message',
message: 'Confusing error message displayed to user',
details: {
originalOutput: output.substring(0, 200),
issue: confusing.issue,
suggestedImprovement: confusing.suggestion
}
};
}
}
return null;
}
/**
* Validate user intent before executing command
* @param {string} message - User's message
* @param {string} mode - Current chat mode
* @returns {object} - Validation result {valid: boolean, error: object|null}
*/
function validateIntentBeforeExecution(message, mode) {
// Check for approval intent mismatch
const approvalMismatch = detectApprovalIntentMismatch(message, mode);
if (approvalMismatch) {
return {
valid: false,
error: approvalMismatch
};
}
// Check for conversational message in command mode
const conversationalCmd = detectConversationalCommand(message, mode);
if (conversationalCmd) {
return {
valid: false,
error: conversationalCmd
};
}
return {
valid: true,
error: null
};
}
/**
* Track assistant message for context
* @param {string} message - Assistant's message
*/
function trackAssistantMessage(message) {
conversationState.addAssistantMessage(message);
}
/**
* Track user message for context
* @param {string} message - User's message
*/
function trackUserMessage(message) {
conversationState.addUserMessage(message);
}
/**
* Get current conversation state
* @returns {string} - Current intent state
*/
function getIntentState() {
return conversationState.intentState;
}
/**
* Report a semantic error to the bug tracker
* @param {object} errorData - Error details
*/
function reportSemanticError(errorData) {
const semanticError = {
type: 'semantic',
subtype: errorData.subtype || errorData.type,
message: errorData.message,
details: errorData.details || {},
timestamp: new Date().toISOString(),
url: window.location.href,
context: {
chatMode: window.currentChatMode || 'unknown',
sessionId: window.attachedSessionId || window.chatSessionId,
intentState: conversationState.intentState,
recentMessages: conversationState.getLastNMessages(3).map(m => ({
content: m.content?.substring(0, 50),
timestamp: m.timestamp
}))
}
};
// Report to bug tracker
if (window.bugTracker) {
const errorId = window.bugTracker.addError(semanticError);
// Only add activity if error was actually added (not skipped)
if (errorId) {
window.bugTracker.addActivity(errorId, '🔍', `Semantic: ${errorData.subtype}`, 'warning');
}
}
// Report to server for auto-fixer
fetch('/claude/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(semanticError)
}).catch(err => console.error('[SemanticError] Failed to report:', err));
// Log for debugging
console.log('[SemanticError] Detected:', semanticError);
return semanticError;
}
// Export to global scope
window.semanticValidator = {
isConversational,
isApprovalResponse,
isContextualConversational,
isShellCommand,
extractCommand,
detectApprovalIntentMismatch,
detectConversationalCommand,
detectConfusingOutput,
validateIntentBeforeExecution,
trackAssistantMessage,
trackUserMessage,
getIntentState,
reportSemanticError
};
console.log('[SemanticValidator] Initialized with enhanced error detection');
})();