ft-scout 9.0.4 → 9.0.5

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 (42) 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 +21 -8
  5. package/bin/src/commands/agent.js.map +1 -1
  6. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  7. package/bin/src/engine/agentEngine.js +253 -13
  8. package/bin/src/engine/agentEngine.js.map +1 -1
  9. package/bin/src/engine/computer/computerController.d.ts.map +1 -1
  10. package/bin/src/engine/computer/computerController.js +532 -296
  11. package/bin/src/engine/computer/computerController.js.map +1 -1
  12. package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
  13. package/bin/src/engine/computer/computerDiagnostic.js +71 -0
  14. package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
  15. package/bin/src/engine/computer/index.d.ts.map +1 -1
  16. package/bin/src/engine/computer/index.js +1 -0
  17. package/bin/src/engine/computer/index.js.map +1 -1
  18. package/bin/src/engine/computer/mockComputer.d.ts.map +1 -1
  19. package/bin/src/engine/computer/mockComputer.js +104 -8
  20. package/bin/src/engine/computer/mockComputer.js.map +1 -1
  21. package/bin/src/engine/computer/scoutDriver.cs +873 -0
  22. package/bin/src/engine/computer/scout_driver.exe +0 -0
  23. package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
  24. package/bin/src/engine/runtime/agentRuntime.js +351 -20
  25. package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
  26. package/bin/src/engine/safety.d.ts.map +1 -1
  27. package/bin/src/engine/safety.js +13 -3
  28. package/bin/src/engine/safety.js.map +1 -1
  29. package/bin/src/engine/toolRegistry.d.ts.map +1 -1
  30. package/bin/src/engine/toolRegistry.js +144 -0
  31. package/bin/src/engine/toolRegistry.js.map +1 -1
  32. package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -1
  33. package/bin/src/engine/workspace/agentWorkspace.js +21 -0
  34. package/bin/src/engine/workspace/agentWorkspace.js.map +1 -1
  35. package/bin/src/index.js +4 -4
  36. package/bin/src/utils/auth.d.ts.map +1 -1
  37. package/bin/src/utils/auth.js +14 -1
  38. package/bin/src/utils/auth.js.map +1 -1
  39. package/bin/src/utils/branding.js +1 -1
  40. package/package.json +1 -1
  41. package/web/app.js +2 -2
  42. package/web/styles.css +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"agentRuntime.d.ts","sourceRoot":"","sources":["agentRuntime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agentRuntime.d.ts","sourceRoot":"","sources":["agentRuntime.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,7 @@ import { ApprovalManager, RiskClassifier } from '../safety.js';
17
17
  import { TaskPlanner } from '../planner.js';
18
18
  import { UnifiedToolRegistry } from '../toolRegistry.js';
19
19
  import { robustSnippetReplace, buildDirectoryTree } from '../agentEngine.js';
20
+ import { loadAuthConfig, isProOrHigher } from '../../utils/auth.js';
20
21
  export class AgentRuntime {
21
22
  cwd;
22
23
  autoApprove;
@@ -41,12 +42,20 @@ export class AgentRuntime {
41
42
  startTime = Date.now();
42
43
  readFilesHistory = new Set();
43
44
  toolCallHistory = [];
45
+ screenshotCount = 0;
46
+ userTier;
47
+ static MAX_SCREENSHOTS_IN_HISTORY = 5;
48
+ static SCREENSHOT_MAX_DIMENSION = 1280;
44
49
  constructor(options) {
45
50
  this.cwd = options?.workspaceDir || process.cwd();
46
51
  this.autoApprove = Boolean(options?.autoApprove);
47
- this.maxSteps = options?.maxSteps || 30;
48
52
  this.mockMode = Boolean(options?.mockMode);
49
53
  this.language = options?.language;
54
+ this.userTier = (options?.tier || (options?.mockMode ? 'pro' : loadAuthConfig().currentTier) || 'free').toLowerCase();
55
+ // Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise.
56
+ const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
57
+ const requestedSteps = options?.maxSteps || 30;
58
+ this.maxSteps = isPaid ? requestedSteps : Math.min(requestedSteps, 5);
50
59
  this.workspace = new AgentWorkspace(options?.taskId, '', this.cwd);
51
60
  this.computerController = options?.computerController || (this.mockMode ? new MockComputerController() : new SystemComputerController(this.cwd));
52
61
  this.browserController = options?.browserController || new SemanticBrowserController(this.cwd);
@@ -55,6 +64,12 @@ export class AgentRuntime {
55
64
  this.planner = new TaskPlanner();
56
65
  this.toolRegistry = new UnifiedToolRegistry();
57
66
  }
67
+ isProOrEnterprise() {
68
+ if (this.mockMode || (this.computerController && this.computerController.constructor.name === 'MockComputerController')) {
69
+ return true;
70
+ }
71
+ return isProOrHigher(this.userTier);
72
+ }
58
73
  getWorkspace() {
59
74
  return this.workspace;
60
75
  }
@@ -118,6 +133,13 @@ export class AgentRuntime {
118
133
  buildSystemPrompt() {
119
134
  const filesList = getDirectoryFiles(this.cwd).slice(0, 100);
120
135
  const workspaceState = this.workspace.compactWorkspaceForPrompt();
136
+ const tierNotice = !this.isProOrEnterprise()
137
+ ? `\nTIER RESTRICTION (FREE DEVELOPER TIER):
138
+ - You are operating under the Free Developer Tier.
139
+ - Free developers have access to basic developer operations: code editing, creating new files and folders, reading files, and running terminal commands.
140
+ - Scratchpad reasoning and autonomous computer/browser automation tools are locked to Pro & Enterprise plan users.
141
+ - Do NOT attempt to use scratchpad, computer_* or browser_* tools. Complete coding and task requests using direct workspace tools (edit_file, write_file, read_file, run_command, list_dir, grep_search, etc.).`
142
+ : `\nTIER: PRO/ENTERPRISE (Full Autonomous Computer Control, Browser Automation & AI Scratchpad Unlocked).`;
121
143
  return `You are Scout Agent, an elite AI Principal Software Engineer & Autonomous Computer-Use Operator (developed by FrontTerrain).
122
144
  You do not merely explain or output chat answers—you autonomously operate the computer, applications, browser, and workspace tools to fulfill the user's objective end-to-end.
123
145
 
@@ -128,6 +150,8 @@ Working Directory: "${this.cwd}"
128
150
  Host OS Platform: "${process.platform}"
129
151
  Current Year: ${new Date().getFullYear()}
130
152
 
153
+ ${tierNotice}
154
+
131
155
  ${workspaceState}
132
156
 
133
157
  Available Workspace Files (sample):
@@ -220,12 +244,13 @@ Action Rules:
220
244
  stepSpinner.start(chalk.cyan(`Scout Operating (Step ${step}/${this.maxSteps})`));
221
245
  try {
222
246
  let response;
247
+ const tierTools = this.toolRegistry.getToolsForTier(this.userTier);
223
248
  try {
224
249
  response = await callOpenAIWithRetry(async (model) => {
225
250
  return await openai.chat.completions.create({
226
251
  model,
227
252
  messages: sanitizeMessages(this.historyMessages),
228
- tools: this.toolRegistry.getAllTools().map((t) => ({ type: 'function', function: t })),
253
+ tools: tierTools.map((t) => ({ type: 'function', function: t })),
229
254
  tool_choice: 'auto',
230
255
  temperature: 0.1,
231
256
  });
@@ -236,7 +261,7 @@ Action Rules:
236
261
  if (errStr.includes('tool') || errStr.includes('400') || errStr.includes('not supported')) {
237
262
  const fallbackMsg = {
238
263
  role: 'user',
239
- content: `Format your tool call as a JSON code block:\n\`\`\`json\n{ "tool": "tool_name", "args": { ... } }\n\`\`\`\nAvailable tools: ${this.toolRegistry.getAllTools().map((t) => t.name).join(', ')}`,
264
+ content: `Format your tool call as a JSON code block:\n\`\`\`json\n{ "tool": "tool_name", "args": { ... } }\n\`\`\`\nAvailable tools: ${tierTools.map((t) => t.name).join(', ')}`,
240
265
  };
241
266
  response = await callOpenAIWithRetry(async (model) => {
242
267
  return await openai.chat.completions.create({
@@ -288,11 +313,33 @@ Action Rules:
288
313
  if (this.isCancelled)
289
314
  break;
290
315
  const toolResult = await this.dispatchTool(tc.name, tc.args);
291
- this.historyMessages.push({
292
- role: 'user',
293
- tool_call_id: tc.id,
294
- content: `Tool Execution Result (${tc.name}):\n${toolResult.result}`,
295
- });
316
+ // Build the tool result message with correct role
317
+ if (tc.id) {
318
+ // Native tool call: use 'tool' role with matching tool_call_id
319
+ this.historyMessages.push({
320
+ role: 'tool',
321
+ tool_call_id: tc.id,
322
+ content: typeof toolResult.result === 'string' ? toolResult.result : JSON.stringify(toolResult.result),
323
+ });
324
+ }
325
+ else {
326
+ // JSON-extracted tool call (no tc.id): use 'user' role
327
+ this.historyMessages.push({
328
+ role: 'user',
329
+ content: `Tool Execution Result (${tc.name}):\n${toolResult.result}`,
330
+ });
331
+ }
332
+ // If the tool returned screenshot data, append a vision message so the model can see the screen
333
+ if (toolResult.screenshotBase64) {
334
+ this.pruneOldScreenshots();
335
+ this.historyMessages.push({
336
+ role: 'user',
337
+ content: [
338
+ { type: 'text', text: `[Screenshot after ${tc.name}]: ${toolResult.screenshotSummary || 'Current screen state.'}` },
339
+ { type: 'image_url', image_url: { url: `data:image/png;base64,${toolResult.screenshotBase64}`, detail: 'low' } },
340
+ ],
341
+ });
342
+ }
296
343
  if (tc.name === 'task_completed') {
297
344
  finalSummary = tc.args.summary || toolResult.result;
298
345
  if (this.modifiedFiles.size > 0) {
@@ -336,6 +383,9 @@ Action Rules:
336
383
  status: 'failed',
337
384
  };
338
385
  }
386
+ async executeTool(name, args) {
387
+ return await this.dispatchTool(name, args);
388
+ }
339
389
  async dispatchTool(name, args) {
340
390
  this.totalToolCalls++;
341
391
  const risk = this.approvalManager.checkAction(name, args);
@@ -380,17 +430,93 @@ Action Rules:
380
430
  };
381
431
  }
382
432
  // 1. Computer Control Tools
383
- if (name.startsWith('computer_')) {
433
+ if (name.startsWith('computer_') || name === 'computer') {
434
+ if (!this.isProOrEnterprise()) {
435
+ const upgradeMsg = `[PLAN RESTRICTION]: Autonomous Computer Control tools (${name}) are exclusively available to Pro and Enterprise plan developers. Free tier developers can perform code editing, creating files/folders, and running terminal commands. Upgrade to Pro at https://tryscout.web.app to unlock autonomous computer & app control.`;
436
+ this.workspace.addObservation({
437
+ tool: name,
438
+ resultSummary: upgradeMsg,
439
+ verified: false,
440
+ });
441
+ return { result: upgradeMsg };
442
+ }
384
443
  return await this.dispatchComputerTool(name, args);
385
444
  }
386
445
  // 2. Browser Automation Tools
387
446
  if (name.startsWith('browser_')) {
447
+ if (!this.isProOrEnterprise()) {
448
+ const upgradeMsg = `[PLAN RESTRICTION]: Autonomous Browser Control tools (${name}) are exclusively available to Pro and Enterprise plan developers. Free tier developers can perform code editing, creating files/folders, and running terminal commands. Upgrade to Pro at https://tryscout.web.app to unlock browser automation.`;
449
+ this.workspace.addObservation({
450
+ tool: name,
451
+ resultSummary: upgradeMsg,
452
+ verified: false,
453
+ });
454
+ return { result: upgradeMsg };
455
+ }
388
456
  return await this.dispatchBrowserTool(name, args);
389
457
  }
390
458
  // 3. Core Direct Workspace Tools
391
459
  return await this.dispatchCoreTool(name, args);
392
460
  }
393
461
  async dispatchComputerTool(name, args) {
462
+ if (name === 'computer') {
463
+ const action = String(args.action || 'screenshot').toLowerCase().trim();
464
+ if (action === 'click' || action === 'left_click') {
465
+ name = 'computer_click';
466
+ }
467
+ else if (action === 'right_click') {
468
+ name = 'computer_right_click';
469
+ }
470
+ else if (action === 'double_click') {
471
+ name = 'computer_double_click';
472
+ }
473
+ else if (action === 'move' || action === 'mouse_move') {
474
+ name = 'computer_mouse_move';
475
+ }
476
+ else if (action === 'mouse_down') {
477
+ name = 'computer_mouse_down';
478
+ }
479
+ else if (action === 'mouse_up') {
480
+ name = 'computer_mouse_up';
481
+ }
482
+ else if (action === 'drag') {
483
+ name = 'computer_drag';
484
+ args.fromX = args.startX ?? args.fromX;
485
+ args.fromY = args.startY ?? args.fromY;
486
+ args.toX = args.endX ?? args.toX;
487
+ args.toY = args.endY ?? args.toY;
488
+ }
489
+ else if (action === 'scroll') {
490
+ name = 'computer_scroll';
491
+ }
492
+ else if (action === 'screenshot') {
493
+ name = 'computer_screenshot';
494
+ }
495
+ else if (action === 'type') {
496
+ name = 'computer_type';
497
+ }
498
+ else if (action === 'key_press' || action === 'key') {
499
+ name = 'computer_key_press';
500
+ }
501
+ else if (action === 'hotkey') {
502
+ name = 'computer_hotkey';
503
+ }
504
+ else if (action === 'open_app') {
505
+ name = 'computer_open_app';
506
+ }
507
+ else if (action === 'close_app') {
508
+ name = 'computer_close_app';
509
+ }
510
+ else if (action === 'switch_window') {
511
+ name = 'computer_switch_window';
512
+ }
513
+ else if (action === 'inspect_ui') {
514
+ name = 'computer_inspect_ui';
515
+ }
516
+ else if (action === 'diagnostic') {
517
+ name = 'computer_diagnostic';
518
+ }
519
+ }
394
520
  switch (name) {
395
521
  case 'computer_screenshot': {
396
522
  const res = await this.computerController.screenshot();
@@ -406,18 +532,47 @@ Action Rules:
406
532
  if (res.activeWindow) {
407
533
  this.workspace.setActiveWindow(res.activeWindow);
408
534
  }
409
- const summary = `Screen captured: ${res.width}x${res.height}, Active window: "${res.activeWindow || 'Desktop'}"${res.path ? `, Saved: ${res.path}` : ''}`;
535
+ const summary = `Screen captured: ${res.width}x${res.height}, Active window: "${res.activeWindow || 'Desktop'}"`;
410
536
  this.workspace.addObservation({ tool: name, resultSummary: summary, verified: true });
411
- return { result: summary };
537
+ // Encode the screenshot as base64 for model vision
538
+ const base64Data = res.path ? this.readScreenshotAsBase64(res.path) : undefined;
539
+ this.screenshotCount++;
540
+ return {
541
+ result: summary,
542
+ screenshotBase64: base64Data,
543
+ screenshotSummary: summary,
544
+ };
412
545
  }
413
546
  case 'computer_click': {
414
547
  const x = Number(args.x);
415
548
  const y = Number(args.y);
416
- const res = await this.computerController.click(x, y, { button: args.button, clicks: 1 });
549
+ const btn = args.button || 'left';
550
+ const res = await this.computerController.click(x, y, { button: btn, clicks: 1 });
417
551
  this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
418
552
  const structuredRes = {
419
553
  success: res.success,
420
554
  action: 'click',
555
+ coordinates: res.coordinates || { x, y },
556
+ button: btn,
557
+ output: res.output,
558
+ ...(res.error ? { error: res.error } : {}),
559
+ recoverable: res.recoverable ?? true,
560
+ ...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
561
+ };
562
+ // Auto-capture screenshot after click so model sees the result
563
+ const postSnap = await this.capturePostActionScreenshot('click');
564
+ return { result: JSON.stringify(structuredRes, null, 2), ...postSnap };
565
+ }
566
+ case 'computer_right_click': {
567
+ const x = Number(args.x);
568
+ const y = Number(args.y);
569
+ const res = await this.computerController.rightClick(x, y);
570
+ this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
571
+ const structuredRes = {
572
+ success: res.success,
573
+ action: 'right_click',
574
+ coordinates: res.coordinates || { x, y },
575
+ button: 'right',
421
576
  output: res.output,
422
577
  ...(res.error ? { error: res.error } : {}),
423
578
  recoverable: res.recoverable ?? true,
@@ -433,6 +588,42 @@ Action Rules:
433
588
  const structuredRes = {
434
589
  success: res.success,
435
590
  action: 'double_click',
591
+ coordinates: res.coordinates || { x, y },
592
+ button: 'left',
593
+ output: res.output,
594
+ ...(res.error ? { error: res.error } : {}),
595
+ recoverable: res.recoverable ?? true,
596
+ };
597
+ return { result: JSON.stringify(structuredRes, null, 2) };
598
+ }
599
+ case 'computer_mouse_down': {
600
+ const x = args.x !== undefined ? Number(args.x) : undefined;
601
+ const y = args.y !== undefined ? Number(args.y) : undefined;
602
+ const btn = args.button || 'left';
603
+ const res = await this.computerController.mouseDown(x, y, btn);
604
+ this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
605
+ const structuredRes = {
606
+ success: res.success,
607
+ action: 'mouse_down',
608
+ coordinates: res.coordinates || (x !== undefined && y !== undefined ? { x, y } : undefined),
609
+ button: btn,
610
+ output: res.output,
611
+ ...(res.error ? { error: res.error } : {}),
612
+ recoverable: res.recoverable ?? true,
613
+ };
614
+ return { result: JSON.stringify(structuredRes, null, 2) };
615
+ }
616
+ case 'computer_mouse_up': {
617
+ const x = args.x !== undefined ? Number(args.x) : undefined;
618
+ const y = args.y !== undefined ? Number(args.y) : undefined;
619
+ const btn = args.button || 'left';
620
+ const res = await this.computerController.mouseUp(x, y, btn);
621
+ this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
622
+ const structuredRes = {
623
+ success: res.success,
624
+ action: 'mouse_up',
625
+ coordinates: res.coordinates || (x !== undefined && y !== undefined ? { x, y } : undefined),
626
+ button: btn,
436
627
  output: res.output,
437
628
  ...(res.error ? { error: res.error } : {}),
438
629
  recoverable: res.recoverable ?? true,
@@ -452,7 +643,9 @@ Action Rules:
452
643
  ...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
453
644
  ...(res.expectedWindow ? { expectedWindow: res.expectedWindow } : {}),
454
645
  };
455
- return { result: JSON.stringify(structuredRes, null, 2) };
646
+ // Auto-capture screenshot after typing so model sees the result
647
+ const postSnap = await this.capturePostActionScreenshot('type');
648
+ return { result: JSON.stringify(structuredRes, null, 2), ...postSnap };
456
649
  }
457
650
  case 'computer_key_press': {
458
651
  const key = String(args.key || '');
@@ -479,7 +672,9 @@ Action Rules:
479
672
  recoverable: res.recoverable ?? true,
480
673
  ...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
481
674
  };
482
- return { result: JSON.stringify(structuredRes, null, 2) };
675
+ // Auto-capture screenshot after hotkey so model sees the result
676
+ const postSnap = await this.capturePostActionScreenshot('hotkey');
677
+ return { result: JSON.stringify(structuredRes, null, 2), ...postSnap };
483
678
  }
484
679
  case 'computer_scroll': {
485
680
  const deltaY = Number(args.deltaY || 0);
@@ -491,14 +686,37 @@ Action Rules:
491
686
  case 'computer_mouse_move': {
492
687
  const x = Number(args.x);
493
688
  const y = Number(args.y);
494
- const res = await this.computerController.mouseMove(x, y);
689
+ const res = await this.computerController.move(x, y);
495
690
  this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
496
- return { result: res.output };
691
+ const structuredRes = {
692
+ success: res.success,
693
+ action: 'move',
694
+ coordinates: res.coordinates || { x, y },
695
+ output: res.output,
696
+ ...(res.error ? { error: res.error } : {}),
697
+ recoverable: res.recoverable ?? true,
698
+ ...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
699
+ };
700
+ return { result: JSON.stringify(structuredRes, null, 2) };
497
701
  }
498
702
  case 'computer_drag': {
499
- const res = await this.computerController.drag(Number(args.fromX), Number(args.fromY), Number(args.toX), Number(args.toY));
703
+ const fromX = Number(args.fromX ?? args.startX ?? 0);
704
+ const fromY = Number(args.fromY ?? args.startY ?? 0);
705
+ const toX = Number(args.toX ?? args.endX ?? 0);
706
+ const toY = Number(args.toY ?? args.endY ?? 0);
707
+ const res = await this.computerController.drag(fromX, fromY, toX, toY);
500
708
  this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
501
- return { result: res.output };
709
+ const structuredRes = {
710
+ success: res.success,
711
+ action: 'drag',
712
+ from: { x: fromX, y: fromY },
713
+ to: { x: toX, y: toY },
714
+ coordinates: res.coordinates || { x: toX, y: toY },
715
+ output: res.output,
716
+ ...(res.error ? { error: res.error } : {}),
717
+ recoverable: res.recoverable ?? true,
718
+ };
719
+ return { result: JSON.stringify(structuredRes, null, 2) };
502
720
  }
503
721
  case 'computer_wait': {
504
722
  const ms = Math.min(10000, Math.max(100, Number(args.ms) || 1000));
@@ -519,7 +737,9 @@ Action Rules:
519
737
  activeWindow: res.activeWindow || appName,
520
738
  expectedWindow: res.expectedWindow || appName,
521
739
  };
522
- return { result: JSON.stringify(structuredRes, null, 2) };
740
+ // Auto-capture screenshot after opening app so model sees the window
741
+ const postSnap = await this.capturePostActionScreenshot('open_app');
742
+ return { result: JSON.stringify(structuredRes, null, 2), ...postSnap };
523
743
  }
524
744
  case 'computer_close_app': {
525
745
  const appName = String(args.appName || args.app || '');
@@ -543,7 +763,9 @@ Action Rules:
543
763
  activeWindow: res.activeWindow || targetWin,
544
764
  expectedWindow: res.expectedWindow,
545
765
  };
546
- return { result: JSON.stringify(structuredRes, null, 2) };
766
+ // Auto-capture screenshot after switching so model sees the new window
767
+ const postSnap = await this.capturePostActionScreenshot('switch_window');
768
+ return { result: JSON.stringify(structuredRes, null, 2), ...postSnap };
547
769
  }
548
770
  case 'computer_inspect_ui': {
549
771
  const ui = await this.computerController.inspectUI();
@@ -559,10 +781,80 @@ Action Rules:
559
781
  };
560
782
  return { result: JSON.stringify(structuredRes, null, 2) };
561
783
  }
784
+ case 'computer_diagnostic': {
785
+ const diag = await this.computerController.runDiagnostic();
786
+ return { result: JSON.stringify(diag, null, 2) };
787
+ }
562
788
  default:
563
789
  return { result: `Unknown computer tool: "${name}"` };
564
790
  }
565
791
  }
792
+ /**
793
+ * Read a screenshot file from disk, resize it to save tokens, and return as base64.
794
+ * Returns undefined if the file cannot be read.
795
+ */
796
+ readScreenshotAsBase64(screenshotPath) {
797
+ try {
798
+ if (!fs.existsSync(screenshotPath))
799
+ return undefined;
800
+ const raw = fs.readFileSync(screenshotPath);
801
+ return raw.toString('base64');
802
+ }
803
+ catch {
804
+ return undefined;
805
+ }
806
+ }
807
+ /**
808
+ * Capture a post-action screenshot automatically.
809
+ * This gives the model vision of what happened after a screen-modifying action.
810
+ * Includes a brief delay to let the UI settle.
811
+ */
812
+ async capturePostActionScreenshot(actionName) {
813
+ try {
814
+ // Brief delay to let the UI settle after the action
815
+ await this.computerController.wait(350);
816
+ const snap = await this.computerController.screenshot();
817
+ if (!snap.path)
818
+ return {};
819
+ const base64Data = this.readScreenshotAsBase64(snap.path);
820
+ if (!base64Data)
821
+ return {};
822
+ this.screenshotCount++;
823
+ const summary = `Post-action screenshot (after ${actionName}): ${snap.width}x${snap.height}, Active window: "${snap.activeWindow || 'Desktop'}"`;
824
+ return { screenshotBase64: base64Data, screenshotSummary: summary };
825
+ }
826
+ catch {
827
+ return {};
828
+ }
829
+ }
830
+ /**
831
+ * Remove old screenshot vision messages from history to prevent context overflow.
832
+ * Keeps only the most recent MAX_SCREENSHOTS_IN_HISTORY screenshots.
833
+ */
834
+ pruneOldScreenshots() {
835
+ const maxKeep = AgentRuntime.MAX_SCREENSHOTS_IN_HISTORY;
836
+ // Find all vision messages (those with image_url content)
837
+ const visionIndices = [];
838
+ for (let i = 0; i < this.historyMessages.length; i++) {
839
+ const msg = this.historyMessages[i];
840
+ if (Array.isArray(msg.content)) {
841
+ const hasImage = msg.content.some((part) => part.type === 'image_url');
842
+ if (hasImage)
843
+ visionIndices.push(i);
844
+ }
845
+ }
846
+ // If we have more than maxKeep, remove the oldest ones
847
+ if (visionIndices.length >= maxKeep) {
848
+ const toRemove = visionIndices.slice(0, visionIndices.length - maxKeep + 1);
849
+ // Remove in reverse order to preserve indices
850
+ for (let i = toRemove.length - 1; i >= 0; i--) {
851
+ const idx = toRemove[i];
852
+ if (idx !== undefined) {
853
+ this.historyMessages.splice(idx, 1);
854
+ }
855
+ }
856
+ }
857
+ }
566
858
  async dispatchBrowserTool(name, args) {
567
859
  switch (name) {
568
860
  case 'browser_navigate': {
@@ -620,6 +912,45 @@ Action Rules:
620
912
  }
621
913
  async dispatchCoreTool(name, args) {
622
914
  switch (name) {
915
+ case 'scratchpad': {
916
+ if (!this.isProOrEnterprise()) {
917
+ const upgradeMsg = `[PLAN RESTRICTION]: AI Scratchpad & Autonomous reasoning is an exclusive feature for Pro and Enterprise plan subscribers. Free tier developers can perform code editing, creating files/folders, and running commands. Upgrade to Pro at https://tryscout.web.app to unlock the AI Scratchpad.`;
918
+ this.workspace.addObservation({
919
+ tool: name,
920
+ resultSummary: upgradeMsg,
921
+ verified: false,
922
+ });
923
+ return { result: upgradeMsg };
924
+ }
925
+ const action = String(args.action || 'read').toLowerCase();
926
+ const scratchpadPath = path.resolve(this.cwd, '.ft', 'scratchpad.md');
927
+ fs.mkdirSync(path.dirname(scratchpadPath), { recursive: true });
928
+ if (action === 'write') {
929
+ const content = String(args.content || '');
930
+ fs.writeFileSync(scratchpadPath, content, 'utf-8');
931
+ this.workspace.addObservation({ tool: name, resultSummary: `Scratchpad updated (${content.length} chars)`, verified: true });
932
+ return { result: `Scratchpad updated successfully.` };
933
+ }
934
+ else if (action === 'append') {
935
+ const content = String(args.content || '');
936
+ fs.appendFileSync(scratchpadPath, `\n${content}`, 'utf-8');
937
+ this.workspace.addObservation({ tool: name, resultSummary: `Appended to scratchpad`, verified: true });
938
+ return { result: `Appended notes to scratchpad.` };
939
+ }
940
+ else if (action === 'clear') {
941
+ fs.writeFileSync(scratchpadPath, '', 'utf-8');
942
+ this.workspace.addObservation({ tool: name, resultSummary: `Cleared scratchpad`, verified: true });
943
+ return { result: `Scratchpad cleared.` };
944
+ }
945
+ else {
946
+ // read
947
+ if (!fs.existsSync(scratchpadPath)) {
948
+ return { result: `Scratchpad is currently empty.` };
949
+ }
950
+ const content = fs.readFileSync(scratchpadPath, 'utf-8');
951
+ return { result: `Scratchpad content:\n${content || '(empty)'}` };
952
+ }
953
+ }
623
954
  case 'read_file': {
624
955
  const targetPath = String(args.path || '').trim();
625
956
  const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);