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.
- package/.firebase/hosting.d2Vi.cache +8 -8
- package/AUTONOMOUS_FEATURES.md +1 -1
- package/README.md +113 -16
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +28 -15
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/audit.js +1 -1
- package/bin/src/commands/audit.js.map +1 -1
- package/bin/src/commands/dashboard.js +1 -1
- package/bin/src/commands/dashboard.js.map +1 -1
- package/bin/src/commands/mcp.d.ts.map +1 -0
- package/bin/src/commands/mcp.js +393 -0
- package/bin/src/commands/mcp.js.map +1 -0
- package/bin/src/commands/owners.js +1 -1
- package/bin/src/commands/owners.js.map +1 -1
- package/bin/src/discovery/messages.js +2 -2
- package/bin/src/discovery/messages.js.map +1 -1
- package/bin/src/discovery/signals.js +3 -3
- package/bin/src/discovery/signals.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +111 -32
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
- package/bin/src/engine/runtime/agentRuntime.js +40 -3
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
- package/bin/src/engine/toolRegistry.d.ts.map +1 -1
- package/bin/src/engine/toolRegistry.js +13 -0
- package/bin/src/engine/toolRegistry.js.map +1 -1
- package/bin/src/engine/verifier.js +2 -2
- package/bin/src/engine/verifier.js.map +1 -1
- package/bin/src/index.js +85 -7
- package/bin/src/index.js.map +1 -1
- package/bin/src/mcp/client.d.ts.map +1 -0
- package/bin/src/mcp/client.js +127 -0
- package/bin/src/mcp/client.js.map +1 -0
- package/bin/src/mcp/discovery.d.ts.map +1 -0
- package/bin/src/mcp/discovery.js +91 -0
- package/bin/src/mcp/discovery.js.map +1 -0
- package/bin/src/mcp/health.d.ts.map +1 -0
- package/bin/src/mcp/health.js +44 -0
- package/bin/src/mcp/health.js.map +1 -0
- package/bin/src/mcp/index.d.ts.map +1 -0
- package/bin/src/mcp/index.js +15 -0
- package/bin/src/mcp/index.js.map +1 -0
- package/bin/src/mcp/manager.d.ts.map +1 -0
- package/bin/src/mcp/manager.js +648 -0
- package/bin/src/mcp/manager.js.map +1 -0
- package/bin/src/mcp/permissions.d.ts.map +1 -0
- package/bin/src/mcp/permissions.js +50 -0
- package/bin/src/mcp/permissions.js.map +1 -0
- package/bin/src/mcp/persistence.d.ts.map +1 -0
- package/bin/src/mcp/persistence.js +171 -0
- package/bin/src/mcp/persistence.js.map +1 -0
- package/bin/src/mcp/recovery.d.ts.map +1 -0
- package/bin/src/mcp/recovery.js +75 -0
- package/bin/src/mcp/recovery.js.map +1 -0
- package/bin/src/mcp/telemetry.d.ts.map +1 -0
- package/bin/src/mcp/telemetry.js +82 -0
- package/bin/src/mcp/telemetry.js.map +1 -0
- package/bin/src/mcp/toolRouter.d.ts.map +1 -0
- package/bin/src/mcp/toolRouter.js +127 -0
- package/bin/src/mcp/toolRouter.js.map +1 -0
- package/bin/src/mcp/transport.d.ts.map +1 -0
- package/bin/src/mcp/transport.js +328 -0
- package/bin/src/mcp/transport.js.map +1 -0
- package/bin/src/mcp/types.d.ts.map +1 -0
- package/bin/src/mcp/types.js +6 -0
- package/bin/src/mcp/types.js.map +1 -0
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +223 -10
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/admin.d.ts.map +1 -1
- package/bin/src/utils/admin.js +5 -11
- package/bin/src/utils/admin.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/email.js +4 -4
- package/bin/src/utils/session.d.ts.map +1 -1
- package/bin/src/utils/session.js +8 -7
- package/bin/src/utils/session.js.map +1 -1
- package/package.json +1 -1
- package/web/app.js +1493 -75
- 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 >)
|
|
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
|
|
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
|
|
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: {
|
|
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:
|
|
619
|
-
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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\`\`\`\
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
980
|
-
|
|
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}
|
|
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}
|
|
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}
|
|
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}
|
|
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
|
}
|