ft-scout 9.4.5 → 9.4.7
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 +7 -7
- package/README.md +2 -2
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +62 -1
- package/bin/src/commands/agent.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/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 +161 -40
- 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 +3 -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 +76 -29
- 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 +41 -9
- 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/index.js +50 -4
- package/bin/src/index.js.map +1 -1
- package/bin/src/mcp/client.js +1 -1
- package/bin/src/server/server.js +2 -2
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/qrBridge.js +1 -1
- package/package.json +2 -1
- package/web/app.js +4 -3
- package/web/styles.css +2 -2
- package/artifacts/scout_drone_3d.glb +0 -0
- package/artifacts/scout_drone_3d_render.png +0 -0
|
@@ -22,6 +22,8 @@ import { AgentRuntime } from './runtime/index.js';
|
|
|
22
22
|
import { loadAuthConfig, isProOrHigher } from '../utils/auth.js';
|
|
23
23
|
import { getMCPManager } from '../mcp/index.js';
|
|
24
24
|
import { writeTelemetry } from '../utils/qrBridge.js';
|
|
25
|
+
import { compress, LEVEL_FAST } from 'codrop';
|
|
26
|
+
import { CheckpointManager } from './checkpoints/checkpointManager.js';
|
|
25
27
|
export { AgentRuntime, AgentWorkspace, inferExecutionIntent, resolveDesktopPath, SystemComputerController, MockComputerController };
|
|
26
28
|
const execAsync = promisify(exec);
|
|
27
29
|
export function robustSnippetReplace(origContent, target, replacement) {
|
|
@@ -292,6 +294,29 @@ export const AGENT_TOOLS = [
|
|
|
292
294
|
required: ['text'],
|
|
293
295
|
},
|
|
294
296
|
},
|
|
297
|
+
{
|
|
298
|
+
name: 'delegate_subtask',
|
|
299
|
+
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.',
|
|
300
|
+
parameters: {
|
|
301
|
+
type: 'object',
|
|
302
|
+
properties: {
|
|
303
|
+
role: {
|
|
304
|
+
type: 'string',
|
|
305
|
+
enum: ['architect', 'coder', 'tester', 'reviewer', 'general'],
|
|
306
|
+
description: 'Specialist role for the subagent (architect, coder, tester, reviewer, general)',
|
|
307
|
+
},
|
|
308
|
+
task: {
|
|
309
|
+
type: 'string',
|
|
310
|
+
description: 'The specific subtask instruction or goal for the subagent to complete',
|
|
311
|
+
},
|
|
312
|
+
context: {
|
|
313
|
+
type: 'string',
|
|
314
|
+
description: 'Relevant file paths, error messages, or architectural context to provide to the subagent',
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
required: ['role', 'task'],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
295
320
|
{
|
|
296
321
|
name: 'task_completed',
|
|
297
322
|
description: 'Signal that the user goal has been fully completed with the full answer, results, and summary.',
|
|
@@ -402,10 +427,10 @@ export class AgentExecutionLoop {
|
|
|
402
427
|
this.mockMode = Boolean(options?.mockMode);
|
|
403
428
|
this.isAudioMode = Boolean(options?.isAudioMode);
|
|
404
429
|
this.userTier = (options?.tier || loadAuthConfig().currentTier || 'free').toLowerCase();
|
|
405
|
-
// Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise.
|
|
430
|
+
// Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise (unlimited steps).
|
|
406
431
|
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
407
|
-
const requestedSteps = options?.maxSteps
|
|
408
|
-
this.maxSteps = isPaid ? requestedSteps : Math.min(requestedSteps, 5);
|
|
432
|
+
const requestedSteps = options?.maxSteps;
|
|
433
|
+
this.maxSteps = isPaid ? (requestedSteps || Infinity) : Math.min(requestedSteps || 5, 5);
|
|
409
434
|
this.taskStateManager = new TaskStateManager(options?.taskId, '', this.cwd);
|
|
410
435
|
this.computerController = options?.computerController || (this.mockMode ? new MockComputerController() : new SystemComputerController(this.cwd));
|
|
411
436
|
this.browserController = options?.browserController || new SemanticBrowserController(this.cwd);
|
|
@@ -417,6 +442,9 @@ export class AgentExecutionLoop {
|
|
|
417
442
|
getUserTier() {
|
|
418
443
|
return this.userTier;
|
|
419
444
|
}
|
|
445
|
+
isPaidTier() {
|
|
446
|
+
return this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
447
|
+
}
|
|
420
448
|
getExecutionIntent() {
|
|
421
449
|
return this.executionIntent;
|
|
422
450
|
}
|
|
@@ -581,7 +609,7 @@ export class AgentExecutionLoop {
|
|
|
581
609
|
}
|
|
582
610
|
}
|
|
583
611
|
systemPrompt() {
|
|
584
|
-
const filesList = getDirectoryFiles(this.cwd).slice(0,
|
|
612
|
+
const filesList = getDirectoryFiles(this.cwd).slice(0, 10);
|
|
585
613
|
const intentInfo = this.executionIntent ? `
|
|
586
614
|
Task Execution Intent & Constraints:
|
|
587
615
|
• Required Applications: ${this.executionIntent.requiredApplications.length > 0 ? this.executionIntent.requiredApplications.join(', ') : 'None specified'}
|
|
@@ -590,9 +618,10 @@ Task Execution Intent & Constraints:
|
|
|
590
618
|
• Target Destination: ${this.executionIntent.targetDestination || 'Default'}
|
|
591
619
|
• Target Artifact: ${this.executionIntent.targetArtifactName || 'None'}
|
|
592
620
|
` : '';
|
|
593
|
-
const isPaid = this.
|
|
621
|
+
const isPaid = this.isPaidTier();
|
|
594
622
|
const tierDirective = isPaid ? `
|
|
595
623
|
PLAN TIER & CAPABILITIES: ${this.userTier.toUpperCase()} (FULL ACCESS UNLOCKED)
|
|
624
|
+
• Unlimited autonomous multi-step loops with no step limits.
|
|
596
625
|
• Autonomous Computer-Use, Browser Automation, and AI Scratchpad Reasoning are fully ENABLED.
|
|
597
626
|
• Use the 'scratchpad' tool to organize multi-step reasoning, break down complex goals, draft hypotheses, and track working memory across turns.
|
|
598
627
|
` : `
|
|
@@ -607,7 +636,7 @@ PLAN TIER & CAPABILITIES: DEVELOPER FREE (BASIC DEVELOPER OPS ONLY)
|
|
|
607
636
|
`;
|
|
608
637
|
const mcpRouting = getMCPManager().routeTools(this.currentGoal);
|
|
609
638
|
const mcpCapabilitiesSection = mcpRouting.capabilitiesSummary ? `\n${mcpRouting.capabilitiesSummary}\n` : '';
|
|
610
|
-
const recentSessionsMemory = getRecentSessionsSummary(
|
|
639
|
+
const recentSessionsMemory = getRecentSessionsSummary(1);
|
|
611
640
|
const sessionHistorySection = recentSessionsMemory ? `\n${recentSessionsMemory}\n` : '';
|
|
612
641
|
return `You are Scout Agent, an elite AI Principal Software Engineer & Autonomous Computer-Use Operator (developed by FrontTerrain).
|
|
613
642
|
|
|
@@ -671,7 +700,7 @@ CORE ARCHITECTURAL PRINCIPLES:
|
|
|
671
700
|
* Light: light_data.energy = 1000.0 (watts)
|
|
672
701
|
* Primitives: bpy.ops.mesh.primitive_uv_sphere_add, primitive_cylinder_add, primitive_cube_add
|
|
673
702
|
- Apply cel-shading or toon materials with vibrant anime colors matching the request (e.g. Genshin Impact aesthetic).
|
|
674
|
-
- Position camera and lighting
|
|
703
|
+
- Position camera and lighting. Immediately after your Blender script creates the 3D model (or after a single quick verification step), you MUST call task_completed with a complete description of the created 3D character and mesh hierarchy. Do NOT run unnecessary repeated polling turns.
|
|
675
704
|
|
|
676
705
|
When returning tool calls, use standard OpenAI function calling format or JSON tool call payload format:
|
|
677
706
|
\`\`\`json
|
|
@@ -820,9 +849,15 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
820
849
|
content: promptContent,
|
|
821
850
|
});
|
|
822
851
|
}
|
|
823
|
-
const
|
|
824
|
-
|
|
825
|
-
|
|
852
|
+
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
853
|
+
if (!isPaid && Number.isFinite(this.maxSteps)) {
|
|
854
|
+
const estTotalSecs = Math.round(this.maxSteps * 12);
|
|
855
|
+
const estTimeText = estTotalSecs >= 60 ? `~${(estTotalSecs / 60).toFixed(1)}m` : `~${estTotalSecs}s`;
|
|
856
|
+
console.log(chalk.gray(`Estimated completion time: ${estTimeText} (Free Tier Max ${this.maxSteps} steps)`));
|
|
857
|
+
}
|
|
858
|
+
else {
|
|
859
|
+
console.log(chalk.gray(`⚡ Autonomous execution loop: Unlimited steps active (${this.userTier.toUpperCase()} plan)`));
|
|
860
|
+
}
|
|
826
861
|
let step = 0;
|
|
827
862
|
let finalSummary = '';
|
|
828
863
|
const stepDurations = [];
|
|
@@ -837,16 +872,21 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
837
872
|
this.currentStep = step;
|
|
838
873
|
this.taskStateManager.startStep(step);
|
|
839
874
|
const stepStartTime = Date.now();
|
|
840
|
-
const avgStepMs = stepDurations.length > 0
|
|
841
|
-
? stepDurations.reduce((a, b) => a + b, 0) / stepDurations.length
|
|
842
|
-
: 12000;
|
|
843
|
-
const remainingSteps = (this.maxSteps - step + 1);
|
|
844
|
-
const estSecsLeft = Math.max(5, Math.round((remainingSteps * avgStepMs) / 1000));
|
|
845
|
-
const estLeftStr = estSecsLeft >= 60
|
|
846
|
-
? `${Math.floor(estSecsLeft / 60)}m ${estSecsLeft % 60}s`
|
|
847
|
-
: `${estSecsLeft}s`;
|
|
848
875
|
const stepSpinner = spinner();
|
|
849
|
-
|
|
876
|
+
if (isPaid && !Number.isFinite(this.maxSteps)) {
|
|
877
|
+
stepSpinner.start(chalk.cyan(`Scout is Working (Step ${step})`));
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
const avgStepMs = stepDurations.length > 0
|
|
881
|
+
? stepDurations.reduce((a, b) => a + b, 0) / stepDurations.length
|
|
882
|
+
: 12000;
|
|
883
|
+
const remainingSteps = (this.maxSteps - step + 1);
|
|
884
|
+
const estSecsLeft = Math.max(5, Math.round((remainingSteps * avgStepMs) / 1000));
|
|
885
|
+
const estLeftStr = estSecsLeft >= 60
|
|
886
|
+
? `${Math.floor(estSecsLeft / 60)}m ${estSecsLeft % 60}s`
|
|
887
|
+
: `${estSecsLeft}s`;
|
|
888
|
+
stepSpinner.start(chalk.cyan(`Scout is Working (Step ${step}/${this.maxSteps} • Est. completion: ~${estLeftStr} left)`));
|
|
889
|
+
}
|
|
850
890
|
try {
|
|
851
891
|
let response;
|
|
852
892
|
const mcpRouting = getMCPManager().routeTools(userGoal);
|
|
@@ -860,7 +900,7 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
860
900
|
}
|
|
861
901
|
return {
|
|
862
902
|
name: mt.originalName,
|
|
863
|
-
description: `[MCP: ${mt.serverName}] ${desc.length >
|
|
903
|
+
description: `[MCP: ${mt.serverName}] ${desc.length > 160 ? desc.slice(0, 157) + '...' : desc}`,
|
|
864
904
|
parameters: {
|
|
865
905
|
type: 'object',
|
|
866
906
|
properties: mt.inputSchema.properties || {},
|
|
@@ -871,7 +911,18 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
871
911
|
mcpServerName: mt.serverName,
|
|
872
912
|
};
|
|
873
913
|
});
|
|
874
|
-
const
|
|
914
|
+
const goalLower = (userGoal || '').toLowerCase();
|
|
915
|
+
const needsBrowser = goalLower.includes('browser') || goalLower.includes('website') || goalLower.includes('webpage') || goalLower.includes('http') || goalLower.includes('url') || goalLower.includes('scrape') || goalLower.includes('chrome');
|
|
916
|
+
const needsComputer = goalLower.includes('computer') || goalLower.includes('desktop') || goalLower.includes('mouse') || goalLower.includes('coordinate') || goalLower.includes('drag') || goalLower.includes('double click');
|
|
917
|
+
let baseTools = this.toolRegistry.getToolsForTier(this.userTier);
|
|
918
|
+
if (mcpToolDefs.length > 0 && !needsBrowser && !needsComputer) {
|
|
919
|
+
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']);
|
|
920
|
+
baseTools = baseTools.filter((t) => ESSENTIAL_BASE_TOOLS.has(t.name));
|
|
921
|
+
}
|
|
922
|
+
else if (!needsBrowser) {
|
|
923
|
+
baseTools = baseTools.filter((t) => !t.name.startsWith('browser_'));
|
|
924
|
+
}
|
|
925
|
+
const activeLoopTools = [...baseTools, ...mcpToolDefs];
|
|
875
926
|
try {
|
|
876
927
|
response = await callOpenAIWithRetry(async (model) => {
|
|
877
928
|
return await openai.chat.completions.create({
|
|
@@ -890,14 +941,15 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
890
941
|
if (errStr.includes('tool') || errStr.includes('400') || errStr.includes('not supported') || errStr.includes('reasoning')) {
|
|
891
942
|
// Model doesn't support native function calling inject tool-call formatting hint
|
|
892
943
|
// so extractJsonToolCall can parse the response as a structured tool invocation
|
|
944
|
+
const availableToolNames = activeLoopTools.map((t) => t.name).join(', ');
|
|
893
945
|
const toolHintMsg = {
|
|
894
946
|
role: 'user',
|
|
895
947
|
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:
|
|
896
948
|
\`\`\`json
|
|
897
949
|
{ "tool": "tool_name", "args": { ... } }
|
|
898
950
|
\`\`\`
|
|
899
|
-
Available tools:
|
|
900
|
-
You MUST output exactly ONE JSON code block per tool call. Do NOT describe what you would do in plain
|
|
951
|
+
Available tools: ${availableToolNames}
|
|
952
|
+
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!`,
|
|
901
953
|
};
|
|
902
954
|
const fallbackMessages = [...this.historyMessages, toolHintMsg];
|
|
903
955
|
response = await callOpenAIWithRetry(async (model) => {
|
|
@@ -964,10 +1016,24 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
964
1016
|
const toolResult = await this.dispatchToolCall(fnName, args);
|
|
965
1017
|
this.totalToolCalls++;
|
|
966
1018
|
this.syncTelemetry('running', `scout: [step ${step}] executed tool ${fnName}`);
|
|
1019
|
+
const toolResStr = String(toolResult.result || '');
|
|
1020
|
+
let cappedResult = toolResStr;
|
|
1021
|
+
if (toolResStr.length > 800) {
|
|
1022
|
+
try {
|
|
1023
|
+
const origBytes = Buffer.byteLength(toolResStr, 'utf-8');
|
|
1024
|
+
const cdp = compress(Buffer.from(toolResStr, 'utf-8'), LEVEL_FAST);
|
|
1025
|
+
const ratio = (origBytes / Math.max(1, cdp.length)).toFixed(1);
|
|
1026
|
+
cappedResult = toolResStr.slice(0, 800) + `\n... [Codrop: ${origBytes}B compressed to ${cdp.length}B (${ratio}x ratio) for LLM context]`;
|
|
1027
|
+
this.syncTelemetry('running', `scout: [codrop] compressed ${fnName} output (${ratio}x ratio)`);
|
|
1028
|
+
}
|
|
1029
|
+
catch {
|
|
1030
|
+
cappedResult = toolResStr.slice(0, 800) + '\n... [output truncated for token efficiency]';
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
967
1033
|
this.historyMessages.push({
|
|
968
1034
|
role: 'user',
|
|
969
1035
|
tool_call_id: tc.id,
|
|
970
|
-
content: `Tool Execution Result (${fnName}):\n${
|
|
1036
|
+
content: `Tool Execution Result (${fnName}):\n${cappedResult}`,
|
|
971
1037
|
});
|
|
972
1038
|
this.handleToolConsecutiveTracking(fnName, args, step);
|
|
973
1039
|
// Automatic immediate completion for simple app actions (open/close/switch window)
|
|
@@ -1022,9 +1088,22 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1022
1088
|
const parsedJsonTool = this.extractJsonToolCall(textContent);
|
|
1023
1089
|
if (parsedJsonTool) {
|
|
1024
1090
|
const toolResult = await this.dispatchToolCall(parsedJsonTool.tool, parsedJsonTool.args);
|
|
1091
|
+
const jsonResStr = String(toolResult.result || '');
|
|
1092
|
+
let cappedJsonResult = jsonResStr;
|
|
1093
|
+
if (jsonResStr.length > 800) {
|
|
1094
|
+
try {
|
|
1095
|
+
const origBytes = Buffer.byteLength(jsonResStr, 'utf-8');
|
|
1096
|
+
const cdp = compress(Buffer.from(jsonResStr, 'utf-8'), LEVEL_FAST);
|
|
1097
|
+
const ratio = (origBytes / Math.max(1, cdp.length)).toFixed(1);
|
|
1098
|
+
cappedJsonResult = jsonResStr.slice(0, 800) + `\n... [Codrop: ${origBytes}B compressed to ${cdp.length}B (${ratio}x ratio) for LLM context]`;
|
|
1099
|
+
}
|
|
1100
|
+
catch {
|
|
1101
|
+
cappedJsonResult = jsonResStr.slice(0, 800) + '\n... [output truncated for token efficiency]';
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1025
1104
|
this.historyMessages.push({
|
|
1026
1105
|
role: 'user',
|
|
1027
|
-
content: `Tool Execution Result (${parsedJsonTool.tool}):\n${
|
|
1106
|
+
content: `Tool Execution Result (${parsedJsonTool.tool}):\n${cappedJsonResult}`,
|
|
1028
1107
|
});
|
|
1029
1108
|
this.handleToolConsecutiveTracking(parsedJsonTool.tool, parsedJsonTool.args, step);
|
|
1030
1109
|
// Automatic immediate completion for simple app actions (open/close/switch window)
|
|
@@ -1129,22 +1208,17 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1129
1208
|
content: `Error in previous turn: ${err?.message || String(err)}. Please try alternative steps or call tools.`,
|
|
1130
1209
|
});
|
|
1131
1210
|
}
|
|
1132
|
-
// Step limit check:
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
this.maxSteps += 15;
|
|
1137
|
-
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');
|
|
1138
|
-
}
|
|
1139
|
-
else if (!isPaid) {
|
|
1140
|
-
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');
|
|
1141
|
-
break;
|
|
1142
|
-
}
|
|
1211
|
+
// Step limit check: Free tier only. Pro and Enterprise have unlimited steps.
|
|
1212
|
+
if (!isPaid && step >= this.maxSteps) {
|
|
1213
|
+
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');
|
|
1214
|
+
break;
|
|
1143
1215
|
}
|
|
1144
1216
|
}
|
|
1145
1217
|
return {
|
|
1146
1218
|
success: false,
|
|
1147
|
-
summary:
|
|
1219
|
+
summary: isPaid
|
|
1220
|
+
? `Task ended after ${step} iteration steps. Modified files: ${Array.from(this.modifiedFiles).join(', ')}`
|
|
1221
|
+
: `Reached max iteration steps limit (${this.maxSteps}). Modified files: ${Array.from(this.modifiedFiles).join(', ')}`,
|
|
1148
1222
|
};
|
|
1149
1223
|
}
|
|
1150
1224
|
extractJsonToolCall(content) {
|
|
@@ -1166,7 +1240,7 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1166
1240
|
'read_file', 'write_file', 'edit_file', 'run_command', 'list_dir',
|
|
1167
1241
|
'grep_search', 'glob_search', 'tree_view', 'file_info', 'multi_edit_file',
|
|
1168
1242
|
'fetch_url', 'git_diff',
|
|
1169
|
-
'ask_user', 'speak_text', 'task_completed', 'scratchpad',
|
|
1243
|
+
'ask_user', 'speak_text', 'delegate_subtask', 'task_completed', 'scratchpad',
|
|
1170
1244
|
];
|
|
1171
1245
|
for (const toolName of knownTools) {
|
|
1172
1246
|
const tagRegex = new RegExp(`<${toolName}>([\\s\\S]*?)<\\/${toolName}>`, 'i');
|
|
@@ -1266,6 +1340,8 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1266
1340
|
actionDesc = `Inspecting visible desktop UI`;
|
|
1267
1341
|
else if (name === 'computer_diagnostic')
|
|
1268
1342
|
actionDesc = `Running computer control diagnostics`;
|
|
1343
|
+
else if (name === 'delegate_subtask')
|
|
1344
|
+
actionDesc = `Delegating subtask to [${args.role || 'subagent'}]: ${String(args.task || '').slice(0, 40)}`;
|
|
1269
1345
|
else if (name === 'computer')
|
|
1270
1346
|
actionDesc = `Computer action: ${args.action || 'action'}`;
|
|
1271
1347
|
else if (name === 'scratchpad')
|
|
@@ -1298,13 +1374,21 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1298
1374
|
}
|
|
1299
1375
|
if (action === 'write') {
|
|
1300
1376
|
fs.writeFileSync(scratchPath, content, 'utf-8');
|
|
1301
|
-
|
|
1377
|
+
try {
|
|
1378
|
+
fs.writeFileSync(path.join(ftDir, 'scratchpad.cdp'), compress(Buffer.from(content, 'utf-8'), LEVEL_FAST));
|
|
1379
|
+
}
|
|
1380
|
+
catch { }
|
|
1381
|
+
this.taskStateManager.addObservation(name, `Scratchpad updated (${content.length} chars, Codrop .cdp archived)`, true);
|
|
1302
1382
|
return { result: `Scratchpad updated successfully:\n${content}` };
|
|
1303
1383
|
}
|
|
1304
1384
|
else if (action === 'append') {
|
|
1305
1385
|
const updated = currentScratch ? `${currentScratch}\n\n---\n<!-- ${new Date().toISOString()} -->\n${content}` : content;
|
|
1306
1386
|
fs.writeFileSync(scratchPath, updated, 'utf-8');
|
|
1307
|
-
|
|
1387
|
+
try {
|
|
1388
|
+
fs.writeFileSync(path.join(ftDir, 'scratchpad.cdp'), compress(Buffer.from(updated, 'utf-8'), LEVEL_FAST));
|
|
1389
|
+
}
|
|
1390
|
+
catch { }
|
|
1391
|
+
this.taskStateManager.addObservation(name, `Appended to scratchpad (${content.length} chars, Codrop .cdp archived)`, true);
|
|
1308
1392
|
return { result: `Appended to scratchpad. Current contents:\n${updated}` };
|
|
1309
1393
|
}
|
|
1310
1394
|
else if (action === 'clear') {
|
|
@@ -1456,6 +1540,11 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1456
1540
|
this.taskStateManager.addObservation(name, upgradeMsg, false);
|
|
1457
1541
|
return { result: upgradeMsg };
|
|
1458
1542
|
}
|
|
1543
|
+
if (name === 'delegate_subtask') {
|
|
1544
|
+
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.`;
|
|
1545
|
+
this.taskStateManager.addObservation(name, upgradeMsg, false);
|
|
1546
|
+
return { result: upgradeMsg };
|
|
1547
|
+
}
|
|
1459
1548
|
}
|
|
1460
1549
|
switch (name) {
|
|
1461
1550
|
case 'read_file': {
|
|
@@ -1515,6 +1604,10 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1515
1604
|
this.backups.push({ filePath: targetPath, originalContent: '__FT_WAS_NEW__' });
|
|
1516
1605
|
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
1517
1606
|
}
|
|
1607
|
+
try {
|
|
1608
|
+
CheckpointManager.getInstance(this.cwd).createCheckpoint(`Step ${this.currentStep}: write_file ${targetPath}`, [targetPath], this.currentStep, this.taskStateManager.getCurrentState().taskId);
|
|
1609
|
+
}
|
|
1610
|
+
catch { }
|
|
1518
1611
|
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
1519
1612
|
this.modifiedFiles.add(targetPath);
|
|
1520
1613
|
const oldLineCount = origContent !== '__FT_WAS_NEW__' ? origContent.split('\n').length : 0;
|
|
@@ -1570,6 +1663,10 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1570
1663
|
return { result: `Error editing "${targetPath}": ${replaceRes.error}` };
|
|
1571
1664
|
}
|
|
1572
1665
|
this.backups.push({ filePath: targetPath, originalContent: origContent });
|
|
1666
|
+
try {
|
|
1667
|
+
CheckpointManager.getInstance(this.cwd).createCheckpoint(`Step ${this.currentStep}: edit_file ${targetPath}`, [targetPath], this.currentStep, this.taskStateManager.getCurrentState().taskId);
|
|
1668
|
+
}
|
|
1669
|
+
catch { }
|
|
1573
1670
|
fs.writeFileSync(fullPath, replaceRes.updatedContent, 'utf-8');
|
|
1574
1671
|
this.modifiedFiles.add(targetPath);
|
|
1575
1672
|
const targetLines = target.split('\n').length;
|
|
@@ -2196,6 +2293,30 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
2196
2293
|
};
|
|
2197
2294
|
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
2198
2295
|
}
|
|
2296
|
+
case 'delegate_subtask': {
|
|
2297
|
+
const role = (args.role || 'general');
|
|
2298
|
+
const task = String(args.task || '').trim();
|
|
2299
|
+
const context = args.context ? String(args.context) : undefined;
|
|
2300
|
+
if (!task) {
|
|
2301
|
+
return { result: JSON.stringify({ success: false, error: 'No task specified for subagent delegation.' }) };
|
|
2302
|
+
}
|
|
2303
|
+
const { SubagentManager } = await import('./subagents/subagentManager.js');
|
|
2304
|
+
const subagentMgr = SubagentManager.getInstance();
|
|
2305
|
+
const subRes = await subagentMgr.delegate({
|
|
2306
|
+
role,
|
|
2307
|
+
task,
|
|
2308
|
+
context,
|
|
2309
|
+
cwd: this.cwd,
|
|
2310
|
+
tier: this.userTier,
|
|
2311
|
+
});
|
|
2312
|
+
if (subRes.modifiedFiles && subRes.modifiedFiles.length > 0) {
|
|
2313
|
+
for (const f of subRes.modifiedFiles) {
|
|
2314
|
+
this.modifiedFiles.add(f);
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
this.taskStateManager.addObservation('delegate_subtask', `[Subagent ${role.toUpperCase()} Result]: ${subRes.summary}`, subRes.success);
|
|
2318
|
+
return { result: JSON.stringify(subRes, null, 2) };
|
|
2319
|
+
}
|
|
2199
2320
|
case 'task_completed': {
|
|
2200
2321
|
const summaryStr = String(args.summary || 'Task completed successfully.');
|
|
2201
2322
|
if (this.executionIntent?.targetDestination === 'Desktop' && this.executionIntent?.targetArtifactName) {
|