Skip to content
235 changes: 212 additions & 23 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ import {
SetConfigValueArgsSchema,
ListProcessesArgsSchema,
EditBlockArgsSchema,
GetUsageStatsArgsSchema,
GiveFeedbackArgsSchema,
} from './tools/schemas.js';
import {getConfig, setConfigValue} from './tools/config.js';
import {getUsageStats} from './tools/usage.js';
import {giveFeedbackToDesktopCommander} from './tools/feedback.js';
import {trackToolCall} from './utils/trackTools.js';
import {usageTracker} from './utils/usageTracker.js';

import {VERSION} from './version.js';
import {capture, capture_call_tool} from "./utils/capture.js";
Expand Down Expand Up @@ -512,6 +517,79 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(KillProcessArgsSchema),
},
{
name: "get_usage_stats",
description: `
Get usage statistics for debugging and analysis.

Returns summary of tool usage, success/failure rates, and performance metrics.

${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(GetUsageStatsArgsSchema),
},
{
name: "give_feedback_to_desktop_commander",
description: `
Open feedback form in browser to provide feedback about Desktop Commander.

CRITICAL WORKFLOW:
1. NEVER call this tool immediately when user says "yes/sure/okay" to feedback
2. ALWAYS offer two options first:
- Fill form manually in browser (just open blank form)
- Get AI help to pre-fill form (ask questions first)
3. If user chooses AI help, ask the mapping questions below
4. If user chooses manual, call tool with no parameters

FORM STRUCTURE (5 pages) - Ask questions based on user preference:

PAGE 1: Let's get to know you
- "What's your role/job title?" → role
- "What department do you work in?" → department
- "What's your primary focus at work?" → what_doing
- "What is your company URL?" → company_url
- "How comfortable are you with writing code by yourself?" → coding_comfort
(Options: Very Comfortable, Somewhat Comfortable, Not Comfortable)
- "Where did you hear about Desktop Commander?" → heard_about
(Options: Friends, Colleagues, YouTube, TikTok, Reddit, Medium, Google/Search)

PAGE 2: Understanding Your Usage
- "What problem were you trying to solve when you started using Desktop Commander?" → problem_solving
- "What's your typical workflow with Desktop Commander?" → workflow
- "Can you describe a task or use case where Desktop Commander helped you significantly?" → task
- "Was there a moment or feature that made everything 'click' for you?" → aha_moment
- "What other AI tools or agents are you currently using or have used before?" → other_tools
- "How easy was it to get started with our product? (0-10 scale)" → ease_of_start

PAGE 3: Feedback & Improvements
- "Is there anything you found confusing or unexpected in working with Desktop Commander?" → confusing_parts
- "What would you improve or change to make Desktop Commander even more useful?" → how_better
- "Is there anything else you would like to share that we didn't ask?" → else_to_share

PAGE 4: Final Thoughts
- "How likely are you to recommend Desktop Commander to a colleague or friend? (0-10 scale)" → recommendation_score
- "Would you be open to participating in user study?" → user_study (Options: Yes, No)
- "Your email" → email

PAGE 5: Usage Statistics (AUTO-FILLED - no need to ask):
- tool_call_count: Approximate number of tool calls made
- days_using: How many days actively used Desktop Commander
- platform: Which platform user is on (Windows, Linux, Mac OS)
- client_used: Which client are you using (Claude, VS Code, Windsurf etc)

EXAMPLE INTERACTION:
User: "sure, I'll give feedback"
Claude: "Great! I can help in two ways:
1. 📝 Open blank form - you fill everything manually (5 pages)
2. 🤖 AI-assisted - I'll ask questions and pre-fill the form

Which would you prefer?"

Only call this tool AFTER the user chooses and you collect responses (if AI-assisted).
All parameters are optional - only include what the user provides.

${CMD_PREFIX_DESCRIPTION}`,
inputSchema: zodToJsonSchema(GiveFeedbackArgsSchema),
},
],
};
} catch (error) {
Expand All @@ -524,8 +602,9 @@ import * as handlers from './handlers/index.js';
import {ServerResult} from './types.js';

server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest): Promise<ServerResult> => {
const {name, arguments: args} = request.params;

try {
const {name, arguments: args} = request.params;
capture_call_tool('server_call_tool', {
name
});
Expand All @@ -534,94 +613,204 @@ server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest)
trackToolCall(name, args);

// Using a more structured approach with dedicated handlers
let result: ServerResult;

switch (name) {
// Config tools
case "get_config":
try {
return await getConfig();
result = await getConfig();
} catch (error) {
capture('server_request_error', {message: `Error in get_config handler: ${error}`});
return {
result = {
content: [{type: "text", text: `Error: Failed to get configuration`}],
isError: true,
};
}
break;
case "set_config_value":
try {
return await setConfigValue(args);
result = await setConfigValue(args);
} catch (error) {
capture('server_request_error', {message: `Error in set_config_value handler: ${error}`});
return {
result = {
content: [{type: "text", text: `Error: Failed to set configuration value`}],
isError: true,
};
}
break;

case "get_usage_stats":
try {
result = await getUsageStats();
} catch (error) {
capture('server_request_error', {message: `Error in get_usage_stats handler: ${error}`});
result = {
content: [{type: "text", text: `Error: Failed to get usage statistics`}],
isError: true,
};
}
break;

case "give_feedback_to_desktop_commander":
try {
result = await giveFeedbackToDesktopCommander(args);
} catch (error) {
capture('server_request_error', {message: `Error in give_feedback_to_desktop_commander handler: ${error}`});
result = {
content: [{type: "text", text: `Error: Failed to open feedback form`}],
isError: true,
};
}
break;

// Terminal tools
case "start_process":
return await handlers.handleStartProcess(args);
result = await handlers.handleStartProcess(args);
break;

case "read_process_output":
return await handlers.handleReadProcessOutput(args);
result = await handlers.handleReadProcessOutput(args);
break;

case "interact_with_process":
return await handlers.handleInteractWithProcess(args);
result = await handlers.handleInteractWithProcess(args);
break;

case "force_terminate":
return await handlers.handleForceTerminate(args);
result = await handlers.handleForceTerminate(args);
break;

case "list_sessions":
return await handlers.handleListSessions();
result = await handlers.handleListSessions();
break;

// Process tools
case "list_processes":
return await handlers.handleListProcesses();
result = await handlers.handleListProcesses();
break;

case "kill_process":
return await handlers.handleKillProcess(args);
result = await handlers.handleKillProcess(args);
break;

// Note: REPL functionality removed in favor of using general terminal commands

// Filesystem tools
case "read_file":
return await handlers.handleReadFile(args);
result = await handlers.handleReadFile(args);
break;

case "read_multiple_files":
return await handlers.handleReadMultipleFiles(args);
result = await handlers.handleReadMultipleFiles(args);
break;

case "write_file":
return await handlers.handleWriteFile(args);
result = await handlers.handleWriteFile(args);
break;

case "create_directory":
return await handlers.handleCreateDirectory(args);
result = await handlers.handleCreateDirectory(args);
break;

case "list_directory":
return await handlers.handleListDirectory(args);
result = await handlers.handleListDirectory(args);
break;

case "move_file":
return await handlers.handleMoveFile(args);
result = await handlers.handleMoveFile(args);
break;

case "search_files":
return await handlers.handleSearchFiles(args);
result = await handlers.handleSearchFiles(args);
break;

case "search_code":
return await handlers.handleSearchCode(args);
result = await handlers.handleSearchCode(args);
break;

case "get_file_info":
return await handlers.handleGetFileInfo(args);
result = await handlers.handleGetFileInfo(args);
break;

case "edit_block":
return await handlers.handleEditBlock(args);
result = await handlers.handleEditBlock(args);
break;

default:
capture('server_unknown_tool', {name});
return {
result = {
content: [{type: "text", text: `Error: Unknown tool: ${name}`}],
isError: true,
};
}

// Track success or failure based on result
if (result.isError) {
await usageTracker.trackFailure(name);
console.log(`[FEEDBACK DEBUG] Tool ${name} failed, not checking feedback`);
} else {
await usageTracker.trackSuccess(name);
console.log(`[FEEDBACK DEBUG] Tool ${name} succeeded, checking feedback...`);

// Check if should prompt for feedback (only on successful operations)
const shouldPrompt = true;//await usageTracker.shouldPromptForFeedback();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is hardcoded value, need to change to function

console.log(`[FEEDBACK DEBUG] Should prompt for feedback: ${shouldPrompt}`);

if (shouldPrompt) {
console.log(`[FEEDBACK DEBUG] Generating feedback message...`);
const feedbackMessage = await usageTracker.getFeedbackPromptMessage();
console.log(`[FEEDBACK DEBUG] Generated message: ${feedbackMessage.substring(0, 50)}...`);

// Capture feedback prompt injection event
const stats = await usageTracker.getStats();
await capture('feedback_prompt_injected', {
trigger_tool: name,
total_calls: stats.totalToolCalls,
successful_calls: stats.successfulCalls,
failed_calls: stats.failedCalls,
days_since_first_use: Math.floor((Date.now() - stats.firstUsed) / (1000 * 60 * 60 * 24)),
total_sessions: stats.totalSessions,
message_variant: feedbackMessage.includes('Simply type "feedback" or "yes"') ? 'direct_command_style' :
feedbackMessage.includes('Just type "feedback"') ? 'value_proposition_action' :
feedbackMessage.includes('Simply say "feedback"') ? 'personal_actionable' :
feedbackMessage.includes('Type "feedback" when ready') ? 'problem_focused_command' :
feedbackMessage.includes('Reply "yes" to share') ? 'community_easy_response' :
feedbackMessage.includes('Type "feedback" to start') ? 'authority_simple_command' :
'unknown_variant'
});

// Inject feedback instruction for the LLM
if (result.content && result.content.length > 0 && result.content[0].type === "text") {
console.log(`[FEEDBACK DEBUG] Using primary path - injecting LLM instruction`);
const currentContent = result.content[0].text || '';
result.content[0].text = `${currentContent}${feedbackMessage}`;
console.log(`[FEEDBACK DEBUG] Instruction injected, length: ${result.content[0].text.length}`);
} else {
console.log(`[FEEDBACK DEBUG] Using fallback path - adding instruction as new content`);
// Fallback if content structure is different
result.content = [
...(result.content || []),
{
type: "text",
text: feedbackMessage
}
];
}
Comment on lines +784 to +805

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add defensive checks for content manipulation

The feedback injection logic should handle edge cases more robustly to prevent potential runtime errors.

-if (result.content && result.content.length > 0 && result.content[0].type === "text") {
+if (result.content && Array.isArray(result.content) && result.content.length > 0 && 
+    result.content[0] && result.content[0].type === "text") {
     console.log(`[FEEDBACK DEBUG] Using primary path - injecting LLM instruction`);
     const currentContent = result.content[0].text || '';
     result.content[0].text = `${currentContent}${feedbackMessage}`;
     console.log(`[FEEDBACK DEBUG] Instruction injected, length: ${result.content[0].text.length}`);
 } else {
     console.log(`[FEEDBACK DEBUG] Using fallback path - adding instruction as new content`);
     // Fallback if content structure is different
+    if (!result.content) {
+        result.content = [];
+    }
     result.content = [
-        ...(result.content || []),
+        ...result.content,
         {
             type: "text",
             text: feedbackMessage
         }
     ];
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (result.content && result.content.length > 0 && result.content[0].type === "text") {
console.log(`[FEEDBACK DEBUG] Using primary path - injecting LLM instruction`);
const currentContent = result.content[0].text || '';
result.content[0].text = `${currentContent}${feedbackMessage}`;
console.log(`[FEEDBACK DEBUG] Instruction injected, length: ${result.content[0].text.length}`);
} else {
console.log(`[FEEDBACK DEBUG] Using fallback path - adding instruction as new content`);
// Fallback if content structure is different
result.content = [
...(result.content || []),
{
type: "text",
text: feedbackMessage
}
];
}
if (result.content && Array.isArray(result.content) && result.content.length > 0 &&
result.content[0] && result.content[0].type === "text") {
console.log(`[FEEDBACK DEBUG] Using primary path - injecting LLM instruction`);
const currentContent = result.content[0].text || '';
result.content[0].text = `${currentContent}${feedbackMessage}`;
console.log(`[FEEDBACK DEBUG] Instruction injected, length: ${result.content[0].text.length}`);
} else {
console.log(`[FEEDBACK DEBUG] Using fallback path - adding instruction as new content`);
// Fallback if content structure is different
if (!result.content) {
result.content = [];
}
result.content = [
...result.content,
{
type: "text",
text: feedbackMessage
}
];
}
🤖 Prompt for AI Agents
In src/server.ts around lines 784 to 799, the code manipulates result.content
without sufficient checks, risking runtime errors if result or result.content is
undefined or not an array. Add defensive checks to ensure result and
result.content exist and that result.content is an array before accessing or
modifying its elements. Also, verify that result.content[0] is an object with
the expected properties before accessing type or text fields. This will prevent
errors when the content structure is unexpected or missing.


// Mark that we've prompted (to prevent spam)
await usageTracker.markFeedbackPrompted();
console.log(`[FEEDBACK DEBUG] Marked as prompted, feedback should appear!`);
}
}
Comment on lines +698 to +810

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove debug code before merging

This section contains debug code that should be removed:

  1. Line 705: shouldPrompt is hardcoded to true
  2. Multiple console.log statements throughout

Apply this diff to fix the debug code:

-            await usageTracker.trackFailure(name);
-            console.log(`[FEEDBACK DEBUG] Tool ${name} failed, not checking feedback`);
+            await usageTracker.trackFailure(name);
         } else {
             await usageTracker.trackSuccess(name);
-            console.log(`[FEEDBACK DEBUG] Tool ${name} succeeded, checking feedback...`);
             
             // Check if should prompt for feedback (only on successful operations)
-            const shouldPrompt = true;//await usageTracker.shouldPromptForFeedback();
-            console.log(`[FEEDBACK DEBUG] Should prompt for feedback: ${shouldPrompt}`);
+            const shouldPrompt = await usageTracker.shouldPromptForFeedback();
             
             if (shouldPrompt) {
-                console.log(`[FEEDBACK DEBUG] Generating feedback message...`);
                 const feedbackMessage = await usageTracker.getFeedbackPromptMessage();
-                console.log(`[FEEDBACK DEBUG] Generated message: ${feedbackMessage.substring(0, 50)}...`);
                 
                 // Inject feedback instruction for the LLM
                 if (result.content && result.content.length > 0 && result.content[0].type === "text") {
-                    console.log(`[FEEDBACK DEBUG] Using primary path - injecting LLM instruction`);
                     const currentContent = result.content[0].text || '';
                     result.content[0].text = `${currentContent}${feedbackMessage}`;
-                    console.log(`[FEEDBACK DEBUG] Instruction injected, length: ${result.content[0].text.length}`);
                 } else {
-                    console.log(`[FEEDBACK DEBUG] Using fallback path - adding instruction as new content`);
                     // Fallback if content structure is different
                     result.content = [
                         ...(result.content || []),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await usageTracker.trackFailure(name);
console.log(`[FEEDBACK DEBUG] Tool ${name} failed, not checking feedback`);
} else {
await usageTracker.trackSuccess(name);
console.log(`[FEEDBACK DEBUG] Tool ${name} succeeded, checking feedback...`);
// Check if should prompt for feedback (only on successful operations)
const shouldPrompt = true;//await usageTracker.shouldPromptForFeedback();
console.log(`[FEEDBACK DEBUG] Should prompt for feedback: ${shouldPrompt}`);
if (shouldPrompt) {
console.log(`[FEEDBACK DEBUG] Generating feedback message...`);
const feedbackMessage = await usageTracker.getFeedbackPromptMessage();
console.log(`[FEEDBACK DEBUG] Generated message: ${feedbackMessage.substring(0, 50)}...`);
// Inject feedback instruction for the LLM
if (result.content && result.content.length > 0 && result.content[0].type === "text") {
console.log(`[FEEDBACK DEBUG] Using primary path - injecting LLM instruction`);
const currentContent = result.content[0].text || '';
result.content[0].text = `${currentContent}${feedbackMessage}`;
console.log(`[FEEDBACK DEBUG] Instruction injected, length: ${result.content[0].text.length}`);
} else {
console.log(`[FEEDBACK DEBUG] Using fallback path - adding instruction as new content`);
// Fallback if content structure is different
result.content = [
...(result.content || []),
{
type: "text",
text: feedbackMessage
}
];
}
// Mark that we've prompted (to prevent spam)
await usageTracker.markFeedbackPrompted();
console.log(`[FEEDBACK DEBUG] Marked as prompted, feedback should appear!`);
}
}
await usageTracker.trackFailure(name);
} else {
await usageTracker.trackSuccess(name);
// Check if should prompt for feedback (only on successful operations)
const shouldPrompt = await usageTracker.shouldPromptForFeedback();
if (shouldPrompt) {
const feedbackMessage = await usageTracker.getFeedbackPromptMessage();
// Inject feedback instruction for the LLM
if (result.content && result.content.length > 0 && result.content[0].type === "text") {
const currentContent = result.content[0].text || '';
result.content[0].text = `${currentContent}${feedbackMessage}`;
} else {
// Fallback if content structure is different
result.content = [
...(result.content || []),
{
type: "text",
text: feedbackMessage
}
];
}
// Mark that we've prompted (to prevent spam)
await usageTracker.markFeedbackPrompted();
console.log(`[FEEDBACK DEBUG] Marked as prompted, feedback should appear!`);
}
}
🤖 Prompt for AI Agents
In src/server.ts between lines 698 and 735, remove all console.log debug
statements and replace the hardcoded 'shouldPrompt = true' with the actual call
to 'await usageTracker.shouldPromptForFeedback()' to ensure proper feedback
prompting logic without debug output.


return result;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);

// Track the failure
await usageTracker.trackFailure(name);

capture('server_request_error', {
error: errorMessage
});
Expand Down
Loading