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.
Files changed (47) hide show
  1. package/.firebase/hosting.d2Vi.cache +7 -7
  2. package/README.md +2 -2
  3. package/bin/src/commands/agent.d.ts.map +1 -1
  4. package/bin/src/commands/agent.js +62 -1
  5. package/bin/src/commands/agent.js.map +1 -1
  6. package/bin/src/commands/checkpoint.d.ts.map +1 -0
  7. package/bin/src/commands/checkpoint.js +49 -0
  8. package/bin/src/commands/checkpoint.js.map +1 -0
  9. package/bin/src/commands/pr.d.ts.map +1 -0
  10. package/bin/src/commands/pr.js +191 -0
  11. package/bin/src/commands/pr.js.map +1 -0
  12. package/bin/src/commands/watch.d.ts.map +1 -0
  13. package/bin/src/commands/watch.js +149 -0
  14. package/bin/src/commands/watch.js.map +1 -0
  15. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  16. package/bin/src/engine/agentEngine.js +161 -40
  17. package/bin/src/engine/agentEngine.js.map +1 -1
  18. package/bin/src/engine/checkpoints/checkpointManager.d.ts.map +1 -0
  19. package/bin/src/engine/checkpoints/checkpointManager.js +171 -0
  20. package/bin/src/engine/checkpoints/checkpointManager.js.map +1 -0
  21. package/bin/src/engine/computer/computerController.d.ts.map +1 -1
  22. package/bin/src/engine/computer/computerController.js +3 -0
  23. package/bin/src/engine/computer/computerController.js.map +1 -1
  24. package/bin/src/engine/computer/operatorHud.d.ts.map +1 -0
  25. package/bin/src/engine/computer/operatorHud.js +126 -0
  26. package/bin/src/engine/computer/operatorHud.js.map +1 -0
  27. package/bin/src/engine/llm.d.ts.map +1 -1
  28. package/bin/src/engine/llm.js +76 -29
  29. package/bin/src/engine/llm.js.map +1 -1
  30. package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
  31. package/bin/src/engine/runtime/agentRuntime.js +41 -9
  32. package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
  33. package/bin/src/engine/subagents/subagentManager.d.ts.map +1 -0
  34. package/bin/src/engine/subagents/subagentManager.js +96 -0
  35. package/bin/src/engine/subagents/subagentManager.js.map +1 -0
  36. package/bin/src/index.js +50 -4
  37. package/bin/src/index.js.map +1 -1
  38. package/bin/src/mcp/client.js +1 -1
  39. package/bin/src/server/server.js +2 -2
  40. package/bin/src/server/server.js.map +1 -1
  41. package/bin/src/utils/branding.js +1 -1
  42. package/bin/src/utils/qrBridge.js +1 -1
  43. package/package.json +2 -1
  44. package/web/app.js +4 -3
  45. package/web/styles.css +2 -2
  46. package/artifacts/scout_drone_3d.glb +0 -0
  47. 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 || 30;
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, 100);
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.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
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(3);
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, verify the scene with get_scene_info, and finish by calling task_completed with a complete description of the created 3D character.
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 estTotalSecs = Math.round(this.maxSteps * 12);
824
- const estTimeText = estTotalSecs >= 60 ? `~${(estTotalSecs / 60).toFixed(1)}m` : `~${estTotalSecs}s`;
825
- console.log(chalk.gray(`Estimated completion time: ${estTimeText} (Max ${this.maxSteps} steps)`));
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
- stepSpinner.start(chalk.cyan(`Scout is Working (Step ${step}/${this.maxSteps} • Est. completion: ~${estLeftStr} left)`));
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 > 250 ? desc.slice(0, 247) + '...' : desc}`,
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 activeLoopTools = [...this.toolRegistry.getToolsForTier(this.userTier), ...mcpToolDefs];
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: read_file, write_file, edit_file, run_command, list_dir, grep_search, glob_search, tree_view, file_info, multi_edit_file, fetch_url, git_diff, ask_user, speak_text, task_completed.
900
- You MUST output exactly ONE JSON code block per tool call. Do NOT describe what you would do in plain textoutput the JSON tool call directly!`,
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${toolResult.result}`,
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${toolResult.result}`,
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: Auto-extend by +15 steps ONLY for Pro & Enterprise (up to 60 max threshold)
1133
- const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
1134
- if (step >= this.maxSteps) {
1135
- if (isPaid && this.maxSteps < 60) {
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: `Reached max iteration steps limit (${this.maxSteps}). Modified files: ${Array.from(this.modifiedFiles).join(', ')}`,
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
- this.taskStateManager.addObservation(name, `Scratchpad updated (${content.length} chars)`, true);
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
- this.taskStateManager.addObservation(name, `Appended to scratchpad (${content.length} chars)`, true);
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) {