ft-scout 9.4.6 → 9.4.8
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/.scoutrules +13 -0
- package/AUTONOMOUS_FEATURES.md +15 -10
- package/README.md +588 -482
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +73 -25
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/authCommand.d.ts.map +1 -1
- package/bin/src/commands/authCommand.js +4 -10
- package/bin/src/commands/authCommand.js.map +1 -1
- package/bin/src/commands/benchmark.d.ts.map +1 -1
- package/bin/src/commands/benchmark.js +0 -5
- package/bin/src/commands/benchmark.js.map +1 -1
- package/bin/src/commands/checkpoint.d.ts.map +1 -0
- package/bin/src/commands/checkpoint.js +49 -0
- package/bin/src/commands/checkpoint.js.map +1 -0
- package/bin/src/commands/init.d.ts.map +1 -1
- package/bin/src/commands/init.js +14 -17
- package/bin/src/commands/init.js.map +1 -1
- package/bin/src/commands/login.d.ts.map +1 -1
- package/bin/src/commands/login.js +2 -17
- package/bin/src/commands/login.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -1
- package/bin/src/commands/plan.js +36 -6
- package/bin/src/commands/plan.js.map +1 -1
- package/bin/src/commands/pr.d.ts.map +1 -0
- package/bin/src/commands/pr.js +191 -0
- package/bin/src/commands/pr.js.map +1 -0
- package/bin/src/commands/watch.d.ts.map +1 -0
- package/bin/src/commands/watch.js +149 -0
- package/bin/src/commands/watch.js.map +1 -0
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +492 -375
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/checkpoints/checkpointManager.d.ts.map +1 -0
- package/bin/src/engine/checkpoints/checkpointManager.js +171 -0
- package/bin/src/engine/checkpoints/checkpointManager.js.map +1 -0
- package/bin/src/engine/computer/computerController.d.ts.map +1 -1
- package/bin/src/engine/computer/computerController.js +49 -0
- package/bin/src/engine/computer/computerController.js.map +1 -1
- package/bin/src/engine/computer/operatorHud.d.ts.map +1 -0
- package/bin/src/engine/computer/operatorHud.js +126 -0
- package/bin/src/engine/computer/operatorHud.js.map +1 -0
- package/bin/src/engine/llm.d.ts.map +1 -1
- package/bin/src/engine/llm.js +21 -51
- package/bin/src/engine/llm.js.map +1 -1
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
- package/bin/src/engine/runtime/agentRuntime.js +308 -206
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
- package/bin/src/engine/subagents/subagentManager.d.ts.map +1 -0
- package/bin/src/engine/subagents/subagentManager.js +96 -0
- package/bin/src/engine/subagents/subagentManager.js.map +1 -0
- package/bin/src/engine/toolRegistry.js +1 -1
- package/bin/src/engine/toolRegistry.js.map +1 -1
- package/bin/src/engine/voiceEngine.d.ts.map +1 -1
- package/bin/src/engine/voiceEngine.js +27 -238
- package/bin/src/engine/voiceEngine.js.map +1 -1
- package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -1
- package/bin/src/engine/workspace/agentWorkspace.js +22 -9
- package/bin/src/engine/workspace/agentWorkspace.js.map +1 -1
- package/bin/src/index.js +76 -9
- package/bin/src/index.js.map +1 -1
- package/bin/src/mcp/client.js +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +37 -3
- 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 +63 -0
- package/bin/src/utils/admin.js.map +1 -1
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js +10 -5
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/claudeUI.d.ts.map +1 -0
- package/bin/src/utils/claudeUI.js +404 -0
- package/bin/src/utils/claudeUI.js.map +1 -0
- package/bin/src/utils/interruptHandler.d.ts.map +1 -0
- package/bin/src/utils/interruptHandler.js +121 -0
- package/bin/src/utils/interruptHandler.js.map +1 -0
- package/bin/src/utils/payment.d.ts.map +1 -1
- package/bin/src/utils/payment.js +1 -1
- package/bin/src/utils/payment.js.map +1 -1
- package/bin/src/utils/pricing.d.ts.map +1 -1
- package/bin/src/utils/pricing.js +106 -45
- package/bin/src/utils/pricing.js.map +1 -1
- package/bin/src/utils/qrBridge.d.ts.map +1 -1
- package/bin/src/utils/qrBridge.js +17 -17
- package/bin/src/utils/qrBridge.js.map +1 -1
- package/bin/src/utils/rules.d.ts.map +1 -0
- package/bin/src/utils/rules.js +105 -0
- package/bin/src/utils/rules.js.map +1 -0
- package/bin/src/utils/updateCheck.d.ts.map +1 -1
- package/bin/src/utils/updateCheck.js +12 -1
- package/bin/src/utils/updateCheck.js.map +1 -1
- package/firebase.json +1 -1
- package/package.json +1 -1
- package/web/app.js +110 -52
- package/web/styles.css +3 -3
|
@@ -23,6 +23,10 @@ import { loadAuthConfig, isProOrHigher } from '../utils/auth.js';
|
|
|
23
23
|
import { getMCPManager } from '../mcp/index.js';
|
|
24
24
|
import { writeTelemetry } from '../utils/qrBridge.js';
|
|
25
25
|
import { compress, LEVEL_FAST } from 'codrop';
|
|
26
|
+
import { CheckpointManager } from './checkpoints/checkpointManager.js';
|
|
27
|
+
import { renderStepHeader, renderThoughtBox, renderToolCall, renderToolResult, renderTaskCompletedCard, } from '../utils/claudeUI.js';
|
|
28
|
+
import { loadWorkspaceRules } from '../utils/rules.js';
|
|
29
|
+
import { InterruptManager } from '../utils/interruptHandler.js';
|
|
26
30
|
export { AgentRuntime, AgentWorkspace, inferExecutionIntent, resolveDesktopPath, SystemComputerController, MockComputerController };
|
|
27
31
|
const execAsync = promisify(exec);
|
|
28
32
|
export function robustSnippetReplace(origContent, target, replacement) {
|
|
@@ -293,6 +297,29 @@ export const AGENT_TOOLS = [
|
|
|
293
297
|
required: ['text'],
|
|
294
298
|
},
|
|
295
299
|
},
|
|
300
|
+
{
|
|
301
|
+
name: 'delegate_subtask',
|
|
302
|
+
description: 'Delegate a specialized subtask to an autonomous specialist subagent (architect, coder, tester, reviewer, general). Useful for delegating code reviews, test generation, architectural breakdowns, or isolated module implementations to an autonomous worker agent.',
|
|
303
|
+
parameters: {
|
|
304
|
+
type: 'object',
|
|
305
|
+
properties: {
|
|
306
|
+
role: {
|
|
307
|
+
type: 'string',
|
|
308
|
+
enum: ['architect', 'coder', 'tester', 'reviewer', 'general'],
|
|
309
|
+
description: 'Specialist role for the subagent (architect, coder, tester, reviewer, general)',
|
|
310
|
+
},
|
|
311
|
+
task: {
|
|
312
|
+
type: 'string',
|
|
313
|
+
description: 'The specific subtask instruction or goal for the subagent to complete',
|
|
314
|
+
},
|
|
315
|
+
context: {
|
|
316
|
+
type: 'string',
|
|
317
|
+
description: 'Relevant file paths, error messages, or architectural context to provide to the subagent',
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
required: ['role', 'task'],
|
|
321
|
+
},
|
|
322
|
+
},
|
|
296
323
|
{
|
|
297
324
|
name: 'task_completed',
|
|
298
325
|
description: 'Signal that the user goal has been fully completed with the full answer, results, and summary.',
|
|
@@ -383,6 +410,7 @@ export class AgentExecutionLoop {
|
|
|
383
410
|
userTier;
|
|
384
411
|
currentGoal = '';
|
|
385
412
|
isAudioMode = false;
|
|
413
|
+
agentWorkspace;
|
|
386
414
|
syncTelemetry(status, lastLog) {
|
|
387
415
|
try {
|
|
388
416
|
writeTelemetry({
|
|
@@ -403,11 +431,12 @@ export class AgentExecutionLoop {
|
|
|
403
431
|
this.mockMode = Boolean(options?.mockMode);
|
|
404
432
|
this.isAudioMode = Boolean(options?.isAudioMode);
|
|
405
433
|
this.userTier = (options?.tier || loadAuthConfig().currentTier || 'free').toLowerCase();
|
|
406
|
-
// Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise.
|
|
407
|
-
const isPaid = this.mockMode || this.userTier
|
|
408
|
-
const requestedSteps = options?.maxSteps
|
|
409
|
-
this.maxSteps = isPaid ? requestedSteps : Math.min(requestedSteps, 5);
|
|
410
|
-
this.
|
|
434
|
+
// Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise (unlimited steps).
|
|
435
|
+
const isPaid = this.mockMode || isProOrHigher(this.userTier);
|
|
436
|
+
const requestedSteps = options?.maxSteps;
|
|
437
|
+
this.maxSteps = isPaid ? (requestedSteps || Infinity) : Math.min(requestedSteps || 5, 5);
|
|
438
|
+
this.agentWorkspace = new AgentWorkspace(options?.taskId, '', this.cwd);
|
|
439
|
+
this.taskStateManager = this.agentWorkspace.getStateManager();
|
|
411
440
|
this.computerController = options?.computerController || (this.mockMode ? new MockComputerController() : new SystemComputerController(this.cwd));
|
|
412
441
|
this.browserController = options?.browserController || new SemanticBrowserController(this.cwd);
|
|
413
442
|
this.actionVerifier = new ActionVerifier(this.cwd);
|
|
@@ -418,6 +447,9 @@ export class AgentExecutionLoop {
|
|
|
418
447
|
getUserTier() {
|
|
419
448
|
return this.userTier;
|
|
420
449
|
}
|
|
450
|
+
isPaidTier() {
|
|
451
|
+
return this.mockMode || isProOrHigher(this.userTier);
|
|
452
|
+
}
|
|
421
453
|
getExecutionIntent() {
|
|
422
454
|
return this.executionIntent;
|
|
423
455
|
}
|
|
@@ -591,9 +623,10 @@ Task Execution Intent & Constraints:
|
|
|
591
623
|
• Target Destination: ${this.executionIntent.targetDestination || 'Default'}
|
|
592
624
|
• Target Artifact: ${this.executionIntent.targetArtifactName || 'None'}
|
|
593
625
|
` : '';
|
|
594
|
-
const isPaid = this.
|
|
626
|
+
const isPaid = this.isPaidTier();
|
|
595
627
|
const tierDirective = isPaid ? `
|
|
596
628
|
PLAN TIER & CAPABILITIES: ${this.userTier.toUpperCase()} (FULL ACCESS UNLOCKED)
|
|
629
|
+
• Unlimited autonomous multi-step loops with no step limits.
|
|
597
630
|
• Autonomous Computer-Use, Browser Automation, and AI Scratchpad Reasoning are fully ENABLED.
|
|
598
631
|
• Use the 'scratchpad' tool to organize multi-step reasoning, break down complex goals, draft hypotheses, and track working memory across turns.
|
|
599
632
|
` : `
|
|
@@ -610,6 +643,8 @@ PLAN TIER & CAPABILITIES: DEVELOPER FREE (BASIC DEVELOPER OPS ONLY)
|
|
|
610
643
|
const mcpCapabilitiesSection = mcpRouting.capabilitiesSummary ? `\n${mcpRouting.capabilitiesSummary}\n` : '';
|
|
611
644
|
const recentSessionsMemory = getRecentSessionsSummary(1);
|
|
612
645
|
const sessionHistorySection = recentSessionsMemory ? `\n${recentSessionsMemory}\n` : '';
|
|
646
|
+
const workspaceRules = loadWorkspaceRules(this.cwd);
|
|
647
|
+
const rulesSection = workspaceRules.found ? `\n${workspaceRules.formattedPrompt}\n` : '';
|
|
613
648
|
return `You are Scout Agent, an elite AI Principal Software Engineer & Autonomous Computer-Use Operator (developed by FrontTerrain).
|
|
614
649
|
|
|
615
650
|
Working Directory: "${this.cwd}"
|
|
@@ -619,6 +654,7 @@ ${tierDirective}
|
|
|
619
654
|
${intentInfo}
|
|
620
655
|
${mcpCapabilitiesSection}
|
|
621
656
|
${sessionHistorySection}
|
|
657
|
+
${rulesSection}
|
|
622
658
|
Workspace Overview (first 100 files):
|
|
623
659
|
${filesList.join('\n')}
|
|
624
660
|
|
|
@@ -710,6 +746,10 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
710
746
|
};
|
|
711
747
|
}
|
|
712
748
|
this.historyMessages.push({ role: 'system', content: this.systemPrompt() });
|
|
749
|
+
const workspaceRules = loadWorkspaceRules(this.cwd);
|
|
750
|
+
if (workspaceRules.found) {
|
|
751
|
+
console.log(chalk.cyan(`📋 Loaded workspace rules from ${workspaceRules.fileName}`));
|
|
752
|
+
}
|
|
713
753
|
// Robust Target File Discovery: Only run file discovery if the goal has explicit code editing/modification intent
|
|
714
754
|
const simpleAppCheck = checkSimpleAppAction(userGoal);
|
|
715
755
|
const lowerGoal = userGoal.toLowerCase().trim();
|
|
@@ -821,371 +861,448 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
821
861
|
content: promptContent,
|
|
822
862
|
});
|
|
823
863
|
}
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
864
|
+
const isPaid = this.mockMode || isProOrHigher(this.userTier);
|
|
865
|
+
if (!isPaid && Number.isFinite(this.maxSteps)) {
|
|
866
|
+
const estTotalSecs = Math.round(this.maxSteps * 12);
|
|
867
|
+
const estTimeText = estTotalSecs >= 60 ? `~${(estTotalSecs / 60).toFixed(1)}m` : `~${estTotalSecs}s`;
|
|
868
|
+
console.log(chalk.gray(`Estimated completion time: ${estTimeText} (Free Tier Max ${this.maxSteps} steps)`));
|
|
869
|
+
}
|
|
870
|
+
else {
|
|
871
|
+
console.log(chalk.gray(`⚡ Autonomous execution loop: Unlimited steps active (${this.userTier.toUpperCase()} plan)`));
|
|
872
|
+
}
|
|
827
873
|
let step = 0;
|
|
828
874
|
let finalSummary = '';
|
|
829
875
|
const stepDurations = [];
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
let desc = mt.description;
|
|
856
|
-
if (mt.originalName === 'execute_blender_code') {
|
|
857
|
-
desc = 'Execute Python (bpy) code inside Blender. Use this to procedurally create, model, texture, color, and render 3D meshes, anime/game characters, clothing, hair, materials, cel-shading, lights, cameras, and scenes directly.';
|
|
858
|
-
}
|
|
859
|
-
else if (mt.originalName === 'generate_3d') {
|
|
860
|
-
desc = 'Cloud AI text-to-3d generator (requires external API key). If keys are not set, use execute_blender_code to model directly.';
|
|
876
|
+
const interruptManager = new InterruptManager({
|
|
877
|
+
getStepInfo: () => ({
|
|
878
|
+
step: this.currentStep || 1,
|
|
879
|
+
maxSteps: this.maxSteps,
|
|
880
|
+
goal: this.currentGoal,
|
|
881
|
+
modifiedFilesCount: this.modifiedFiles.size,
|
|
882
|
+
}),
|
|
883
|
+
onPause: () => {
|
|
884
|
+
this.syncTelemetry('paused', `scout: execution paused by user at step ${this.currentStep}`);
|
|
885
|
+
},
|
|
886
|
+
onResume: () => {
|
|
887
|
+
this.syncTelemetry('running', `scout: execution resumed at step ${this.currentStep}`);
|
|
888
|
+
},
|
|
889
|
+
onFeedback: (feedback) => {
|
|
890
|
+
this.historyMessages.push({
|
|
891
|
+
role: 'user',
|
|
892
|
+
content: `[USER STEERING FEEDBACK (INTERRUPTED AT STEP ${this.currentStep})]:\n"${feedback}"\n\nPlease adapt your execution plan accordingly and follow these user steering instructions immediately.`,
|
|
893
|
+
});
|
|
894
|
+
},
|
|
895
|
+
onRevert: () => {
|
|
896
|
+
if (this.backups.length === 0)
|
|
897
|
+
return false;
|
|
898
|
+
for (const b of this.backups) {
|
|
899
|
+
try {
|
|
900
|
+
fs.writeFileSync(b.filePath, b.originalContent, 'utf-8');
|
|
861
901
|
}
|
|
902
|
+
catch { }
|
|
903
|
+
}
|
|
904
|
+
this.modifiedFiles.clear();
|
|
905
|
+
return true;
|
|
906
|
+
},
|
|
907
|
+
onAbort: (reason) => {
|
|
908
|
+
this.isCancelled = true;
|
|
909
|
+
this.cancelReason = reason;
|
|
910
|
+
},
|
|
911
|
+
});
|
|
912
|
+
interruptManager.attach();
|
|
913
|
+
try {
|
|
914
|
+
while (step < this.maxSteps) {
|
|
915
|
+
if (this.isCancelled) {
|
|
862
916
|
return {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
parameters: {
|
|
866
|
-
type: 'object',
|
|
867
|
-
properties: mt.inputSchema.properties || {},
|
|
868
|
-
required: mt.inputSchema.required || [],
|
|
869
|
-
},
|
|
870
|
-
source: 'mcp',
|
|
871
|
-
mcpServerId: mt.serverId,
|
|
872
|
-
mcpServerName: mt.serverName,
|
|
917
|
+
success: false,
|
|
918
|
+
summary: `Task cancelled: ${this.cancelReason || 'User abort'}`,
|
|
873
919
|
};
|
|
920
|
+
}
|
|
921
|
+
step++;
|
|
922
|
+
this.currentStep = step;
|
|
923
|
+
this.taskStateManager.startStep(step);
|
|
924
|
+
// Context Compaction check: If history is long, compact middle turns using workspace state (including scratchpad working memory)
|
|
925
|
+
if (this.historyMessages.length > 10) {
|
|
926
|
+
this.historyMessages = this.agentWorkspace.compactHistoryMessages(this.historyMessages, 6);
|
|
927
|
+
}
|
|
928
|
+
const stepStartTime = Date.now();
|
|
929
|
+
renderStepHeader({
|
|
930
|
+
step,
|
|
931
|
+
maxSteps: this.maxSteps,
|
|
932
|
+
startTimeMs: this.startTime,
|
|
933
|
+
tokensConsumed: this.totalTokensConsumed,
|
|
934
|
+
userTier: this.userTier,
|
|
874
935
|
});
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
let baseTools = this.toolRegistry.getToolsForTier(this.userTier);
|
|
879
|
-
if (mcpToolDefs.length > 0 && !needsBrowser && !needsComputer) {
|
|
880
|
-
const ESSENTIAL_BASE_TOOLS = new Set(['read_file', 'write_file', 'edit_file', 'run_command', 'list_dir', 'grep_search', 'task_completed', 'ask_user', 'speak_text', 'scratchpad', 'computer_open_app']);
|
|
881
|
-
baseTools = baseTools.filter((t) => ESSENTIAL_BASE_TOOLS.has(t.name));
|
|
936
|
+
const stepSpinner = spinner();
|
|
937
|
+
if (isPaid && !Number.isFinite(this.maxSteps)) {
|
|
938
|
+
stepSpinner.start(chalk.cyan(`Scout is Working (Step ${step})`));
|
|
882
939
|
}
|
|
883
|
-
else
|
|
884
|
-
|
|
940
|
+
else {
|
|
941
|
+
const avgStepMs = stepDurations.length > 0
|
|
942
|
+
? stepDurations.reduce((a, b) => a + b, 0) / stepDurations.length
|
|
943
|
+
: 12000;
|
|
944
|
+
const remainingSteps = (this.maxSteps - step + 1);
|
|
945
|
+
const estSecsLeft = Math.max(5, Math.round((remainingSteps * avgStepMs) / 1000));
|
|
946
|
+
const estLeftStr = estSecsLeft >= 60
|
|
947
|
+
? `${Math.floor(estSecsLeft / 60)}m ${estSecsLeft % 60}s`
|
|
948
|
+
: `${estSecsLeft}s`;
|
|
949
|
+
stepSpinner.start(chalk.cyan(`Scout is Working (Step ${step}/${this.maxSteps} • Est. completion: ~${estLeftStr} left)`));
|
|
885
950
|
}
|
|
886
|
-
const activeLoopTools = [...baseTools, ...mcpToolDefs];
|
|
887
951
|
try {
|
|
888
|
-
response
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
\`\`\`json
|
|
910
|
-
{ "tool": "tool_name", "args": { ... } }
|
|
911
|
-
\`\`\`
|
|
912
|
-
Available tools: ${availableToolNames}
|
|
913
|
-
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!`,
|
|
952
|
+
let response;
|
|
953
|
+
const mcpRouting = getMCPManager().routeTools(userGoal);
|
|
954
|
+
const mcpToolDefs = mcpRouting.tools.map((mt) => {
|
|
955
|
+
let desc = mt.description;
|
|
956
|
+
if (mt.originalName === 'execute_blender_code') {
|
|
957
|
+
desc = 'Execute Python (bpy) code inside Blender. Use this to procedurally create, model, texture, color, and render 3D meshes, anime/game characters, clothing, hair, materials, cel-shading, lights, cameras, and scenes directly.';
|
|
958
|
+
}
|
|
959
|
+
else if (mt.originalName === 'generate_3d') {
|
|
960
|
+
desc = 'Cloud AI text-to-3d generator (requires external API key). If keys are not set, use execute_blender_code to model directly.';
|
|
961
|
+
}
|
|
962
|
+
return {
|
|
963
|
+
name: mt.originalName,
|
|
964
|
+
description: `[MCP: ${mt.serverName}] ${desc.length > 160 ? desc.slice(0, 157) + '...' : desc}`,
|
|
965
|
+
parameters: {
|
|
966
|
+
type: 'object',
|
|
967
|
+
properties: mt.inputSchema.properties || {},
|
|
968
|
+
required: mt.inputSchema.required || [],
|
|
969
|
+
},
|
|
970
|
+
source: 'mcp',
|
|
971
|
+
mcpServerId: mt.serverId,
|
|
972
|
+
mcpServerName: mt.serverName,
|
|
914
973
|
};
|
|
915
|
-
|
|
974
|
+
});
|
|
975
|
+
const goalLower = (userGoal || '').toLowerCase();
|
|
976
|
+
const needsBrowser = goalLower.includes('browser') || goalLower.includes('website') || goalLower.includes('webpage') || goalLower.includes('http') || goalLower.includes('url') || goalLower.includes('scrape') || goalLower.includes('chrome');
|
|
977
|
+
const needsComputer = goalLower.includes('computer') || goalLower.includes('desktop') || goalLower.includes('mouse') || goalLower.includes('coordinate') || goalLower.includes('drag') || goalLower.includes('double click');
|
|
978
|
+
let baseTools = this.toolRegistry.getToolsForTier(this.userTier);
|
|
979
|
+
if (mcpToolDefs.length > 0 && !needsBrowser && !needsComputer) {
|
|
980
|
+
const ESSENTIAL_BASE_TOOLS = new Set(['read_file', 'write_file', 'edit_file', 'run_command', 'list_dir', 'grep_search', 'task_completed', 'ask_user', 'speak_text', 'scratchpad', 'computer_open_app']);
|
|
981
|
+
baseTools = baseTools.filter((t) => ESSENTIAL_BASE_TOOLS.has(t.name));
|
|
982
|
+
}
|
|
983
|
+
else if (!needsBrowser) {
|
|
984
|
+
baseTools = baseTools.filter((t) => !t.name.startsWith('browser_'));
|
|
985
|
+
}
|
|
986
|
+
const activeLoopTools = [...baseTools, ...mcpToolDefs];
|
|
987
|
+
try {
|
|
916
988
|
response = await callOpenAIWithRetry(async (model) => {
|
|
917
989
|
return await openai.chat.completions.create({
|
|
918
990
|
model,
|
|
919
|
-
messages: sanitizeMessages(
|
|
991
|
+
messages: sanitizeMessages(this.historyMessages),
|
|
992
|
+
tools: activeLoopTools.map((t) => ({ type: 'function', function: t })),
|
|
993
|
+
tool_choice: 'auto',
|
|
920
994
|
temperature: 0.1,
|
|
921
995
|
presence_penalty: 0.1,
|
|
922
996
|
frequency_penalty: 0.1,
|
|
923
997
|
});
|
|
924
998
|
});
|
|
925
999
|
}
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1000
|
+
catch (err) {
|
|
1001
|
+
const errStr = String(err?.message || err?.error || err || '').toLowerCase();
|
|
1002
|
+
if (errStr.includes('tool') || errStr.includes('400') || errStr.includes('not supported') || errStr.includes('reasoning')) {
|
|
1003
|
+
// Model doesn't support native function calling inject tool-call formatting hint
|
|
1004
|
+
// so extractJsonToolCall can parse the response as a structured tool invocation
|
|
1005
|
+
const availableToolNames = activeLoopTools.map((t) => t.name).join(', ');
|
|
1006
|
+
const toolHintMsg = {
|
|
1007
|
+
role: 'user',
|
|
1008
|
+
content: `IMPORTANT: This model does not support native function/tool calling. You MUST format your tool invocations as a JSON code block in your response like this:
|
|
1009
|
+
\`\`\`json
|
|
1010
|
+
{ "tool": "tool_name", "args": { ... } }
|
|
1011
|
+
\`\`\`
|
|
1012
|
+
Available tools: ${availableToolNames}
|
|
1013
|
+
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!`,
|
|
1014
|
+
};
|
|
1015
|
+
const fallbackMessages = [...this.historyMessages, toolHintMsg];
|
|
1016
|
+
response = await callOpenAIWithRetry(async (model) => {
|
|
1017
|
+
return await openai.chat.completions.create({
|
|
1018
|
+
model,
|
|
1019
|
+
messages: sanitizeMessages(fallbackMessages),
|
|
1020
|
+
temperature: 0.1,
|
|
1021
|
+
presence_penalty: 0.1,
|
|
1022
|
+
frequency_penalty: 0.1,
|
|
1023
|
+
});
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
throw err;
|
|
948
1028
|
}
|
|
949
1029
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
else if (fnName.startsWith('mcp_') || fnName.includes('github'))
|
|
973
|
-
actionPhrase = `Querying ${fnName}`;
|
|
974
|
-
if (actionPhrase)
|
|
975
|
-
speakText(actionPhrase, { async: true });
|
|
976
|
-
}
|
|
977
|
-
const toolResult = await this.dispatchToolCall(fnName, args);
|
|
978
|
-
this.totalToolCalls++;
|
|
979
|
-
this.syncTelemetry('running', `scout: [step ${step}] executed tool ${fnName}`);
|
|
980
|
-
const toolResStr = String(toolResult.result || '');
|
|
981
|
-
let cappedResult = toolResStr;
|
|
982
|
-
if (toolResStr.length > 800) {
|
|
1030
|
+
const choice = response.choices[0];
|
|
1031
|
+
if (!choice) {
|
|
1032
|
+
stepSpinner.stop(chalk.yellow('No response from Scout. Retrying step...'));
|
|
1033
|
+
continue;
|
|
1034
|
+
}
|
|
1035
|
+
const msg = sanitizeMessage(choice.message);
|
|
1036
|
+
const tokensThisTurn = response?.usage?.total_tokens || 0;
|
|
1037
|
+
this.totalTokensConsumed += tokensThisTurn;
|
|
1038
|
+
this.syncTelemetry('running', `scout: [step ${step}] generated reasoning (${tokensThisTurn} tokens)`);
|
|
1039
|
+
this.historyMessages.push(msg);
|
|
1040
|
+
// Render assistant's thought reasoning text if provided on this turn
|
|
1041
|
+
const textContent = msg.content || '';
|
|
1042
|
+
if (textContent.trim()) {
|
|
1043
|
+
renderThoughtBox(textContent, step);
|
|
1044
|
+
}
|
|
1045
|
+
// Check native tool calls
|
|
1046
|
+
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
1047
|
+
stepSpinner.stop(chalk.green(`Step ${step}: Scout issued ${msg.tool_calls.length} tool call(s).`));
|
|
1048
|
+
for (const tc of msg.tool_calls) {
|
|
1049
|
+
if (tc.type === 'function' && tc.function) {
|
|
1050
|
+
const fnName = tc.function.name;
|
|
1051
|
+
let args = {};
|
|
983
1052
|
try {
|
|
984
|
-
|
|
985
|
-
const cdp = compress(Buffer.from(toolResStr, 'utf-8'), LEVEL_FAST);
|
|
986
|
-
const ratio = (origBytes / Math.max(1, cdp.length)).toFixed(1);
|
|
987
|
-
cappedResult = toolResStr.slice(0, 800) + `\n... [Codrop: ${origBytes}B compressed to ${cdp.length}B (${ratio}x ratio) for LLM context]`;
|
|
988
|
-
this.syncTelemetry('running', `scout: [codrop] compressed ${fnName} output (${ratio}x ratio)`);
|
|
1053
|
+
args = JSON.parse(tc.function.arguments || '{}');
|
|
989
1054
|
}
|
|
990
|
-
catch {
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1055
|
+
catch { }
|
|
1056
|
+
const toolStartTime = Date.now();
|
|
1057
|
+
const toolResult = await this.dispatchToolCall(fnName, args);
|
|
1058
|
+
const durationMs = Date.now() - toolStartTime;
|
|
1059
|
+
renderToolResult({
|
|
1060
|
+
name: fnName,
|
|
1061
|
+
args,
|
|
1062
|
+
result: toolResult.result,
|
|
1063
|
+
durationMs,
|
|
1064
|
+
success: !String(toolResult.result).startsWith('[PLAN RESTRICTION') && !String(toolResult.result).startsWith('[ACTION BLOCKED'),
|
|
1065
|
+
});
|
|
1066
|
+
this.totalToolCalls++;
|
|
1067
|
+
this.syncTelemetry('running', `scout: [step ${step}] executed tool ${fnName}`);
|
|
1068
|
+
const toolResStr = String(toolResult.result || '');
|
|
1069
|
+
let cappedResult = toolResStr;
|
|
1070
|
+
if (toolResStr.length > 800) {
|
|
1071
|
+
try {
|
|
1072
|
+
const origBytes = Buffer.byteLength(toolResStr, 'utf-8');
|
|
1073
|
+
const cdp = compress(Buffer.from(toolResStr, 'utf-8'), LEVEL_FAST);
|
|
1074
|
+
const ratio = (origBytes / Math.max(1, cdp.length)).toFixed(1);
|
|
1075
|
+
cappedResult = toolResStr.slice(0, 800) + `\n... [Codrop: ${origBytes}B compressed to ${cdp.length}B (${ratio}x ratio) for LLM context]`;
|
|
1076
|
+
this.syncTelemetry('running', `scout: [codrop] compressed ${fnName} output (${ratio}x ratio)`);
|
|
1077
|
+
}
|
|
1078
|
+
catch {
|
|
1079
|
+
cappedResult = toolResStr.slice(0, 800) + '\n... [output truncated for token efficiency]';
|
|
1080
|
+
}
|
|
1006
1081
|
}
|
|
1007
|
-
|
|
1008
|
-
|
|
1082
|
+
this.historyMessages.push({
|
|
1083
|
+
role: 'user',
|
|
1084
|
+
tool_call_id: tc.id,
|
|
1085
|
+
content: `Tool Execution Result (${fnName}):\n${cappedResult}`,
|
|
1086
|
+
});
|
|
1087
|
+
this.handleToolConsecutiveTracking(fnName, args, step);
|
|
1088
|
+
// Automatic immediate completion for simple app actions (open/close/switch window)
|
|
1089
|
+
if (simpleAppCheck.isSimple && (fnName === 'computer_open_app' || fnName === 'computer_close_app' || fnName === 'computer_switch_window')) {
|
|
1090
|
+
let callSucceeded = false;
|
|
1091
|
+
try {
|
|
1092
|
+
const parsed = JSON.parse(toolResult.result);
|
|
1093
|
+
callSucceeded = parsed.success !== false;
|
|
1094
|
+
}
|
|
1095
|
+
catch {
|
|
1096
|
+
callSucceeded = !toolResult.result.toLowerCase().includes('failed') && !toolResult.result.toLowerCase().includes('error');
|
|
1097
|
+
}
|
|
1098
|
+
if (callSucceeded) {
|
|
1099
|
+
const appName = simpleAppCheck.appName || 'application';
|
|
1100
|
+
const actionVerb = simpleAppCheck.action === 'close' ? 'closed' : (simpleAppCheck.action === 'switch' ? 'focused' : 'launched');
|
|
1101
|
+
finalSummary = `I've ${actionVerb} ${appName} for you. It's ready on your screen!`;
|
|
1102
|
+
renderTaskCompletedCard({
|
|
1103
|
+
objective: userGoal,
|
|
1104
|
+
summary: finalSummary,
|
|
1105
|
+
modifiedFiles: [],
|
|
1106
|
+
steps: step,
|
|
1107
|
+
maxSteps: this.maxSteps,
|
|
1108
|
+
totalTokens: this.totalTokensConsumed,
|
|
1109
|
+
totalToolCalls: this.totalToolCalls,
|
|
1110
|
+
elapsedSecs: Math.round((Date.now() - this.startTime) / 1000),
|
|
1111
|
+
userTier: this.userTier,
|
|
1112
|
+
});
|
|
1113
|
+
saveAgentSession({
|
|
1114
|
+
goal: userGoal,
|
|
1115
|
+
summary: finalSummary,
|
|
1116
|
+
modifiedFiles: [],
|
|
1117
|
+
});
|
|
1118
|
+
this.syncTelemetry('idle', `scout: completed task successfully (${this.modifiedFiles.size} files modified)`);
|
|
1119
|
+
return { success: true, summary: finalSummary };
|
|
1120
|
+
}
|
|
1009
1121
|
}
|
|
1010
|
-
if (
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1122
|
+
if (fnName === 'task_completed') {
|
|
1123
|
+
finalSummary = args.summary || toolResult.result;
|
|
1124
|
+
if (this.modifiedFiles.size > 0) {
|
|
1125
|
+
const filesList = getDirectoryFiles(this.cwd);
|
|
1126
|
+
const healRes = await verifyAndSelfHealFiles(Array.from(this.modifiedFiles), this.cwd, path.basename(this.cwd), filesList, { maxRetries: 3 });
|
|
1127
|
+
if (healRes.verifiedFiles.length > 0) {
|
|
1128
|
+
safeNote(chalk.green(` Self-Healing Verification Confirmed: ${healRes.verifiedFiles.length} file(s) syntax & build clean!`), ' Code Verification Clean');
|
|
1129
|
+
}
|
|
1130
|
+
if (healRes.remainingErrors.length > 0) {
|
|
1131
|
+
safeNote(chalk.yellow(`️ Remaining verification issues:\n${healRes.remainingErrors.join('\n')}`), '️ Verification Warning');
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
renderTaskCompletedCard({
|
|
1135
|
+
objective: userGoal,
|
|
1136
|
+
summary: finalSummary,
|
|
1137
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1138
|
+
steps: step,
|
|
1139
|
+
maxSteps: this.maxSteps,
|
|
1140
|
+
totalTokens: this.totalTokensConsumed,
|
|
1141
|
+
totalToolCalls: this.totalToolCalls,
|
|
1142
|
+
elapsedSecs: Math.round((Date.now() - this.startTime) / 1000),
|
|
1143
|
+
userTier: this.userTier,
|
|
1144
|
+
});
|
|
1015
1145
|
saveAgentSession({
|
|
1016
1146
|
goal: userGoal,
|
|
1017
1147
|
summary: finalSummary,
|
|
1018
|
-
modifiedFiles:
|
|
1148
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1019
1149
|
});
|
|
1020
|
-
this.syncTelemetry('idle', `scout: completed task successfully (${this.modifiedFiles.size} files modified)`);
|
|
1021
1150
|
return { success: true, summary: finalSummary };
|
|
1022
1151
|
}
|
|
1023
1152
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1153
|
+
}
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
// Check text response or fallback JSON tool call
|
|
1157
|
+
stepSpinner.stop(chalk.blue(`Step ${step} thinking complete.`));
|
|
1158
|
+
const parsedJsonTool = this.extractJsonToolCall(textContent);
|
|
1159
|
+
if (parsedJsonTool) {
|
|
1160
|
+
const toolStartTime = Date.now();
|
|
1161
|
+
const toolResult = await this.dispatchToolCall(parsedJsonTool.tool, parsedJsonTool.args);
|
|
1162
|
+
const durationMs = Date.now() - toolStartTime;
|
|
1163
|
+
renderToolResult({
|
|
1164
|
+
name: parsedJsonTool.tool,
|
|
1165
|
+
args: parsedJsonTool.args,
|
|
1166
|
+
result: toolResult.result,
|
|
1167
|
+
durationMs,
|
|
1168
|
+
success: !String(toolResult.result).startsWith('[PLAN RESTRICTION') && !String(toolResult.result).startsWith('[ACTION BLOCKED'),
|
|
1169
|
+
});
|
|
1170
|
+
const jsonResStr = String(toolResult.result || '');
|
|
1171
|
+
let cappedJsonResult = jsonResStr;
|
|
1172
|
+
if (jsonResStr.length > 800) {
|
|
1173
|
+
try {
|
|
1174
|
+
const origBytes = Buffer.byteLength(jsonResStr, 'utf-8');
|
|
1175
|
+
const cdp = compress(Buffer.from(jsonResStr, 'utf-8'), LEVEL_FAST);
|
|
1176
|
+
const ratio = (origBytes / Math.max(1, cdp.length)).toFixed(1);
|
|
1177
|
+
cappedJsonResult = jsonResStr.slice(0, 800) + `\n... [Codrop: ${origBytes}B compressed to ${cdp.length}B (${ratio}x ratio) for LLM context]`;
|
|
1178
|
+
}
|
|
1179
|
+
catch {
|
|
1180
|
+
cappedJsonResult = jsonResStr.slice(0, 800) + '\n... [output truncated for token efficiency]';
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
this.historyMessages.push({
|
|
1184
|
+
role: 'user',
|
|
1185
|
+
content: `Tool Execution Result (${parsedJsonTool.tool}):\n${cappedJsonResult}`,
|
|
1186
|
+
});
|
|
1187
|
+
this.handleToolConsecutiveTracking(parsedJsonTool.tool, parsedJsonTool.args, step);
|
|
1188
|
+
// Automatic immediate completion for simple app actions (open/close/switch window)
|
|
1189
|
+
if (simpleAppCheck.isSimple && (parsedJsonTool.tool === 'computer_open_app' || parsedJsonTool.tool === 'computer_close_app' || parsedJsonTool.tool === 'computer_switch_window')) {
|
|
1190
|
+
let callSucceeded = false;
|
|
1191
|
+
try {
|
|
1192
|
+
const parsed = JSON.parse(toolResult.result);
|
|
1193
|
+
callSucceeded = parsed.success !== false;
|
|
1194
|
+
}
|
|
1195
|
+
catch {
|
|
1196
|
+
callSucceeded = !toolResult.result.toLowerCase().includes('failed') && !toolResult.result.toLowerCase().includes('error');
|
|
1197
|
+
}
|
|
1198
|
+
if (callSucceeded) {
|
|
1199
|
+
const appName = simpleAppCheck.appName || 'application';
|
|
1200
|
+
const actionVerb = simpleAppCheck.action === 'close' ? 'closed' : (simpleAppCheck.action === 'switch' ? 'focused' : 'launched');
|
|
1201
|
+
finalSummary = `I've ${actionVerb} ${appName} for you. It's ready on your screen!`;
|
|
1202
|
+
safeNote(chalk.green(` ${finalSummary}`), 'Task Completed');
|
|
1036
1203
|
saveAgentSession({
|
|
1037
1204
|
goal: userGoal,
|
|
1038
1205
|
summary: finalSummary,
|
|
1039
|
-
modifiedFiles:
|
|
1206
|
+
modifiedFiles: [],
|
|
1040
1207
|
});
|
|
1041
1208
|
return { success: true, summary: finalSummary };
|
|
1042
1209
|
}
|
|
1043
1210
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
}
|
|
1047
|
-
// Check text response or fallback JSON tool call
|
|
1048
|
-
stepSpinner.stop(chalk.blue(`Step ${step} thinking complete.`));
|
|
1049
|
-
const parsedJsonTool = this.extractJsonToolCall(textContent);
|
|
1050
|
-
if (parsedJsonTool) {
|
|
1051
|
-
const toolResult = await this.dispatchToolCall(parsedJsonTool.tool, parsedJsonTool.args);
|
|
1052
|
-
const jsonResStr = String(toolResult.result || '');
|
|
1053
|
-
let cappedJsonResult = jsonResStr;
|
|
1054
|
-
if (jsonResStr.length > 800) {
|
|
1055
|
-
try {
|
|
1056
|
-
const origBytes = Buffer.byteLength(jsonResStr, 'utf-8');
|
|
1057
|
-
const cdp = compress(Buffer.from(jsonResStr, 'utf-8'), LEVEL_FAST);
|
|
1058
|
-
const ratio = (origBytes / Math.max(1, cdp.length)).toFixed(1);
|
|
1059
|
-
cappedJsonResult = jsonResStr.slice(0, 800) + `\n... [Codrop: ${origBytes}B compressed to ${cdp.length}B (${ratio}x ratio) for LLM context]`;
|
|
1060
|
-
}
|
|
1061
|
-
catch {
|
|
1062
|
-
cappedJsonResult = jsonResStr.slice(0, 800) + '\n... [output truncated for token efficiency]';
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
this.historyMessages.push({
|
|
1066
|
-
role: 'user',
|
|
1067
|
-
content: `Tool Execution Result (${parsedJsonTool.tool}):\n${cappedJsonResult}`,
|
|
1068
|
-
});
|
|
1069
|
-
this.handleToolConsecutiveTracking(parsedJsonTool.tool, parsedJsonTool.args, step);
|
|
1070
|
-
// Automatic immediate completion for simple app actions (open/close/switch window)
|
|
1071
|
-
if (simpleAppCheck.isSimple && (parsedJsonTool.tool === 'computer_open_app' || parsedJsonTool.tool === 'computer_close_app' || parsedJsonTool.tool === 'computer_switch_window')) {
|
|
1072
|
-
let callSucceeded = false;
|
|
1073
|
-
try {
|
|
1074
|
-
const parsed = JSON.parse(toolResult.result);
|
|
1075
|
-
callSucceeded = parsed.success !== false;
|
|
1076
|
-
}
|
|
1077
|
-
catch {
|
|
1078
|
-
callSucceeded = !toolResult.result.toLowerCase().includes('failed') && !toolResult.result.toLowerCase().includes('error');
|
|
1079
|
-
}
|
|
1080
|
-
if (callSucceeded) {
|
|
1081
|
-
const appName = simpleAppCheck.appName || 'application';
|
|
1082
|
-
const actionVerb = simpleAppCheck.action === 'close' ? 'closed' : (simpleAppCheck.action === 'switch' ? 'focused' : 'launched');
|
|
1083
|
-
finalSummary = `I've ${actionVerb} ${appName} for you. It's ready on your screen!`;
|
|
1084
|
-
safeNote(chalk.green(` ${finalSummary}`), 'Task Completed');
|
|
1211
|
+
if (parsedJsonTool.tool === 'task_completed') {
|
|
1212
|
+
finalSummary = parsedJsonTool.args?.summary || toolResult.result;
|
|
1085
1213
|
saveAgentSession({
|
|
1086
1214
|
goal: userGoal,
|
|
1087
1215
|
summary: finalSummary,
|
|
1088
|
-
modifiedFiles:
|
|
1216
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1089
1217
|
});
|
|
1090
1218
|
return { success: true, summary: finalSummary };
|
|
1091
1219
|
}
|
|
1220
|
+
continue;
|
|
1092
1221
|
}
|
|
1093
|
-
if (
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1222
|
+
if (textContent.trim()) {
|
|
1223
|
+
let cleanedThought = textContent
|
|
1224
|
+
.replace(/[\u0600-\u06FF\u0750-\u077F\uAC00-\uD7AF\u3040-\u30FF\u4E00-\u9FFF\u0D80-\u0DFF]+/g, '')
|
|
1225
|
+
.trim();
|
|
1226
|
+
if (!cleanedThought || cleanedThought.length < 5) {
|
|
1227
|
+
cleanedThought = 'Analyzing codebase files and executing next tool operation...';
|
|
1228
|
+
}
|
|
1229
|
+
safeNote(renderMarkdown(cleanedThought), ` Scout Agent Thought (Step ${step})`);
|
|
1230
|
+
const lowerText = textContent.toLowerCase().trim();
|
|
1231
|
+
const isExplicitCompletion = lowerText.includes('task is complete') ||
|
|
1232
|
+
lowerText.includes('task complete') ||
|
|
1233
|
+
lowerText.includes('goal completed') ||
|
|
1234
|
+
lowerText.includes('goal is completed') ||
|
|
1235
|
+
lowerText.includes('all tasks completed') ||
|
|
1236
|
+
lowerText.includes('i have completed') ||
|
|
1237
|
+
lowerText.includes('no further changes needed') ||
|
|
1238
|
+
lowerText.includes('the fix is complete') ||
|
|
1239
|
+
lowerText.includes('has been created') ||
|
|
1240
|
+
lowerText.includes('successfully created') ||
|
|
1241
|
+
lowerText.includes('created the file') ||
|
|
1242
|
+
lowerText.includes('file created') ||
|
|
1243
|
+
lowerText.includes('implementation complete') ||
|
|
1244
|
+
lowerText.includes('setup is complete') ||
|
|
1245
|
+
lowerText.includes('setup complete') ||
|
|
1246
|
+
lowerText.includes('instructions have been executed') ||
|
|
1247
|
+
lowerText.includes('work is complete');
|
|
1248
|
+
const isIntermediateThought = /^(?:the\s+user\s+wants|i\s+will|i'll|let\s+me|i\s+need\s+to|i\s+am\s+going\s+to|first|starting|next|looking\s+at|checking|we\s+need\s+to|we\s+will)\b/i.test(lowerText);
|
|
1249
|
+
const hasActionDirective = /\b(execute|run|setup|set\s+up|install|configure|create|make|write|edit|modify|fix|update|refactor|build|delete|remove|patch|implement|apply|download|save|deploy)\b/i.test(userGoal);
|
|
1250
|
+
const isInformationalGoal = !hasActionDirective && (/^(?:lookup|look\s*up|check|find|who|what|why|where|how|tell|show|explain|display|list|search|query|status|info|describe)\b/i.test(userGoal.trim()) ||
|
|
1251
|
+
!/\b(create|make|write|edit|modify|fix|update|refactor|build|delete|remove|patch|implement|execute|run|setup|set\s+up|install)\b/i.test(userGoal.trim()));
|
|
1252
|
+
const isAnsweringInformational = !isIntermediateThought && isInformationalGoal && textContent.trim().length > 20 && !lowerText.includes('error occurred') && !lowerText.includes('cannot complete');
|
|
1253
|
+
const isSummaryAfterModifications = !isIntermediateThought && this.modifiedFiles.size > 0 && !lowerText.includes('?') && textContent.trim().length > 30;
|
|
1254
|
+
if (isExplicitCompletion || isAnsweringInformational || isSummaryAfterModifications) {
|
|
1255
|
+
saveAgentSession({
|
|
1256
|
+
goal: userGoal,
|
|
1257
|
+
summary: textContent,
|
|
1258
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1259
|
+
});
|
|
1260
|
+
return { success: true, summary: textContent };
|
|
1261
|
+
}
|
|
1101
1262
|
}
|
|
1102
|
-
|
|
1263
|
+
// If assistant responded with text without calling tools, prompt it to execute tools to complete the goal
|
|
1264
|
+
this.historyMessages.push({
|
|
1265
|
+
role: 'user',
|
|
1266
|
+
content: 'You provided a text response but have not called any tools (write_file, edit_file, run_command, task_completed). Please execute necessary tool calls to complete the user goal, or invoke task_completed if finished.',
|
|
1267
|
+
});
|
|
1268
|
+
stepDurations.push(Date.now() - stepStartTime);
|
|
1103
1269
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
.
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
lowerText.includes('all tasks completed') ||
|
|
1118
|
-
lowerText.includes('i have completed') ||
|
|
1119
|
-
lowerText.includes('no further changes needed') ||
|
|
1120
|
-
lowerText.includes('the fix is complete') ||
|
|
1121
|
-
lowerText.includes('has been created') ||
|
|
1122
|
-
lowerText.includes('successfully created') ||
|
|
1123
|
-
lowerText.includes('created the file') ||
|
|
1124
|
-
lowerText.includes('file created') ||
|
|
1125
|
-
lowerText.includes('implementation complete') ||
|
|
1126
|
-
lowerText.includes('setup is complete') ||
|
|
1127
|
-
lowerText.includes('setup complete') ||
|
|
1128
|
-
lowerText.includes('instructions have been executed') ||
|
|
1129
|
-
lowerText.includes('work is complete');
|
|
1130
|
-
const isIntermediateThought = /^(?:the\s+user\s+wants|i\s+will|i'll|let\s+me|i\s+need\s+to|i\s+am\s+going\s+to|first|starting|next|looking\s+at|checking|we\s+need\s+to|we\s+will)\b/i.test(lowerText);
|
|
1131
|
-
const hasActionDirective = /\b(execute|run|setup|set\s+up|install|configure|create|make|write|edit|modify|fix|update|refactor|build|delete|remove|patch|implement|apply|download|save|deploy)\b/i.test(userGoal);
|
|
1132
|
-
const isInformationalGoal = !hasActionDirective && (/^(?:lookup|look\s*up|check|find|who|what|why|where|how|tell|show|explain|display|list|search|query|status|info|describe)\b/i.test(userGoal.trim()) ||
|
|
1133
|
-
!/\b(create|make|write|edit|modify|fix|update|refactor|build|delete|remove|patch|implement|execute|run|setup|set\s+up|install)\b/i.test(userGoal.trim()));
|
|
1134
|
-
const isAnsweringInformational = !isIntermediateThought && isInformationalGoal && textContent.trim().length > 20 && !lowerText.includes('error occurred') && !lowerText.includes('cannot complete');
|
|
1135
|
-
const isSummaryAfterModifications = !isIntermediateThought && this.modifiedFiles.size > 0 && !lowerText.includes('?') && textContent.trim().length > 30;
|
|
1136
|
-
if (isExplicitCompletion || isAnsweringInformational || isSummaryAfterModifications) {
|
|
1137
|
-
saveAgentSession({
|
|
1138
|
-
goal: userGoal,
|
|
1139
|
-
summary: textContent,
|
|
1140
|
-
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1141
|
-
});
|
|
1142
|
-
return { success: true, summary: textContent };
|
|
1270
|
+
catch (err) {
|
|
1271
|
+
stepDurations.push(Date.now() - stepStartTime);
|
|
1272
|
+
if (isQuotaExceededError(err)) {
|
|
1273
|
+
stepSpinner.stop(chalk.red(`Oops! it\'s not you, it\'s us`));
|
|
1274
|
+
safeNote(`${chalk.bold.red(`Something went wrong in (Step ${step}), please try again in a moment`)}\n\n` +
|
|
1275
|
+
`${chalk.yellow('The configured AI provider has reached its API usage limit or rate cap.')}\n` +
|
|
1276
|
+
`${chalk.dim('This is separate from your Scout credit balance shown by `scout quota`.')}\n\n` +
|
|
1277
|
+
`${chalk.bold.cyan(' Please come back and try again in a few hours (or check back later today).')}\n\n` +
|
|
1278
|
+
`${chalk.dim('Scout Agent session has ended gracefully to protect remaining workflow.')}`, '️ API Quota Limit Reached');
|
|
1279
|
+
return {
|
|
1280
|
+
success: false,
|
|
1281
|
+
summary: 'Something went wrong, please try again in a moment.',
|
|
1282
|
+
};
|
|
1143
1283
|
}
|
|
1284
|
+
stepSpinner.stop(chalk.red(`Step ${step} execution error: ${err?.message || String(err)}`));
|
|
1285
|
+
this.historyMessages.push({
|
|
1286
|
+
role: 'user',
|
|
1287
|
+
content: `Error in previous turn: ${err?.message || String(err)}. Please try alternative steps or call tools.`,
|
|
1288
|
+
});
|
|
1144
1289
|
}
|
|
1145
|
-
//
|
|
1146
|
-
this.
|
|
1147
|
-
role: 'user',
|
|
1148
|
-
content: 'You provided a text response but have not called any tools (write_file, edit_file, run_command, task_completed). Please execute necessary tool calls to complete the user goal, or invoke task_completed if finished.',
|
|
1149
|
-
});
|
|
1150
|
-
stepDurations.push(Date.now() - stepStartTime);
|
|
1151
|
-
}
|
|
1152
|
-
catch (err) {
|
|
1153
|
-
stepDurations.push(Date.now() - stepStartTime);
|
|
1154
|
-
if (isQuotaExceededError(err)) {
|
|
1155
|
-
stepSpinner.stop(chalk.red(`Oops! it\'s not you, it\'s us`));
|
|
1156
|
-
safeNote(`${chalk.bold.red(`Something went wrong in (Step ${step}), please try again in a moment`)}\n\n` +
|
|
1157
|
-
`${chalk.yellow('The configured AI provider has reached its API usage limit or rate cap.')}\n` +
|
|
1158
|
-
`${chalk.dim('This is separate from your Scout credit balance shown by `scout quota`.')}\n\n` +
|
|
1159
|
-
`${chalk.bold.cyan(' Please come back and try again in a few hours (or check back later today).')}\n\n` +
|
|
1160
|
-
`${chalk.dim('Scout Agent session has ended gracefully to protect remaining workflow.')}`, '️ API Quota Limit Reached');
|
|
1161
|
-
return {
|
|
1162
|
-
success: false,
|
|
1163
|
-
summary: 'Something went wrong, please try again in a moment.',
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
stepSpinner.stop(chalk.red(`Step ${step} execution error: ${err?.message || String(err)}`));
|
|
1167
|
-
this.historyMessages.push({
|
|
1168
|
-
role: 'user',
|
|
1169
|
-
content: `Error in previous turn: ${err?.message || String(err)}. Please try alternative steps or call tools.`,
|
|
1170
|
-
});
|
|
1171
|
-
}
|
|
1172
|
-
// Step limit check: Auto-extend by +15 steps ONLY for Pro & Enterprise (up to 60 max threshold)
|
|
1173
|
-
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
1174
|
-
if (step >= this.maxSteps) {
|
|
1175
|
-
if (isPaid && this.maxSteps < 60) {
|
|
1176
|
-
this.maxSteps += 15;
|
|
1177
|
-
safeNote(chalk.bold.yellow(`⚡ Step limit reached while task is in progress. Automatically extending execution by +15 extra steps (New Max Limit: ${this.maxSteps})...`), ' Auto Extra Steps Extension');
|
|
1178
|
-
}
|
|
1179
|
-
else if (!isPaid) {
|
|
1290
|
+
// Step limit check: Free tier only. Pro and Enterprise have unlimited steps.
|
|
1291
|
+
if (!isPaid && step >= this.maxSteps) {
|
|
1180
1292
|
safeNote(chalk.bold.yellow(`⚡ Free Tier limit reached (${this.maxSteps} steps).\nFree developers can perform basic code editing, creating files/folders, and running commands.\nTo unlock unlimited autonomous multi-step loops and scratchpad reasoning, upgrade to Pro at https://scout.frontterrain.com/plans or run 'scout dashboard'.`), ' Free Tier Step Limit');
|
|
1181
1293
|
break;
|
|
1182
1294
|
}
|
|
1183
1295
|
}
|
|
1296
|
+
return {
|
|
1297
|
+
success: false,
|
|
1298
|
+
summary: isPaid
|
|
1299
|
+
? `Task ended after ${step} iteration steps. Modified files: ${Array.from(this.modifiedFiles).join(', ')}`
|
|
1300
|
+
: `Reached max iteration steps limit (${this.maxSteps}). Modified files: ${Array.from(this.modifiedFiles).join(', ')}`,
|
|
1301
|
+
};
|
|
1302
|
+
}
|
|
1303
|
+
finally {
|
|
1304
|
+
interruptManager.detach();
|
|
1184
1305
|
}
|
|
1185
|
-
return {
|
|
1186
|
-
success: false,
|
|
1187
|
-
summary: `Reached max iteration steps limit (${this.maxSteps}). Modified files: ${Array.from(this.modifiedFiles).join(', ')}`,
|
|
1188
|
-
};
|
|
1189
1306
|
}
|
|
1190
1307
|
extractJsonToolCall(content) {
|
|
1191
1308
|
if (!content)
|
|
@@ -1206,7 +1323,7 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1206
1323
|
'read_file', 'write_file', 'edit_file', 'run_command', 'list_dir',
|
|
1207
1324
|
'grep_search', 'glob_search', 'tree_view', 'file_info', 'multi_edit_file',
|
|
1208
1325
|
'fetch_url', 'git_diff',
|
|
1209
|
-
'ask_user', 'speak_text', 'task_completed', 'scratchpad',
|
|
1326
|
+
'ask_user', 'speak_text', 'delegate_subtask', 'task_completed', 'scratchpad',
|
|
1210
1327
|
];
|
|
1211
1328
|
for (const toolName of knownTools) {
|
|
1212
1329
|
const tagRegex = new RegExp(`<${toolName}>([\\s\\S]*?)<\\/${toolName}>`, 'i');
|
|
@@ -1306,16 +1423,16 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1306
1423
|
actionDesc = `Inspecting visible desktop UI`;
|
|
1307
1424
|
else if (name === 'computer_diagnostic')
|
|
1308
1425
|
actionDesc = `Running computer control diagnostics`;
|
|
1426
|
+
else if (name === 'delegate_subtask')
|
|
1427
|
+
actionDesc = `Delegating subtask to [${args.role || 'subagent'}]: ${String(args.task || '').slice(0, 40)}`;
|
|
1309
1428
|
else if (name === 'computer')
|
|
1310
1429
|
actionDesc = `Computer action: ${args.action || 'action'}`;
|
|
1311
1430
|
else if (name === 'scratchpad')
|
|
1312
1431
|
actionDesc = `Scratchpad (${args.action || 'read'})`;
|
|
1313
1432
|
else if (name === 'task_completed')
|
|
1314
1433
|
actionDesc = `Task finished`;
|
|
1315
|
-
|
|
1316
|
-
const
|
|
1317
|
-
console.log(`\n${badge}${descText} ${chalk.dim(JSON.stringify(args))}`);
|
|
1318
|
-
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
1434
|
+
renderToolCall({ name, args, step: this.currentStep });
|
|
1435
|
+
const isPaid = this.mockMode || isProOrHigher(this.userTier);
|
|
1319
1436
|
// 1. Scratchpad Feature Gating (Pro & Enterprise only)
|
|
1320
1437
|
if (name === 'scratchpad') {
|
|
1321
1438
|
if (!isPaid) {
|
|
@@ -1326,41 +1443,25 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1326
1443
|
}
|
|
1327
1444
|
const action = String(args.action || 'read').toLowerCase().trim();
|
|
1328
1445
|
const content = String(args.content || '');
|
|
1329
|
-
const ftDir = path.join(this.cwd, '.ft');
|
|
1330
|
-
const scratchPath = path.join(ftDir, 'scratchpad.md');
|
|
1331
|
-
fs.mkdirSync(ftDir, { recursive: true });
|
|
1332
|
-
let currentScratch = '';
|
|
1333
|
-
if (fs.existsSync(scratchPath)) {
|
|
1334
|
-
try {
|
|
1335
|
-
currentScratch = fs.readFileSync(scratchPath, 'utf-8');
|
|
1336
|
-
}
|
|
1337
|
-
catch { }
|
|
1338
|
-
}
|
|
1339
1446
|
if (action === 'write') {
|
|
1340
|
-
|
|
1341
|
-
try {
|
|
1342
|
-
fs.writeFileSync(path.join(ftDir, 'scratchpad.cdp'), compress(Buffer.from(content, 'utf-8'), LEVEL_FAST));
|
|
1343
|
-
}
|
|
1344
|
-
catch { }
|
|
1447
|
+
this.agentWorkspace.setScratchpad(content);
|
|
1345
1448
|
this.taskStateManager.addObservation(name, `Scratchpad updated (${content.length} chars, Codrop .cdp archived)`, true);
|
|
1346
1449
|
return { result: `Scratchpad updated successfully:\n${content}` };
|
|
1347
1450
|
}
|
|
1348
1451
|
else if (action === 'append') {
|
|
1452
|
+
const currentScratch = this.agentWorkspace.getScratchpad();
|
|
1349
1453
|
const updated = currentScratch ? `${currentScratch}\n\n---\n<!-- ${new Date().toISOString()} -->\n${content}` : content;
|
|
1350
|
-
|
|
1351
|
-
try {
|
|
1352
|
-
fs.writeFileSync(path.join(ftDir, 'scratchpad.cdp'), compress(Buffer.from(updated, 'utf-8'), LEVEL_FAST));
|
|
1353
|
-
}
|
|
1354
|
-
catch { }
|
|
1454
|
+
this.agentWorkspace.setScratchpad(updated);
|
|
1355
1455
|
this.taskStateManager.addObservation(name, `Appended to scratchpad (${content.length} chars, Codrop .cdp archived)`, true);
|
|
1356
1456
|
return { result: `Appended to scratchpad. Current contents:\n${updated}` };
|
|
1357
1457
|
}
|
|
1358
1458
|
else if (action === 'clear') {
|
|
1359
|
-
|
|
1459
|
+
this.agentWorkspace.clearScratchpad();
|
|
1360
1460
|
this.taskStateManager.addObservation(name, `Cleared scratchpad`, true);
|
|
1361
1461
|
return { result: `Scratchpad cleared.` };
|
|
1362
1462
|
}
|
|
1363
1463
|
else {
|
|
1464
|
+
const currentScratch = this.agentWorkspace.getScratchpad();
|
|
1364
1465
|
this.taskStateManager.addObservation(name, `Read scratchpad (${currentScratch.length} chars)`, true);
|
|
1365
1466
|
return { result: currentScratch ? `Scratchpad Contents:\n${currentScratch}` : `Scratchpad is currently empty.` };
|
|
1366
1467
|
}
|
|
@@ -1486,6 +1587,9 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1486
1587
|
else if (action === 'diagnostic') {
|
|
1487
1588
|
name = 'computer_diagnostic';
|
|
1488
1589
|
}
|
|
1590
|
+
else if (action === 'wait') {
|
|
1591
|
+
name = 'computer_wait';
|
|
1592
|
+
}
|
|
1489
1593
|
}
|
|
1490
1594
|
// Tier Gating Enforcement
|
|
1491
1595
|
if (!isPaid) {
|
|
@@ -1504,6 +1608,11 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1504
1608
|
this.taskStateManager.addObservation(name, upgradeMsg, false);
|
|
1505
1609
|
return { result: upgradeMsg };
|
|
1506
1610
|
}
|
|
1611
|
+
if (name === 'delegate_subtask') {
|
|
1612
|
+
const upgradeMsg = `[PLAN RESTRICTION]: Autonomous Multi-Agent Swarm Delegation is exclusively available to Pro and Enterprise plan subscribers. Free tier developers can perform code editing, creating files/folders, and running commands. Upgrade to Pro at https://scout.frontterrain.com to unlock autonomous subagents.`;
|
|
1613
|
+
this.taskStateManager.addObservation(name, upgradeMsg, false);
|
|
1614
|
+
return { result: upgradeMsg };
|
|
1615
|
+
}
|
|
1507
1616
|
}
|
|
1508
1617
|
switch (name) {
|
|
1509
1618
|
case 'read_file': {
|
|
@@ -1563,6 +1672,10 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1563
1672
|
this.backups.push({ filePath: targetPath, originalContent: '__FT_WAS_NEW__' });
|
|
1564
1673
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
1565
1674
|
}
|
|
1675
|
+
try {
|
|
1676
|
+
CheckpointManager.getInstance(this.cwd).createCheckpoint(`Step ${this.currentStep}: write_file ${targetPath}`, [targetPath], this.currentStep, this.taskStateManager.getCurrentState().taskId);
|
|
1677
|
+
}
|
|
1678
|
+
catch { }
|
|
1566
1679
|
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
1567
1680
|
this.modifiedFiles.add(targetPath);
|
|
1568
1681
|
const oldLineCount = origContent !== '__FT_WAS_NEW__' ? origContent.split('\n').length : 0;
|
|
@@ -1618,6 +1731,10 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1618
1731
|
return { result: `Error editing "${targetPath}": ${replaceRes.error}` };
|
|
1619
1732
|
}
|
|
1620
1733
|
this.backups.push({ filePath: targetPath, originalContent: origContent });
|
|
1734
|
+
try {
|
|
1735
|
+
CheckpointManager.getInstance(this.cwd).createCheckpoint(`Step ${this.currentStep}: edit_file ${targetPath}`, [targetPath], this.currentStep, this.taskStateManager.getCurrentState().taskId);
|
|
1736
|
+
}
|
|
1737
|
+
catch { }
|
|
1621
1738
|
fs.writeFileSync(fullPath, replaceRes.updatedContent, 'utf-8');
|
|
1622
1739
|
this.modifiedFiles.add(targetPath);
|
|
1623
1740
|
const targetLines = target.split('\n').length;
|
|
@@ -1877,11 +1994,7 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1877
1994
|
}
|
|
1878
1995
|
case 'speak_text': {
|
|
1879
1996
|
const phrase = String(args.text || args.phrase || '').trim();
|
|
1880
|
-
|
|
1881
|
-
await speakText(phrase, { async: true });
|
|
1882
|
-
return { result: `Spoke text out loud: "${phrase}"` };
|
|
1883
|
-
}
|
|
1884
|
-
return { result: 'No text provided to speak.' };
|
|
1997
|
+
return { result: `[TTS DISABLED]: Text-to-speech audio synthesis has been disabled. Text displayed: "${phrase}"` };
|
|
1885
1998
|
}
|
|
1886
1999
|
case 'browser_navigate': {
|
|
1887
2000
|
const targetUrl = String(args.url || '').trim();
|
|
@@ -1953,35 +2066,6 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1953
2066
|
this.taskStateManager.addObservation('browser_scroll', res.output, res.success);
|
|
1954
2067
|
return { result: res.output };
|
|
1955
2068
|
}
|
|
1956
|
-
case 'scratchpad': {
|
|
1957
|
-
const action = String(args.action || 'read').toLowerCase().trim();
|
|
1958
|
-
const scratchpadPath = path.resolve(this.cwd, '.ft', 'scratchpad.md');
|
|
1959
|
-
fs.mkdirSync(path.dirname(scratchpadPath), { recursive: true });
|
|
1960
|
-
if (action === 'read') {
|
|
1961
|
-
const content = fs.existsSync(scratchpadPath) ? fs.readFileSync(scratchpadPath, 'utf-8') : '(Scratchpad is empty)';
|
|
1962
|
-
this.taskStateManager.addObservation('scratchpad', 'Read scratchpad content', true);
|
|
1963
|
-
return { result: `Scratchpad Content:\n${content || '(empty)'}` };
|
|
1964
|
-
}
|
|
1965
|
-
else if (action === 'write') {
|
|
1966
|
-
const newContent = String(args.content || '');
|
|
1967
|
-
fs.writeFileSync(scratchpadPath, newContent, 'utf-8');
|
|
1968
|
-
this.taskStateManager.addObservation('scratchpad', `Wrote scratchpad (${newContent.length} chars)`, true);
|
|
1969
|
-
return { result: `Scratchpad updated (${newContent.length} chars).` };
|
|
1970
|
-
}
|
|
1971
|
-
else if (action === 'append') {
|
|
1972
|
-
const appendContent = String(args.content || '');
|
|
1973
|
-
fs.appendFileSync(scratchpadPath, `\n${appendContent}`, 'utf-8');
|
|
1974
|
-
this.taskStateManager.addObservation('scratchpad', `Appended to scratchpad (${appendContent.length} chars)`, true);
|
|
1975
|
-
return { result: `Appended to scratchpad (${appendContent.length} chars).` };
|
|
1976
|
-
}
|
|
1977
|
-
else if (action === 'clear') {
|
|
1978
|
-
if (fs.existsSync(scratchpadPath))
|
|
1979
|
-
fs.unlinkSync(scratchpadPath);
|
|
1980
|
-
this.taskStateManager.addObservation('scratchpad', 'Cleared scratchpad', true);
|
|
1981
|
-
return { result: 'Scratchpad cleared.' };
|
|
1982
|
-
}
|
|
1983
|
-
return { result: `Unknown scratchpad action: "${action}". Supported: read, write, append, clear.` };
|
|
1984
|
-
}
|
|
1985
2069
|
case 'computer_screenshot': {
|
|
1986
2070
|
const res = await this.computerController.screenshot();
|
|
1987
2071
|
if (res.path) {
|
|
@@ -2244,6 +2328,39 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
2244
2328
|
};
|
|
2245
2329
|
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
2246
2330
|
}
|
|
2331
|
+
case 'computer': {
|
|
2332
|
+
return {
|
|
2333
|
+
result: JSON.stringify({
|
|
2334
|
+
success: false,
|
|
2335
|
+
error: 'InvalidComputerAction',
|
|
2336
|
+
message: `Unknown computer action: "${args.action}". Supported actions: click, right_click, double_click, move, mouse_down, mouse_up, drag, scroll, screenshot, type, key_press, hotkey, open_app, close_app, switch_window, inspect_ui, diagnostic, wait.`
|
|
2337
|
+
}, null, 2)
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
case 'delegate_subtask': {
|
|
2341
|
+
const role = (args.role || 'general');
|
|
2342
|
+
const task = String(args.task || '').trim();
|
|
2343
|
+
const context = args.context ? String(args.context) : undefined;
|
|
2344
|
+
if (!task) {
|
|
2345
|
+
return { result: JSON.stringify({ success: false, error: 'No task specified for subagent delegation.' }) };
|
|
2346
|
+
}
|
|
2347
|
+
const { SubagentManager } = await import('./subagents/subagentManager.js');
|
|
2348
|
+
const subagentMgr = SubagentManager.getInstance();
|
|
2349
|
+
const subRes = await subagentMgr.delegate({
|
|
2350
|
+
role,
|
|
2351
|
+
task,
|
|
2352
|
+
context,
|
|
2353
|
+
cwd: this.cwd,
|
|
2354
|
+
tier: this.userTier,
|
|
2355
|
+
});
|
|
2356
|
+
if (subRes.modifiedFiles && subRes.modifiedFiles.length > 0) {
|
|
2357
|
+
for (const f of subRes.modifiedFiles) {
|
|
2358
|
+
this.modifiedFiles.add(f);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
this.taskStateManager.addObservation('delegate_subtask', `[Subagent ${role.toUpperCase()} Result]: ${subRes.summary}`, subRes.success);
|
|
2362
|
+
return { result: JSON.stringify(subRes, null, 2) };
|
|
2363
|
+
}
|
|
2247
2364
|
case 'task_completed': {
|
|
2248
2365
|
const summaryStr = String(args.summary || 'Task completed successfully.');
|
|
2249
2366
|
if (this.executionIntent?.targetDestination === 'Desktop' && this.executionIntent?.targetArtifactName) {
|