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.
- 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 +21 -8
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +253 -13
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/computer/computerController.d.ts.map +1 -1
- package/bin/src/engine/computer/computerController.js +532 -296
- package/bin/src/engine/computer/computerController.js.map +1 -1
- package/bin/src/engine/computer/computerDiagnostic.d.ts.map +1 -0
- package/bin/src/engine/computer/computerDiagnostic.js +71 -0
- package/bin/src/engine/computer/computerDiagnostic.js.map +1 -0
- package/bin/src/engine/computer/index.d.ts.map +1 -1
- package/bin/src/engine/computer/index.js +1 -0
- package/bin/src/engine/computer/index.js.map +1 -1
- package/bin/src/engine/computer/mockComputer.d.ts.map +1 -1
- package/bin/src/engine/computer/mockComputer.js +104 -8
- package/bin/src/engine/computer/mockComputer.js.map +1 -1
- package/bin/src/engine/computer/scoutDriver.cs +873 -0
- package/bin/src/engine/computer/scout_driver.exe +0 -0
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -1
- package/bin/src/engine/runtime/agentRuntime.js +351 -20
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -1
- package/bin/src/engine/safety.d.ts.map +1 -1
- package/bin/src/engine/safety.js +13 -3
- package/bin/src/engine/safety.js.map +1 -1
- package/bin/src/engine/toolRegistry.d.ts.map +1 -1
- package/bin/src/engine/toolRegistry.js +144 -0
- package/bin/src/engine/toolRegistry.js.map +1 -1
- package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -1
- package/bin/src/engine/workspace/agentWorkspace.js +21 -0
- package/bin/src/engine/workspace/agentWorkspace.js.map +1 -1
- package/bin/src/index.js +4 -4
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js +14 -1
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/package.json +1 -1
- package/web/app.js +2 -2
- package/web/styles.css +2 -2
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentRuntime.d.ts","sourceRoot":"","sources":["agentRuntime.ts"],"names":[],"mappings":"AAQA,OAAO,EAA0C,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EAAE,cAAc,EAAsB,KAAK,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAC7G,OAAO,EAAE,KAAK,mBAAmB,EAAsD,MAAM,mCAAmC,CAAC;AAEjI,OAAO,EAAE,KAAK,kBAAkB,EAA6B,MAAM,qBAAqB,CAAC;AACzF,OAAO,EAAE,cAAc,EAAyB,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAkB,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;
|
|
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:
|
|
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: ${
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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'}"
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
689
|
+
const res = await this.computerController.move(x, y);
|
|
495
690
|
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
496
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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);
|