ft-scout 9.0.8 → 9.1.0
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/README.md +2 -2
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +26 -13
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/mcp.d.ts.map +1 -1
- package/bin/src/commands/mcp.js +7 -4
- package/bin/src/commands/mcp.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +58 -52
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/llm.d.ts.map +1 -1
- package/bin/src/engine/llm.js +2 -4
- package/bin/src/engine/llm.js.map +1 -1
- package/bin/src/index.js +70 -25
- package/bin/src/index.js.map +1 -1
- package/bin/src/mcp/client.js +1 -1
- package/bin/src/mcp/manager.d.ts.map +1 -1
- package/bin/src/mcp/manager.js +140 -4
- package/bin/src/mcp/manager.js.map +1 -1
- package/bin/src/mcp/persistence.d.ts.map +1 -1
- package/bin/src/mcp/persistence.js +15 -0
- package/bin/src/mcp/persistence.js.map +1 -1
- package/bin/src/mcp/types.d.ts.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +18 -12
- 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/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 +466 -108
- package/web/styles.css +2 -2
|
@@ -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';
|
|
@@ -293,11 +293,14 @@ export const AGENT_TOOLS = [
|
|
|
293
293
|
},
|
|
294
294
|
{
|
|
295
295
|
name: 'task_completed',
|
|
296
|
-
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.',
|
|
297
297
|
parameters: {
|
|
298
298
|
type: 'object',
|
|
299
299
|
properties: {
|
|
300
|
-
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
|
+
},
|
|
301
304
|
},
|
|
302
305
|
required: ['summary'],
|
|
303
306
|
},
|
|
@@ -376,10 +379,12 @@ export class AgentExecutionLoop {
|
|
|
376
379
|
executionIntent;
|
|
377
380
|
userTier;
|
|
378
381
|
currentGoal = '';
|
|
382
|
+
isAudioMode = false;
|
|
379
383
|
constructor(options) {
|
|
380
384
|
this.cwd = process.cwd();
|
|
381
385
|
this.autoApprove = Boolean(options?.autoApprove);
|
|
382
386
|
this.mockMode = Boolean(options?.mockMode);
|
|
387
|
+
this.isAudioMode = Boolean(options?.isAudioMode);
|
|
383
388
|
this.userTier = (options?.tier || loadAuthConfig().currentTier || 'free').toLowerCase();
|
|
384
389
|
// Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise.
|
|
385
390
|
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
@@ -586,6 +591,8 @@ PLAN TIER & CAPABILITIES: DEVELOPER FREE (BASIC DEVELOPER OPS ONLY)
|
|
|
586
591
|
`;
|
|
587
592
|
const mcpRouting = getMCPManager().routeTools(this.currentGoal);
|
|
588
593
|
const mcpCapabilitiesSection = mcpRouting.capabilitiesSummary ? `\n${mcpRouting.capabilitiesSummary}\n` : '';
|
|
594
|
+
const recentSessionsMemory = getRecentSessionsSummary(3);
|
|
595
|
+
const sessionHistorySection = recentSessionsMemory ? `\n${recentSessionsMemory}\n` : '';
|
|
589
596
|
return `You are Scout Agent, an elite AI Principal Software Engineer & Autonomous Computer-Use Operator (developed by FrontTerrain).
|
|
590
597
|
|
|
591
598
|
Working Directory: "${this.cwd}"
|
|
@@ -594,6 +601,7 @@ Current Date & Time: "${new Date().toISOString()}" (Current Year: ${new Date().g
|
|
|
594
601
|
${tierDirective}
|
|
595
602
|
${intentInfo}
|
|
596
603
|
${mcpCapabilitiesSection}
|
|
604
|
+
${sessionHistorySection}
|
|
597
605
|
Workspace Overview (first 100 files):
|
|
598
606
|
${filesList.join('\n')}
|
|
599
607
|
|
|
@@ -620,10 +628,18 @@ CORE ARCHITECTURAL PRINCIPLES:
|
|
|
620
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.).
|
|
621
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).
|
|
622
630
|
8. RUN & VERIFY COMMANDS: Execute shell commands, tests, builds, and formatters using run_command.
|
|
623
|
-
9. CRITICAL TERMINATION & CONVERSATIONAL VOICE RULE:
|
|
624
|
-
-
|
|
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!
|
|
625
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.
|
|
626
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.
|
|
627
643
|
|
|
628
644
|
When returning tool calls, use standard OpenAI function calling format or JSON tool call payload format:
|
|
629
645
|
\`\`\`json
|
|
@@ -637,6 +653,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
637
653
|
async executeGoal(userGoal) {
|
|
638
654
|
this.resetContext();
|
|
639
655
|
this.currentGoal = userGoal;
|
|
656
|
+
this.taskStateManager.createTaskState(userGoal);
|
|
640
657
|
this.executionIntent = inferExecutionIntent(userGoal);
|
|
641
658
|
this.taskStateManager.setObjective(userGoal);
|
|
642
659
|
if (this.computerController.setExpectedApplication && this.executionIntent.requiredApplications.length > 0) {
|
|
@@ -659,19 +676,14 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
659
676
|
};
|
|
660
677
|
}
|
|
661
678
|
this.historyMessages.push({ role: 'system', content: this.systemPrompt() });
|
|
662
|
-
//
|
|
663
|
-
const recentSessionsMemory = getRecentSessionsSummary(3);
|
|
664
|
-
if (recentSessionsMemory) {
|
|
665
|
-
this.historyMessages.push({
|
|
666
|
-
role: 'user',
|
|
667
|
-
content: recentSessionsMemory,
|
|
668
|
-
});
|
|
669
|
-
}
|
|
670
|
-
// 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
|
|
671
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);
|
|
672
684
|
const filesList = getDirectoryFiles(this.cwd);
|
|
673
685
|
const matchedFiles = [];
|
|
674
|
-
if (!simpleAppCheck.isSimple) {
|
|
686
|
+
if (hasCodeEditIntent && !simpleAppCheck.isSimple) {
|
|
675
687
|
const isIgnoredFile = (filePath) => {
|
|
676
688
|
const norm = filePath.replace(/\\/g, '/').toLowerCase();
|
|
677
689
|
const ext = path.extname(norm);
|
|
@@ -709,7 +721,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
709
721
|
}
|
|
710
722
|
}
|
|
711
723
|
}
|
|
712
|
-
// Keyword fallback if
|
|
724
|
+
// Keyword fallback only if explicit path wasn't matched and code edit intent exists
|
|
713
725
|
if (matchedFiles.length === 0) {
|
|
714
726
|
const stopWords = new Set([
|
|
715
727
|
'the', 'a', 'an', 'in', 'is', 'for', 'to', 'of', 'and', 'or', 'on', 'at', 'by', 'from',
|
|
@@ -755,7 +767,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
755
767
|
this.readFilesHistory.add(file.replace(/\\/g, '/').toLowerCase());
|
|
756
768
|
this.historyMessages.push({
|
|
757
769
|
role: 'user',
|
|
758
|
-
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.)`,
|
|
759
771
|
});
|
|
760
772
|
}
|
|
761
773
|
catch {
|
|
@@ -767,7 +779,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
767
779
|
if (userGoal && userGoal.trim()) {
|
|
768
780
|
this.historyMessages.push({
|
|
769
781
|
role: 'user',
|
|
770
|
-
content: userGoal
|
|
782
|
+
content: `USER GOAL: "${userGoal.trim()}"\n\nPlease execute the necessary steps and tools to fulfill this exact goal now.`,
|
|
771
783
|
});
|
|
772
784
|
}
|
|
773
785
|
const estTotalSecs = Math.round(this.maxSteps * 12);
|
|
@@ -866,6 +878,12 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
866
878
|
const textContent = msg.content || '';
|
|
867
879
|
if (textContent.trim()) {
|
|
868
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
|
+
}
|
|
869
887
|
}
|
|
870
888
|
// Check native tool calls
|
|
871
889
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
@@ -878,6 +896,21 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
878
896
|
args = JSON.parse(tc.function.arguments || '{}');
|
|
879
897
|
}
|
|
880
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
|
+
}
|
|
881
914
|
const toolResult = await this.dispatchToolCall(fnName, args);
|
|
882
915
|
this.historyMessages.push({
|
|
883
916
|
role: 'user',
|
|
@@ -998,8 +1031,13 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
998
1031
|
lowerText.includes('file created') ||
|
|
999
1032
|
lowerText.includes('implementation complete') ||
|
|
1000
1033
|
lowerText.includes('work is complete');
|
|
1001
|
-
|
|
1002
|
-
|
|
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) {
|
|
1003
1041
|
saveAgentSession({
|
|
1004
1042
|
goal: userGoal,
|
|
1005
1043
|
summary: textContent,
|
|
@@ -1412,17 +1450,6 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1412
1450
|
const actionText = fileExists ? 'Editing file' : 'Creating file';
|
|
1413
1451
|
const writeSpin = spinner();
|
|
1414
1452
|
writeSpin.start(chalk.yellow(`️ ${actionText} ${targetPath}...`));
|
|
1415
|
-
if (!this.autoApprove && process.stdout.isTTY) {
|
|
1416
|
-
writeSpin.stop();
|
|
1417
|
-
const ok = await confirm({
|
|
1418
|
-
message: chalk.yellow(`Allow Scout Agent to write file: ${chalk.bold.cyan(targetPath)}?`),
|
|
1419
|
-
initialValue: true,
|
|
1420
|
-
});
|
|
1421
|
-
if (isCancel(ok) || !ok) {
|
|
1422
|
-
return { result: `User rejected write_file to "${targetPath}"` };
|
|
1423
|
-
}
|
|
1424
|
-
writeSpin.start(chalk.yellow(`️ ${actionText} ${targetPath}...`));
|
|
1425
|
-
}
|
|
1426
1453
|
let origContent = '__FT_WAS_NEW__';
|
|
1427
1454
|
if (fileExists) {
|
|
1428
1455
|
origContent = fs.readFileSync(fullPath, 'utf-8');
|
|
@@ -1486,17 +1513,6 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1486
1513
|
editSpin.stop(chalk.yellow(`Edit snippet failed for ${targetPath}.`));
|
|
1487
1514
|
return { result: `Error editing "${targetPath}": ${replaceRes.error}` };
|
|
1488
1515
|
}
|
|
1489
|
-
if (!this.autoApprove && process.stdout.isTTY) {
|
|
1490
|
-
editSpin.stop();
|
|
1491
|
-
const ok = await confirm({
|
|
1492
|
-
message: chalk.yellow(`Allow Scout Agent to edit snippet in file: ${chalk.bold.cyan(targetPath)}?`),
|
|
1493
|
-
initialValue: true,
|
|
1494
|
-
});
|
|
1495
|
-
if (isCancel(ok) || !ok) {
|
|
1496
|
-
return { result: `User rejected edit_file to "${targetPath}"` };
|
|
1497
|
-
}
|
|
1498
|
-
editSpin.start(chalk.yellow(`️ Editing file ${targetPath}...`));
|
|
1499
|
-
}
|
|
1500
1516
|
this.backups.push({ filePath: targetPath, originalContent: origContent });
|
|
1501
1517
|
fs.writeFileSync(fullPath, replaceRes.updatedContent, 'utf-8');
|
|
1502
1518
|
this.modifiedFiles.add(targetPath);
|
|
@@ -1555,15 +1571,6 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1555
1571
|
}
|
|
1556
1572
|
const isTestCmd = /\b(?:test|vitest|jest|pytest|mocha|jasmine|cargo test|go test)\b/i.test(cmd);
|
|
1557
1573
|
const actionLabel = isTestCmd ? `Running command for testing: \`${cmd}\`` : `Running command: \`${cmd}\``;
|
|
1558
|
-
if (!this.autoApprove && process.stdout.isTTY) {
|
|
1559
|
-
const ok = await confirm({
|
|
1560
|
-
message: chalk.yellow(`Allow Scout Agent to run shell command: ${chalk.bold.cyan(cmd)}?`),
|
|
1561
|
-
initialValue: true,
|
|
1562
|
-
});
|
|
1563
|
-
if (isCancel(ok) || !ok) {
|
|
1564
|
-
return { result: `User rejected shell execution: "${cmd}"` };
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
1574
|
const runSpin = spinner();
|
|
1568
1575
|
runSpin.start(chalk.cyan(` ${actionLabel}...`));
|
|
1569
1576
|
const res = await executeSmartCommand(cmd, this.cwd, 3000);
|
|
@@ -2149,7 +2156,6 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
2149
2156
|
};
|
|
2150
2157
|
}
|
|
2151
2158
|
}
|
|
2152
|
-
speakText(summaryStr, { async: true });
|
|
2153
2159
|
this.taskStateManager.setStatus('completed');
|
|
2154
2160
|
this.taskStateManager.completeStep(this.currentStep, summaryStr);
|
|
2155
2161
|
return { result: summaryStr };
|