ft-scout 9.0.7 → 9.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/.firebase/hosting.d2Vi.cache +8 -8
  2. package/AUTONOMOUS_FEATURES.md +1 -1
  3. package/README.md +113 -16
  4. package/bin/src/commands/agent.d.ts.map +1 -1
  5. package/bin/src/commands/agent.js +28 -15
  6. package/bin/src/commands/agent.js.map +1 -1
  7. package/bin/src/commands/audit.js +1 -1
  8. package/bin/src/commands/audit.js.map +1 -1
  9. package/bin/src/commands/dashboard.js +1 -1
  10. package/bin/src/commands/dashboard.js.map +1 -1
  11. package/bin/src/commands/mcp.d.ts.map +1 -0
  12. package/bin/src/commands/mcp.js +393 -0
  13. package/bin/src/commands/mcp.js.map +1 -0
  14. package/bin/src/commands/owners.js +1 -1
  15. package/bin/src/commands/owners.js.map +1 -1
  16. package/bin/src/discovery/messages.js +2 -2
  17. package/bin/src/discovery/messages.js.map +1 -1
  18. package/bin/src/discovery/signals.js +3 -3
  19. package/bin/src/discovery/signals.js.map +1 -1
  20. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  21. package/bin/src/engine/agentEngine.js +111 -32
  22. package/bin/src/engine/agentEngine.js.map +1 -1
  23. package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
  24. package/bin/src/engine/runtime/agentRuntime.js +40 -3
  25. package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
  26. package/bin/src/engine/toolRegistry.d.ts.map +1 -1
  27. package/bin/src/engine/toolRegistry.js +13 -0
  28. package/bin/src/engine/toolRegistry.js.map +1 -1
  29. package/bin/src/engine/verifier.js +2 -2
  30. package/bin/src/engine/verifier.js.map +1 -1
  31. package/bin/src/index.js +85 -7
  32. package/bin/src/index.js.map +1 -1
  33. package/bin/src/mcp/client.d.ts.map +1 -0
  34. package/bin/src/mcp/client.js +127 -0
  35. package/bin/src/mcp/client.js.map +1 -0
  36. package/bin/src/mcp/discovery.d.ts.map +1 -0
  37. package/bin/src/mcp/discovery.js +91 -0
  38. package/bin/src/mcp/discovery.js.map +1 -0
  39. package/bin/src/mcp/health.d.ts.map +1 -0
  40. package/bin/src/mcp/health.js +44 -0
  41. package/bin/src/mcp/health.js.map +1 -0
  42. package/bin/src/mcp/index.d.ts.map +1 -0
  43. package/bin/src/mcp/index.js +15 -0
  44. package/bin/src/mcp/index.js.map +1 -0
  45. package/bin/src/mcp/manager.d.ts.map +1 -0
  46. package/bin/src/mcp/manager.js +648 -0
  47. package/bin/src/mcp/manager.js.map +1 -0
  48. package/bin/src/mcp/permissions.d.ts.map +1 -0
  49. package/bin/src/mcp/permissions.js +50 -0
  50. package/bin/src/mcp/permissions.js.map +1 -0
  51. package/bin/src/mcp/persistence.d.ts.map +1 -0
  52. package/bin/src/mcp/persistence.js +171 -0
  53. package/bin/src/mcp/persistence.js.map +1 -0
  54. package/bin/src/mcp/recovery.d.ts.map +1 -0
  55. package/bin/src/mcp/recovery.js +75 -0
  56. package/bin/src/mcp/recovery.js.map +1 -0
  57. package/bin/src/mcp/telemetry.d.ts.map +1 -0
  58. package/bin/src/mcp/telemetry.js +82 -0
  59. package/bin/src/mcp/telemetry.js.map +1 -0
  60. package/bin/src/mcp/toolRouter.d.ts.map +1 -0
  61. package/bin/src/mcp/toolRouter.js +127 -0
  62. package/bin/src/mcp/toolRouter.js.map +1 -0
  63. package/bin/src/mcp/transport.d.ts.map +1 -0
  64. package/bin/src/mcp/transport.js +328 -0
  65. package/bin/src/mcp/transport.js.map +1 -0
  66. package/bin/src/mcp/types.d.ts.map +1 -0
  67. package/bin/src/mcp/types.js +6 -0
  68. package/bin/src/mcp/types.js.map +1 -0
  69. package/bin/src/server/server.d.ts.map +1 -1
  70. package/bin/src/server/server.js +223 -10
  71. package/bin/src/server/server.js.map +1 -1
  72. package/bin/src/utils/admin.d.ts.map +1 -1
  73. package/bin/src/utils/admin.js +5 -11
  74. package/bin/src/utils/admin.js.map +1 -1
  75. package/bin/src/utils/branding.js +1 -1
  76. package/bin/src/utils/email.js +4 -4
  77. package/bin/src/utils/session.d.ts.map +1 -1
  78. package/bin/src/utils/session.js +8 -7
  79. package/bin/src/utils/session.js.map +1 -1
  80. package/package.json +1 -1
  81. package/web/app.js +1493 -75
  82. package/web/styles.css +1474 -15
@@ -9,7 +9,7 @@ import { saveAgentSession, getRecentSessionsSummary } from '../utils/session.js'
9
9
  import { safeNote, renderMarkdown } from '../utils/markdown.js';
10
10
  import { openai, callOpenAIWithRetry, isQuotaExceededError, sanitizeMessage, sanitizeMessages } from './llm.js';
11
11
  import { checkFileSyntax, verifyAndSelfHealFiles, executeSmartCommand, extractErrorDiagnostics } from './verifier.js';
12
- import { speakText, listenSpeechToText } from './voiceEngine.js';
12
+ import { speakText, listenSpeechToText, cleanTextForSpeech } from './voiceEngine.js';
13
13
  import { TaskStateManager } from './agentState.js';
14
14
  import { SemanticBrowserController } from './browser/index.js';
15
15
  import { ActionVerifier } from './actionVerifier.js';
@@ -20,6 +20,7 @@ import { SystemComputerController, MockComputerController } from './computer/ind
20
20
  import { AgentWorkspace, inferExecutionIntent, resolveDesktopPath } from './workspace/index.js';
21
21
  import { AgentRuntime } from './runtime/index.js';
22
22
  import { loadAuthConfig, isProOrHigher } from '../utils/auth.js';
23
+ import { getMCPManager } from '../mcp/index.js';
23
24
  export { AgentRuntime, AgentWorkspace, inferExecutionIntent, resolveDesktopPath, SystemComputerController, MockComputerController };
24
25
  const execAsync = promisify(exec);
25
26
  export function robustSnippetReplace(origContent, target, replacement) {
@@ -157,7 +158,7 @@ export const AGENT_TOOLS = [
157
158
  },
158
159
  {
159
160
  name: 'run_command',
160
- description: 'Execute a terminal/shell command in the workspace (e.g. npm test, npm run build, git status). WARNING: Do NOT use run_command to create files or folders (e.g. mkdir, touch, echo >)—call write_file directly instead!',
161
+ description: 'Execute a terminal/shell command in the workspace (e.g. npm test, npm run build, git status). WARNING: Do NOT use run_command to create files or folders (e.g. mkdir, touch, echo >)call write_file directly instead!',
161
162
  parameters: {
162
163
  type: 'object',
163
164
  properties: {
@@ -270,7 +271,7 @@ export const AGENT_TOOLS = [
270
271
  },
271
272
  {
272
273
  name: 'ask_user',
273
- description: 'ONLY use for missing API keys or secret credentials. DO NOT use to ask the user what code to write or to re-confirm their prompt—take immediate autonomous tool action instead.',
274
+ description: 'ONLY use for missing API keys or secret credentials. DO NOT use to ask the user what code to write or to re-confirm their prompttake immediate autonomous tool action instead.',
274
275
  parameters: {
275
276
  type: 'object',
276
277
  properties: {
@@ -292,11 +293,14 @@ export const AGENT_TOOLS = [
292
293
  },
293
294
  {
294
295
  name: 'task_completed',
295
- description: 'Signal that the user goal has been fully completed with a final teardown summary.',
296
+ description: 'Signal that the user goal has been fully completed with the full answer, results, and summary.',
296
297
  parameters: {
297
298
  type: 'object',
298
299
  properties: {
299
- summary: { type: 'string', description: 'Comprehensive teardown report of completed changes' },
300
+ summary: {
301
+ type: 'string',
302
+ description: 'The complete answer, findings, and results delivered directly to the user. For lookups or questions, this MUST include all retrieved details, accounts, URLs, or data.',
303
+ },
300
304
  },
301
305
  required: ['summary'],
302
306
  },
@@ -374,10 +378,13 @@ export class AgentExecutionLoop {
374
378
  cancelReason;
375
379
  executionIntent;
376
380
  userTier;
381
+ currentGoal = '';
382
+ isAudioMode = false;
377
383
  constructor(options) {
378
384
  this.cwd = process.cwd();
379
385
  this.autoApprove = Boolean(options?.autoApprove);
380
386
  this.mockMode = Boolean(options?.mockMode);
387
+ this.isAudioMode = Boolean(options?.isAudioMode);
381
388
  this.userTier = (options?.tier || loadAuthConfig().currentTier || 'free').toLowerCase();
382
389
  // Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise.
383
390
  const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
@@ -582,6 +589,10 @@ PLAN TIER & CAPABILITIES: DEVELOPER FREE (BASIC DEVELOPER OPS ONLY)
582
589
  4. Reading & exploring: use read_file, list_dir, grep_search to inspect files.
583
590
  • Do NOT attempt to use scratchpad, computer, or browser tools. Execute the requested file edit, file creation, or command directly and call task_completed immediately.
584
591
  `;
592
+ const mcpRouting = getMCPManager().routeTools(this.currentGoal);
593
+ const mcpCapabilitiesSection = mcpRouting.capabilitiesSummary ? `\n${mcpRouting.capabilitiesSummary}\n` : '';
594
+ const recentSessionsMemory = getRecentSessionsSummary(3);
595
+ const sessionHistorySection = recentSessionsMemory ? `\n${recentSessionsMemory}\n` : '';
585
596
  return `You are Scout Agent, an elite AI Principal Software Engineer & Autonomous Computer-Use Operator (developed by FrontTerrain).
586
597
 
587
598
  Working Directory: "${this.cwd}"
@@ -589,6 +600,8 @@ Host OS Platform: "${process.platform}"
589
600
  Current Date & Time: "${new Date().toISOString()}" (Current Year: ${new Date().getFullYear()})
590
601
  ${tierDirective}
591
602
  ${intentInfo}
603
+ ${mcpCapabilitiesSection}
604
+ ${sessionHistorySection}
592
605
  Workspace Overview (first 100 files):
593
606
  ${filesList.join('\n')}
594
607
 
@@ -615,10 +628,18 @@ CORE ARCHITECTURAL PRINCIPLES:
615
628
  6. MULTILINGUAL & INTENT COMPREHENSION: Understand user intent in any language (English, Hinglish like "likho", "bnao", "code karo", "fix karo", "samjha do", Hindi, Spanish, etc.).
616
629
  7. FOR CODE & REPO TASKS: When the task is a coding, project refactoring, or repository maintenance task, use direct code modification tools (write_file, edit_file, run_command, read_file, grep_search).
617
630
  8. RUN & VERIFY COMMANDS: Execute shell commands, tests, builds, and formatters using run_command.
618
- 9. CRITICAL TERMINATION & CONVERSATIONAL VOICE RULE: As soon as you have finished creating/modifying the necessary files, running verification checks, or completing the desktop application workflow, YOU MUST CALL task_completed tool immediately.
619
- - CONVERSATIONAL CO-PILOT TONE: In your 'summary' parameter for task_completed, speak directly and warmly to the developer like an elite human co-pilot (e.g. "I've launched Chrome for you and it's active on your screen. What should we tackle next?" or "I've created the authentication controller and all unit tests pass with zero regressions!"). Never output dry mechanical robot logs or third-person phrases like "Objective achieved with 1 tool call". Talk directly to the user!
631
+ 9. CRITICAL TERMINATION, RESULTS DELIVERY & CONVERSATIONAL VOICE RULE:
632
+ - For ANY lookup, question, status check, external data retrieval, or inquiry (e.g. "Lookup up my github account", "What is X", "Check my PRs"):
633
+ Your 'summary' argument in task_completed (or your direct response) MUST contain the COMPLETE, ACCURATE, AND DETAILED ANSWER with all specific data found (usernames, repository names, statistics, URLs, findings, or explanations).
634
+ NEVER output a vacuous meta-summary like "Task completed" or "Looked up account" without providing the actual information the user asked for!
635
+ - For coding/modification tasks: Detail what was changed, created, and tested.
636
+ - CONVERSATIONAL CO-PILOT TONE: In your 'summary' parameter for task_completed, speak directly and warmly to the developer like an elite human co-pilot (e.g. "Here are your GitHub account details: your username is ..."). Never output dry mechanical robot logs or third-person phrases like "Objective achieved with 1 tool call". Talk directly to the user!
620
637
  10. THOUGHT TRANSPARENCY PROTOCOL: In EVERY step before calling any tools or ending a turn, you MUST provide a clear 1-2 sentence explanation in your text response describing your current reasoning, what file/action you are taking, and why you are taking it.
621
638
  11. APPLICATION LAUNCH & SIMPLE ACTION TERMINATION: When the user's objective is simply to open, launch, close, or focus an application (e.g. "Open Chrome", "Launch Notepad", "Open Calculator"): Immediately after calling computer_open_app or computer_close_app, your goal is 100% complete! You MUST call task_completed on the very next step with your friendly conversational summary. NEVER explore the repository or call list_dir, read_file, grep_search, or edit_file for app launching tasks!
639
+ 12. STRICT ACTIVE GOAL ISOLATION & ACCURACY:
640
+ - You must strictly execute ONLY the current active goal provided in the user prompt.
641
+ - Historical workspace sessions are provided for background context ONLY. NEVER re-run, repeat, or resume past session tasks unless the user explicitly requests it in the current prompt.
642
+ - For lookups, questions, status checks, or external service queries (e.g. GitHub account, MCP tools, web): deliver the concrete results directly and do NOT attempt to edit or rewrite unrelated workspace files.
622
643
 
623
644
  When returning tool calls, use standard OpenAI function calling format or JSON tool call payload format:
624
645
  \`\`\`json
@@ -631,6 +652,8 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
631
652
  }
632
653
  async executeGoal(userGoal) {
633
654
  this.resetContext();
655
+ this.currentGoal = userGoal;
656
+ this.taskStateManager.createTaskState(userGoal);
634
657
  this.executionIntent = inferExecutionIntent(userGoal);
635
658
  this.taskStateManager.setObjective(userGoal);
636
659
  if (this.computerController.setExpectedApplication && this.executionIntent.requiredApplications.length > 0) {
@@ -638,7 +661,9 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
638
661
  }
639
662
  const structuredPlan = this.taskPlanner.createPlan(userGoal);
640
663
  this.taskStateManager.setPlan(structuredPlan.steps);
641
- this.taskStateManager.setAvailableTools(this.toolRegistry.getToolsForTier(this.userTier).map((t) => t.name));
664
+ const mcpRouted = getMCPManager().routeTools(userGoal);
665
+ const mcpNames = mcpRouted.tools.map((t) => t.originalName);
666
+ this.taskStateManager.setAvailableTools([...this.toolRegistry.getToolsForTier(this.userTier).map((t) => t.name), ...mcpNames]);
642
667
  if (this.mockMode) {
643
668
  for (let i = 1; i <= structuredPlan.steps.length; i++) {
644
669
  this.taskStateManager.startStep(i);
@@ -651,19 +676,14 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
651
676
  };
652
677
  }
653
678
  this.historyMessages.push({ role: 'system', content: this.systemPrompt() });
654
- // Load persistent session history memory (minimum 3 sessions Context)
655
- const recentSessionsMemory = getRecentSessionsSummary(3);
656
- if (recentSessionsMemory) {
657
- this.historyMessages.push({
658
- role: 'user',
659
- content: recentSessionsMemory,
660
- });
661
- }
662
- // Robust Target File Discovery: Only run file discovery if the goal is NOT a simple desktop application launch/action
679
+ // Robust Target File Discovery: Only run file discovery if the goal has explicit code editing/modification intent
663
680
  const simpleAppCheck = checkSimpleAppAction(userGoal);
681
+ const lowerGoal = userGoal.toLowerCase().trim();
682
+ const hasCodeEditIntent = /\b(edit|modify|fix|update|change|refactor|write|create|implement|patch|delete|remove|add\s+code|add\s+function|debug)\b/i.test(lowerGoal) &&
683
+ !/\b(lookup|look\s*up|who|what|why|where|how|explain|describe|tell|status|check|github|mcp)\b/i.test(lowerGoal);
664
684
  const filesList = getDirectoryFiles(this.cwd);
665
685
  const matchedFiles = [];
666
- if (!simpleAppCheck.isSimple) {
686
+ if (hasCodeEditIntent && !simpleAppCheck.isSimple) {
667
687
  const isIgnoredFile = (filePath) => {
668
688
  const norm = filePath.replace(/\\/g, '/').toLowerCase();
669
689
  const ext = path.extname(norm);
@@ -701,7 +721,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
701
721
  }
702
722
  }
703
723
  }
704
- // Keyword fallback if no direct path matched
724
+ // Keyword fallback only if explicit path wasn't matched and code edit intent exists
705
725
  if (matchedFiles.length === 0) {
706
726
  const stopWords = new Set([
707
727
  'the', 'a', 'an', 'in', 'is', 'for', 'to', 'of', 'and', 'or', 'on', 'at', 'by', 'from',
@@ -747,7 +767,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
747
767
  this.readFilesHistory.add(file.replace(/\\/g, '/').toLowerCase());
748
768
  this.historyMessages.push({
749
769
  role: 'user',
750
- content: `[Reference Workspace Context - Pre-loaded target file "${file}" (${lines.length} lines)]:\n\`\`\`\n${previewSnippet}\n\`\`\`\nCRITICAL DIRECTIVE: The target file "${file}" is ALREADY fully pre-loaded into your reference context above. DO NOT call read_file or list_dir for this file again! Immediately begin calling write_file, edit_file, or run_command to satisfy the user goal.`,
770
+ content: `[Reference Workspace Context - Pre-loaded target file "${file}" (${lines.length} lines)]:\n\`\`\`\n${previewSnippet}\n\`\`\`\n(Note: "${file}" is pre-loaded above for your reference. Use this context if relevant to completing the user goal.)`,
751
771
  });
752
772
  }
753
773
  catch {
@@ -759,7 +779,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
759
779
  if (userGoal && userGoal.trim()) {
760
780
  this.historyMessages.push({
761
781
  role: 'user',
762
- content: userGoal,
782
+ content: `USER GOAL: "${userGoal.trim()}"\n\nPlease execute the necessary steps and tools to fulfill this exact goal now.`,
763
783
  });
764
784
  }
765
785
  const estTotalSecs = Math.round(this.maxSteps * 12);
@@ -791,12 +811,26 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
791
811
  stepSpinner.start(chalk.cyan(`Scout is Working (Step ${step}/${this.maxSteps} • Est. completion: ~${estLeftStr} left)`));
792
812
  try {
793
813
  let response;
814
+ const mcpRouting = getMCPManager().routeTools(userGoal);
815
+ const mcpToolDefs = mcpRouting.tools.map((mt) => ({
816
+ name: mt.originalName,
817
+ description: `[MCP: ${mt.serverName}] ${mt.description}`,
818
+ parameters: {
819
+ type: 'object',
820
+ properties: mt.inputSchema.properties || {},
821
+ required: mt.inputSchema.required || [],
822
+ },
823
+ source: 'mcp',
824
+ mcpServerId: mt.serverId,
825
+ mcpServerName: mt.serverName,
826
+ }));
827
+ const activeLoopTools = [...this.toolRegistry.getToolsForTier(this.userTier), ...mcpToolDefs];
794
828
  try {
795
829
  response = await callOpenAIWithRetry(async (model) => {
796
830
  return await openai.chat.completions.create({
797
831
  model,
798
832
  messages: sanitizeMessages(this.historyMessages),
799
- tools: this.toolRegistry.getToolsForTier(this.userTier).map((t) => ({ type: 'function', function: t })),
833
+ tools: activeLoopTools.map((t) => ({ type: 'function', function: t })),
800
834
  tool_choice: 'auto',
801
835
  temperature: 0.1,
802
836
  presence_penalty: 0.1,
@@ -807,7 +841,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
807
841
  catch (err) {
808
842
  const errStr = String(err?.message || err?.error || err || '').toLowerCase();
809
843
  if (errStr.includes('tool') || errStr.includes('400') || errStr.includes('not supported') || errStr.includes('reasoning')) {
810
- // Model doesn't support native function calling — inject tool-call formatting hint
844
+ // Model doesn't support native function calling inject tool-call formatting hint
811
845
  // so extractJsonToolCall can parse the response as a structured tool invocation
812
846
  const toolHintMsg = {
813
847
  role: 'user',
@@ -816,7 +850,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
816
850
  { "tool": "tool_name", "args": { ... } }
817
851
  \`\`\`
818
852
  Available tools: read_file, write_file, edit_file, run_command, list_dir, grep_search, glob_search, tree_view, file_info, multi_edit_file, fetch_url, git_diff, ask_user, speak_text, task_completed.
819
- You MUST output exactly ONE JSON code block per tool call. Do NOT describe what you would do in plain text—output the JSON tool call directly!`,
853
+ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what you would do in plain textoutput the JSON tool call directly!`,
820
854
  };
821
855
  const fallbackMessages = [...this.historyMessages, toolHintMsg];
822
856
  response = await callOpenAIWithRetry(async (model) => {
@@ -844,6 +878,12 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
844
878
  const textContent = msg.content || '';
845
879
  if (textContent.trim()) {
846
880
  safeNote(renderMarkdown(textContent), `Scout Thought (Step ${step})`);
881
+ if (this.isAudioMode) {
882
+ const speechText = cleanTextForSpeech(textContent);
883
+ if (speechText) {
884
+ speakText(speechText.length > 200 ? speechText.slice(0, 200) + '...' : speechText, { async: true });
885
+ }
886
+ }
847
887
  }
848
888
  // Check native tool calls
849
889
  if (msg.tool_calls && msg.tool_calls.length > 0) {
@@ -856,6 +896,21 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
856
896
  args = JSON.parse(tc.function.arguments || '{}');
857
897
  }
858
898
  catch { }
899
+ if (this.isAudioMode && fnName !== 'task_completed') {
900
+ let actionPhrase = '';
901
+ if (fnName === 'run_command')
902
+ actionPhrase = `Running command ${args.command ? args.command.slice(0, 40) : ''}`;
903
+ else if (fnName === 'read_file')
904
+ actionPhrase = `Reading ${path.basename(args.filePath || '')}`;
905
+ else if (fnName === 'write_file')
906
+ actionPhrase = `Creating ${path.basename(args.filePath || '')}`;
907
+ else if (fnName === 'edit_file')
908
+ actionPhrase = `Editing ${path.basename(args.filePath || '')}`;
909
+ else if (fnName.startsWith('mcp_') || fnName.includes('github'))
910
+ actionPhrase = `Querying ${fnName}`;
911
+ if (actionPhrase)
912
+ speakText(actionPhrase, { async: true });
913
+ }
859
914
  const toolResult = await this.dispatchToolCall(fnName, args);
860
915
  this.historyMessages.push({
861
916
  role: 'user',
@@ -976,8 +1031,13 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
976
1031
  lowerText.includes('file created') ||
977
1032
  lowerText.includes('implementation complete') ||
978
1033
  lowerText.includes('work is complete');
979
- // If explicit completion phrase found, OR files have already been modified and assistant returned a final summary without calling tools
980
- if (isExplicitCompletion || (this.modifiedFiles.size > 0 && !lowerText.includes('?') && textContent.length > 50)) {
1034
+ const isInformationalGoal = /^(?:lookup|look\s*up|check|find|who|what|why|where|how|tell|show|explain|display|list|search|query|status|info|get|fetch|describe)\b/i.test(userGoal.trim()) ||
1035
+ !/\b(create|make|write|edit|modify|fix|update|refactor|build|delete|remove|patch|implement)\b/i.test(userGoal.trim());
1036
+ const isAnsweringInformational = isInformationalGoal && textContent.trim().length > 20 && !lowerText.includes('error occurred') && !lowerText.includes('cannot complete');
1037
+ const isSummaryAfterModifications = this.modifiedFiles.size > 0 && !lowerText.includes('?') && textContent.trim().length > 30;
1038
+ const isSummaryAfterToolExecution = this.totalToolCalls > 0 && textContent.trim().length > 30 && !lowerText.includes('calling tool') && !lowerText.includes('next step');
1039
+ // If explicit completion phrase found, OR informational answer provided, OR files were modified, OR tools were executed and assistant is delivering the summary
1040
+ if (isExplicitCompletion || isAnsweringInformational || isSummaryAfterModifications || isSummaryAfterToolExecution) {
981
1041
  saveAgentSession({
982
1042
  goal: userGoal,
983
1043
  summary: textContent,
@@ -1423,13 +1483,13 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
1423
1483
  checkSpin.start(chalk.cyan(` Checking for error: edited ${targetPath}...`));
1424
1484
  const syntaxCheck = await checkFileSyntax(targetPath, this.cwd);
1425
1485
  if (!syntaxCheck.valid) {
1426
- checkSpin.stop(chalk.red(`️ Checking for error: edited ${targetPath} — Compiler/Syntax Error!`));
1486
+ checkSpin.stop(chalk.red(`️ Checking for error: edited ${targetPath} Compiler/Syntax Error!`));
1427
1487
  this.taskStateManager.recordError(`Syntax error in "${targetPath}": ${syntaxCheck.errors.join('; ')}`, 'write_file');
1428
1488
  return {
1429
1489
  result: `Successfully wrote ${content.length} characters (${numberOfLinesChanged} lines changed) to "${targetPath}".\n\n️ AUTOMATED SYNTAX / COMPILER ERROR DETECTED IN "${targetPath}":\n${syntaxCheck.errors.join('\n')}\n\nPLEASE IMMEDIATELY FIX THIS SYNTAX ERROR IN YOUR NEXT TOOL CALL.`
1430
1490
  };
1431
1491
  }
1432
- checkSpin.stop(chalk.green(` Checking for error: edited ${targetPath} — Syntax & build clean.`));
1492
+ checkSpin.stop(chalk.green(` Checking for error: edited ${targetPath} Syntax & build clean.`));
1433
1493
  this.taskStateManager.recordArtifact({
1434
1494
  filename: path.basename(targetPath),
1435
1495
  path: targetPath,
@@ -1489,12 +1549,12 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
1489
1549
  checkSpin.start(chalk.cyan(` Checking for error: edited ${targetPath}...`));
1490
1550
  const syntaxCheck = await checkFileSyntax(targetPath, this.cwd);
1491
1551
  if (!syntaxCheck.valid) {
1492
- checkSpin.stop(chalk.red(`️ Checking for error: edited ${targetPath} — Compiler/Syntax Error!`));
1552
+ checkSpin.stop(chalk.red(`️ Checking for error: edited ${targetPath} Compiler/Syntax Error!`));
1493
1553
  return {
1494
1554
  result: `Successfully replaced snippet (${numberOfLinesChanged} lines changed) in "${targetPath}".\n\n️ AUTOMATED SYNTAX / COMPILER ERROR DETECTED IN "${targetPath}":\n${syntaxCheck.errors.join('\n')}\n\nPLEASE IMMEDIATELY FIX THIS SYNTAX ERROR IN YOUR NEXT TOOL CALL.`
1495
1555
  };
1496
1556
  }
1497
- checkSpin.stop(chalk.green(` Checking for error: edited ${targetPath} — Syntax & build clean.`));
1557
+ checkSpin.stop(chalk.green(` Checking for error: edited ${targetPath} Syntax & build clean.`));
1498
1558
  return { result: `Successfully replaced target snippet (${numberOfLinesChanged} lines changed) in "${targetPath}". Rationale: ${reason}. Syntax check passed cleanly.` };
1499
1559
  }
1500
1560
  case 'run_command': {
@@ -2127,13 +2187,32 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
2127
2187
  };
2128
2188
  }
2129
2189
  }
2130
- speakText(summaryStr, { async: true });
2131
2190
  this.taskStateManager.setStatus('completed');
2132
2191
  this.taskStateManager.completeStep(this.currentStep, summaryStr);
2133
2192
  return { result: summaryStr };
2134
2193
  }
2135
- default:
2194
+ default: {
2195
+ // Check if this tool is provided by a connected MCP server
2196
+ const mcpManager = getMCPManager();
2197
+ const mcpTarget = mcpManager.findTool(name);
2198
+ if (mcpTarget) {
2199
+ const mcpRes = await mcpManager.executeTool(name, args);
2200
+ const isSuccess = mcpRes.status === 'success';
2201
+ const outputStr = typeof mcpRes.result === 'string'
2202
+ ? mcpRes.result
2203
+ : (mcpRes.result?.text ? String(mcpRes.result.text) : JSON.stringify(mcpRes.result ?? { status: mcpRes.status, error: mcpRes.error }));
2204
+ this.taskStateManager.addObservation(name, mcpRes.outputSummary || outputStr, isSuccess);
2205
+ // Check if MCP result generated or referenced local files/artifacts
2206
+ if (isSuccess && typeof mcpRes.result === 'object' && mcpRes.result !== null) {
2207
+ const possiblePath = mcpRes.result.path || mcpRes.result.filePath || mcpRes.result.file || args.destPath || args.path || args.outputPath;
2208
+ if (typeof possiblePath === 'string' && fs.existsSync(path.resolve(this.cwd, possiblePath))) {
2209
+ this.modifiedFiles.add(path.relative(this.cwd, path.resolve(this.cwd, possiblePath)).replace(/\\/g, '/'));
2210
+ }
2211
+ }
2212
+ return { result: outputStr };
2213
+ }
2136
2214
  return { result: `Unknown tool call name: "${name}"` };
2215
+ }
2137
2216
  }
2138
2217
  }
2139
2218
  }