ft-scout 9.0.3 → 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 +76 -83
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +31 -228
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -1
- package/bin/src/commands/plan.js +45 -18
- package/bin/src/commands/plan.js.map +1 -1
- package/bin/src/engine/actionVerifier.d.ts.map +1 -1
- package/bin/src/engine/actionVerifier.js +61 -9
- package/bin/src/engine/actionVerifier.js.map +1 -1
- package/bin/src/engine/agentEngine.d.ts.map +1 -1
- package/bin/src/engine/agentEngine.js +701 -553
- package/bin/src/engine/agentEngine.js.map +1 -1
- package/bin/src/engine/agentState.d.ts.map +1 -1
- package/bin/src/engine/agentState.js +0 -64
- package/bin/src/engine/agentState.js.map +1 -1
- package/bin/src/engine/browser/browserController.d.ts.map +1 -1
- package/bin/src/engine/browser/browserController.js +8 -0
- package/bin/src/engine/browser/browserController.js.map +1 -1
- package/bin/src/engine/computer/computerController.d.ts.map +1 -1
- package/bin/src/engine/computer/computerController.js +686 -240
- 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 +2 -0
- package/bin/src/engine/computer/index.js.map +1 -1
- package/bin/src/engine/computer/mockComputer.d.ts.map +1 -0
- package/bin/src/engine/computer/mockComputer.js +260 -0
- package/bin/src/engine/computer/mockComputer.js.map +1 -0
- 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 -0
- package/bin/src/engine/runtime/agentRuntime.js +1160 -0
- package/bin/src/engine/runtime/agentRuntime.js.map +1 -0
- package/bin/src/engine/runtime/index.d.ts.map +1 -0
- package/bin/src/engine/runtime/index.js +2 -0
- package/bin/src/engine/runtime/index.js.map +1 -0
- package/bin/src/engine/safety.d.ts.map +1 -1
- package/bin/src/engine/safety.js +59 -25
- 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 +293 -68
- package/bin/src/engine/toolRegistry.js.map +1 -1
- package/bin/src/engine/voiceEngine.d.ts.map +1 -1
- package/bin/src/engine/voiceEngine.js +0 -1
- package/bin/src/engine/voiceEngine.js.map +1 -1
- package/bin/src/engine/workspace/agentWorkspace.d.ts.map +1 -0
- package/bin/src/engine/workspace/agentWorkspace.js +505 -0
- package/bin/src/engine/workspace/agentWorkspace.js.map +1 -0
- package/bin/src/engine/workspace/index.d.ts.map +1 -0
- package/bin/src/engine/workspace/index.js +2 -0
- package/bin/src/engine/workspace/index.js.map +1 -0
- package/bin/src/index.js +4 -5
- package/bin/src/index.js.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +25 -4
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js +81 -50
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/payment.d.ts.map +1 -1
- package/bin/src/utils/payment.js +15 -1
- package/bin/src/utils/payment.js.map +1 -1
- package/bin/src/utils/pricing.js +1 -1
- package/bin/src/utils/pricing.js.map +1 -1
- package/bin/src/utils/razorpay.d.ts.map +1 -1
- package/bin/src/utils/razorpay.js +10 -2
- package/bin/src/utils/razorpay.js.map +1 -1
- package/downloads/invoice-2026-03.pdf +1 -0
- package/package.json +1 -1
- package/web/app.js +54 -135
- package/web/styles.css +2 -2
- package/bin/src/engine/appControl.d.ts.map +0 -1
- package/bin/src/engine/appControl.js +0 -2413
- package/bin/src/engine/appControl.js.map +0 -1
- package/bin/src/engine/liveScreenEngine.d.ts.map +0 -1
- package/bin/src/engine/liveScreenEngine.js +0 -421
- package/bin/src/engine/liveScreenEngine.js.map +0 -1
- package/bin/src/engine/scripts/winCapture.ps1 +0 -248
- package/downloads/INVOICE_2026_ACME.pdf +0 -1
|
@@ -9,16 +9,18 @@ import { saveAgentSession, getRecentSessionsSummary } from '../utils/session.js'
|
|
|
9
9
|
import { safeNote, renderMarkdown } from '../utils/markdown.js';
|
|
10
10
|
import { openai, callOpenAIWithRetry, isQuotaExceededError, sanitizeMessage, sanitizeMessages } from './llm.js';
|
|
11
11
|
import { checkFileSyntax, verifyAndSelfHealFiles, executeSmartCommand, extractErrorDiagnostics } from './verifier.js';
|
|
12
|
-
import { openApp, executeInApp, cleanupScreenshots } from './appControl.js';
|
|
13
12
|
import { speakText, listenSpeechToText } from './voiceEngine.js';
|
|
14
|
-
import { startLiveScreenShare, stopLiveScreenShare, isLiveScreenShareActive } from './liveScreenEngine.js';
|
|
15
13
|
import { TaskStateManager } from './agentState.js';
|
|
16
|
-
import { OSComputerController } from './computer/index.js';
|
|
17
14
|
import { SemanticBrowserController } from './browser/index.js';
|
|
18
15
|
import { ActionVerifier } from './actionVerifier.js';
|
|
19
16
|
import { ApprovalManager, RiskClassifier } from './safety.js';
|
|
20
17
|
import { TaskPlanner } from './planner.js';
|
|
21
18
|
import { UnifiedToolRegistry } from './toolRegistry.js';
|
|
19
|
+
import { SystemComputerController, MockComputerController } from './computer/index.js';
|
|
20
|
+
import { AgentWorkspace, inferExecutionIntent, resolveDesktopPath } from './workspace/index.js';
|
|
21
|
+
import { AgentRuntime } from './runtime/index.js';
|
|
22
|
+
import { loadAuthConfig, isProOrHigher } from '../utils/auth.js';
|
|
23
|
+
export { AgentRuntime, AgentWorkspace, inferExecutionIntent, resolveDesktopPath, SystemComputerController, MockComputerController };
|
|
22
24
|
const execAsync = promisify(exec);
|
|
23
25
|
export function robustSnippetReplace(origContent, target, replacement) {
|
|
24
26
|
if (!origContent || !target) {
|
|
@@ -277,54 +279,6 @@ export const AGENT_TOOLS = [
|
|
|
277
279
|
required: ['question'],
|
|
278
280
|
},
|
|
279
281
|
},
|
|
280
|
-
{
|
|
281
|
-
name: 'open_app',
|
|
282
|
-
description: 'Open, launch, or focus an external application (browser, terminal, editor, VS Code, Notepad, or custom app) as requested by user prompt.',
|
|
283
|
-
parameters: {
|
|
284
|
-
type: 'object',
|
|
285
|
-
properties: {
|
|
286
|
-
app: { type: 'string', description: 'Application name or executable (e.g. "browser", "chrome", "edge", "terminal", "powershell", "code", "vscode", "notepad", or path)' },
|
|
287
|
-
target: { type: 'string', description: 'Optional target URL (for browser), file/folder path (for editor), or initial command (for terminal)' },
|
|
288
|
-
reason: { type: 'string', description: 'Explanation of why this app is being launched and taken over' },
|
|
289
|
-
},
|
|
290
|
-
required: ['app'],
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
name: 'app_action',
|
|
295
|
-
description: 'Execute interactive desktop/browser scratchpad automation actions, live screen sharing with model, UI element clicking, screen takeover, mouse cursor positioning & click takeover, mouse scrolling, window management, keyboard keystrokes, hotkeys, focus locks, or command sequences without shell commands (e.g. action: "live_screen_share", "start_screen_share", "stop_screen_share", "live_screen_stream", "see_screen", "analyze_screen", "capture_screen", "click_element", "click_app", "scroll", "list_windows", "focus_window", "takeover", "move_mouse", "type_text", "send_keys", "key_combo", "lock_app", "fetch_page", "navigate", "search", "send_dm", "exec_command", "open_file"). All screen frames are streamed in-memory directly to the model with zero temporary files.',
|
|
296
|
-
parameters: {
|
|
297
|
-
type: 'object',
|
|
298
|
-
properties: {
|
|
299
|
-
app: { type: 'string', description: 'Target application name, window title, screen, or category ("browser", "terminal", "editor", "notepad", "chrome", "desktop")' },
|
|
300
|
-
action: { type: 'string', description: 'Action type ("live_screen_share", "start_screen_share", "stop_screen_share", "live_screen_stream", "see_screen", "analyze_screen", "capture_screen", "click_element", "click_app", "scroll", "list_windows", "focus_window", "takeover", "send_mail", "type_text", "move_mouse", "send_keys", "key_combo", "lock_app", "fetch_page", "search", "send_dm", "exec_command", "open_file")' },
|
|
301
|
-
payload: { description: 'Action details/payload object or string (e.g. element name/description to click { element: "Search Google" }, coordinates { x: 100, y: 200 }, scroll { direction: "down", amount: 4 }, text string, { text: "...", enter: true }, { keyCombo: "ctrl+v" }, { duration: 3000 }, URL, search query, command, or file path)' },
|
|
302
|
-
},
|
|
303
|
-
required: ['app', 'action'],
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
{
|
|
307
|
-
name: 'agent_scratchpad',
|
|
308
|
-
description: 'Update, read, or clear the agent working memory scratchpad, step checklist, and working notes for multi-step goals.',
|
|
309
|
-
parameters: {
|
|
310
|
-
type: 'object',
|
|
311
|
-
properties: {
|
|
312
|
-
action: { type: 'string', description: 'Action type ("update", "read", "clear")' },
|
|
313
|
-
plan: {
|
|
314
|
-
type: 'array',
|
|
315
|
-
description: 'List of step items in the goal execution plan',
|
|
316
|
-
items: { type: 'string' },
|
|
317
|
-
},
|
|
318
|
-
completedSteps: {
|
|
319
|
-
type: 'array',
|
|
320
|
-
description: 'Indices of steps that are completed (0-based numbers)',
|
|
321
|
-
items: { type: 'number' },
|
|
322
|
-
},
|
|
323
|
-
notes: { type: 'string', description: 'Working memory notes, findings, or scratchpad text' },
|
|
324
|
-
},
|
|
325
|
-
required: ['action'],
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
282
|
{
|
|
329
283
|
name: 'speak_text',
|
|
330
284
|
description: 'Speak a text phrase out loud using Text-to-Speech (TTS) voice synthesis for voice feedback or audio summaries.',
|
|
@@ -400,7 +354,6 @@ export class AgentExecutionLoop {
|
|
|
400
354
|
backups = [];
|
|
401
355
|
autoApprove;
|
|
402
356
|
maxSteps;
|
|
403
|
-
projectName;
|
|
404
357
|
startTime = Date.now();
|
|
405
358
|
totalToolCalls = 0;
|
|
406
359
|
currentStep = 0;
|
|
@@ -409,48 +362,40 @@ export class AgentExecutionLoop {
|
|
|
409
362
|
consecutiveCommands = 0;
|
|
410
363
|
consecutiveScreenChecks = 0;
|
|
411
364
|
toolCallHistory = [];
|
|
412
|
-
scratchpadState = { plan: [], completedSteps: [], notes: '' };
|
|
413
365
|
taskStateManager;
|
|
414
|
-
computerController;
|
|
415
366
|
browserController;
|
|
367
|
+
computerController;
|
|
416
368
|
actionVerifier;
|
|
417
369
|
approvalManager;
|
|
418
370
|
taskPlanner;
|
|
419
371
|
toolRegistry;
|
|
420
372
|
mockMode;
|
|
373
|
+
isCancelled = false;
|
|
374
|
+
cancelReason;
|
|
375
|
+
executionIntent;
|
|
376
|
+
userTier;
|
|
421
377
|
constructor(options) {
|
|
422
378
|
this.cwd = process.cwd();
|
|
423
379
|
this.autoApprove = Boolean(options?.autoApprove);
|
|
424
|
-
this.maxSteps = options?.maxSteps || 30;
|
|
425
|
-
this.projectName = options?.projectName || path.basename(this.cwd);
|
|
426
380
|
this.mockMode = Boolean(options?.mockMode);
|
|
381
|
+
this.userTier = (options?.tier || loadAuthConfig().currentTier || 'free').toLowerCase();
|
|
382
|
+
// Free tier is bounded to basic direct actions (max 5 steps). Full autonomous multi-step loops require Pro/Enterprise.
|
|
383
|
+
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
384
|
+
const requestedSteps = options?.maxSteps || 30;
|
|
385
|
+
this.maxSteps = isPaid ? requestedSteps : Math.min(requestedSteps, 5);
|
|
427
386
|
this.taskStateManager = new TaskStateManager(options?.taskId, '', this.cwd);
|
|
428
|
-
this.computerController = options?.computerController || new
|
|
387
|
+
this.computerController = options?.computerController || (this.mockMode ? new MockComputerController() : new SystemComputerController(this.cwd));
|
|
429
388
|
this.browserController = options?.browserController || new SemanticBrowserController(this.cwd);
|
|
430
389
|
this.actionVerifier = new ActionVerifier(this.cwd);
|
|
431
390
|
this.approvalManager = new ApprovalManager(this.autoApprove);
|
|
432
391
|
this.taskPlanner = new TaskPlanner();
|
|
433
392
|
this.toolRegistry = new UnifiedToolRegistry();
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
cleanupScreenshots();
|
|
441
|
-
}
|
|
442
|
-
catch { }
|
|
443
|
-
try {
|
|
444
|
-
if (isLiveScreenShareActive())
|
|
445
|
-
stopLiveScreenShare();
|
|
446
|
-
}
|
|
447
|
-
catch { }
|
|
448
|
-
};
|
|
449
|
-
process.once('exit', onExitOrInterrupt);
|
|
450
|
-
process.once('SIGINT', () => {
|
|
451
|
-
onExitOrInterrupt();
|
|
452
|
-
process.exit(0);
|
|
453
|
-
});
|
|
393
|
+
}
|
|
394
|
+
getUserTier() {
|
|
395
|
+
return this.userTier;
|
|
396
|
+
}
|
|
397
|
+
getExecutionIntent() {
|
|
398
|
+
return this.executionIntent;
|
|
454
399
|
}
|
|
455
400
|
getTaskStateManager() {
|
|
456
401
|
return this.taskStateManager;
|
|
@@ -458,12 +403,12 @@ export class AgentExecutionLoop {
|
|
|
458
403
|
getStateManager() {
|
|
459
404
|
return this.taskStateManager;
|
|
460
405
|
}
|
|
461
|
-
getComputerController() {
|
|
462
|
-
return this.computerController;
|
|
463
|
-
}
|
|
464
406
|
getBrowserController() {
|
|
465
407
|
return this.browserController;
|
|
466
408
|
}
|
|
409
|
+
getComputerController() {
|
|
410
|
+
return this.computerController;
|
|
411
|
+
}
|
|
467
412
|
getActionVerifier() {
|
|
468
413
|
return this.actionVerifier;
|
|
469
414
|
}
|
|
@@ -476,6 +421,15 @@ export class AgentExecutionLoop {
|
|
|
476
421
|
getToolRegistry() {
|
|
477
422
|
return this.toolRegistry;
|
|
478
423
|
}
|
|
424
|
+
cancel(reason = 'User cancelled task') {
|
|
425
|
+
this.isCancelled = true;
|
|
426
|
+
this.cancelReason = reason;
|
|
427
|
+
this.taskStateManager.setStatus('failed');
|
|
428
|
+
this.taskStateManager.recordError(`Task execution cancelled: ${reason}`, 'runtime');
|
|
429
|
+
}
|
|
430
|
+
isTaskCancelled() {
|
|
431
|
+
return this.isCancelled;
|
|
432
|
+
}
|
|
479
433
|
async resumeGoal(taskId) {
|
|
480
434
|
const saved = TaskStateManager.loadTaskState(taskId, this.cwd);
|
|
481
435
|
if (!saved) {
|
|
@@ -517,92 +471,7 @@ export class AgentExecutionLoop {
|
|
|
517
471
|
this.consecutiveCommands = 0;
|
|
518
472
|
this.consecutiveScreenChecks = 0;
|
|
519
473
|
this.toolCallHistory = [];
|
|
520
|
-
this.
|
|
521
|
-
}
|
|
522
|
-
updateScratchpad(plan, completedSteps, notes) {
|
|
523
|
-
if (plan !== undefined)
|
|
524
|
-
this.scratchpadState.plan = plan;
|
|
525
|
-
if (completedSteps !== undefined)
|
|
526
|
-
this.scratchpadState.completedSteps = completedSteps;
|
|
527
|
-
if (notes !== undefined)
|
|
528
|
-
this.scratchpadState.notes = notes;
|
|
529
|
-
try {
|
|
530
|
-
const ftDir = path.join(this.cwd, '.ft');
|
|
531
|
-
if (!fs.existsSync(ftDir)) {
|
|
532
|
-
fs.mkdirSync(ftDir, { recursive: true });
|
|
533
|
-
}
|
|
534
|
-
const scratchpadPath = path.join(ftDir, 'scratchpad.md');
|
|
535
|
-
let mdContent = `# Scout Agent Scratchpad\n\n`;
|
|
536
|
-
if (this.scratchpadState.plan.length > 0) {
|
|
537
|
-
mdContent += `## Plan Checklist\n`;
|
|
538
|
-
this.scratchpadState.plan.forEach((item, idx) => {
|
|
539
|
-
const isDone = this.scratchpadState.completedSteps.includes(idx);
|
|
540
|
-
mdContent += `- [${isDone ? 'x' : ' '}] Step ${idx + 1}: ${item}\n`;
|
|
541
|
-
});
|
|
542
|
-
mdContent += `\n`;
|
|
543
|
-
}
|
|
544
|
-
if (this.scratchpadState.notes) {
|
|
545
|
-
mdContent += `## Working Memory & Notes\n${this.scratchpadState.notes}\n`;
|
|
546
|
-
}
|
|
547
|
-
fs.writeFileSync(scratchpadPath, mdContent, 'utf-8');
|
|
548
|
-
}
|
|
549
|
-
catch {
|
|
550
|
-
// Ignore write errors to prevent breaking execution
|
|
551
|
-
}
|
|
552
|
-
let summary = 'Agent Scratchpad updated successfully.\n';
|
|
553
|
-
if (this.scratchpadState.plan.length > 0) {
|
|
554
|
-
summary += `Plan (${this.scratchpadState.plan.length} items):\n` +
|
|
555
|
-
this.scratchpadState.plan.map((item, idx) => ` ${this.scratchpadState.completedSteps.includes(idx) ? '[✓]' : '[ ]'} Step ${idx + 1}: ${item}`).join('\n') + '\n';
|
|
556
|
-
}
|
|
557
|
-
if (this.scratchpadState.notes) {
|
|
558
|
-
summary += `Notes: ${this.scratchpadState.notes}`;
|
|
559
|
-
}
|
|
560
|
-
return summary;
|
|
561
|
-
}
|
|
562
|
-
autoSyncScratchpadFromText(text) {
|
|
563
|
-
try {
|
|
564
|
-
const planLines = text.match(/[-*]\s*\[([ xX✓/])\]\s*(.*)/g);
|
|
565
|
-
if (planLines && planLines.length > 0) {
|
|
566
|
-
const plan = [];
|
|
567
|
-
const completed = [];
|
|
568
|
-
planLines.forEach((l, idx) => {
|
|
569
|
-
const isDone = l.includes('[x]') || l.includes('[X]') || l.includes('[✓]');
|
|
570
|
-
const cleanText = l.replace(/^[-*]\s*\[[ xX✓/]\]\s*/, '').trim();
|
|
571
|
-
plan.push(cleanText);
|
|
572
|
-
if (isDone)
|
|
573
|
-
completed.push(idx);
|
|
574
|
-
});
|
|
575
|
-
this.scratchpadState.plan = plan;
|
|
576
|
-
this.scratchpadState.completedSteps = completed;
|
|
577
|
-
}
|
|
578
|
-
const obsMatch = text.match(/Observations?:\s*([^\n]+(?:\n[^\n]+)*)/i);
|
|
579
|
-
const thoughtMatch = text.match(/Thought:\s*([^\n]+(?:\n[^\n]+)*)/i);
|
|
580
|
-
const notes = [
|
|
581
|
-
thoughtMatch ? `Thought: ${thoughtMatch[1]?.trim()}` : '',
|
|
582
|
-
obsMatch ? `Observations: ${obsMatch[1]?.trim()}` : '',
|
|
583
|
-
].filter(Boolean).join('\n\n');
|
|
584
|
-
if (notes) {
|
|
585
|
-
this.scratchpadState.notes = notes;
|
|
586
|
-
}
|
|
587
|
-
const ftDir = path.join(this.cwd, '.ft');
|
|
588
|
-
if (!fs.existsSync(ftDir))
|
|
589
|
-
fs.mkdirSync(ftDir, { recursive: true });
|
|
590
|
-
const scratchpadPath = path.join(ftDir, 'scratchpad.md');
|
|
591
|
-
let mdContent = `# Scout Agent Scratchpad & Working Memory\n\n`;
|
|
592
|
-
if (this.scratchpadState.plan.length > 0) {
|
|
593
|
-
mdContent += `## Plan Checklist\n`;
|
|
594
|
-
this.scratchpadState.plan.forEach((item, idx) => {
|
|
595
|
-
const isDone = this.scratchpadState.completedSteps.includes(idx);
|
|
596
|
-
mdContent += `- [${isDone ? 'x' : ' '}] Step ${idx + 1}: ${item}\n`;
|
|
597
|
-
});
|
|
598
|
-
mdContent += `\n`;
|
|
599
|
-
}
|
|
600
|
-
if (this.scratchpadState.notes) {
|
|
601
|
-
mdContent += `## Working Memory & Observations\n${this.scratchpadState.notes}\n`;
|
|
602
|
-
}
|
|
603
|
-
fs.writeFileSync(scratchpadPath, mdContent, 'utf-8');
|
|
604
|
-
}
|
|
605
|
-
catch { }
|
|
474
|
+
this.executionIntent = undefined;
|
|
606
475
|
}
|
|
607
476
|
handleToolConsecutiveTracking(fnName, args, step) {
|
|
608
477
|
const argsKey = JSON.stringify(args || {});
|
|
@@ -661,9 +530,12 @@ export class AgentExecutionLoop {
|
|
|
661
530
|
this.consecutiveReads++;
|
|
662
531
|
this.consecutiveCommands = 0;
|
|
663
532
|
if (this.consecutiveReads >= 2) {
|
|
533
|
+
const actionHint = this.executionIntent?.requiredInteraction === 'computer'
|
|
534
|
+
? `Take action in the requested application (${this.executionIntent.requiredApplications.join(', ') || 'Notepad'})! Use computer_type, computer_click, or computer_hotkey now.`
|
|
535
|
+
: `You MUST execute write_file or edit_file on this step to write or modify code and satisfy the user goal.`;
|
|
664
536
|
this.historyMessages.push({
|
|
665
537
|
role: 'user',
|
|
666
|
-
content: `URGENT ACTION MANDATE (Step ${step}): You have issued ${this.consecutiveReads} exploration tool calls in a row without making
|
|
538
|
+
content: `URGENT ACTION MANDATE (Step ${step}): You have issued ${this.consecutiveReads} exploration tool calls in a row without making progress! ${actionHint}`,
|
|
667
539
|
});
|
|
668
540
|
}
|
|
669
541
|
}
|
|
@@ -671,9 +543,12 @@ export class AgentExecutionLoop {
|
|
|
671
543
|
this.consecutiveReads = 0;
|
|
672
544
|
this.consecutiveCommands++;
|
|
673
545
|
if (this.consecutiveCommands >= 2) {
|
|
546
|
+
const actionHint = this.executionIntent?.requiredInteraction === 'computer'
|
|
547
|
+
? `Continue with computer interaction tools (computer_type, computer_hotkey) in the target application.`
|
|
548
|
+
: `Call 'write_file' or 'edit_file' NOW to write the required source code and files directly to disk!`;
|
|
674
549
|
this.historyMessages.push({
|
|
675
550
|
role: 'user',
|
|
676
|
-
content: `URGENT ACTION MANDATE (Step ${step}): You have executed ${this.consecutiveCommands} shell commands in a row without
|
|
551
|
+
content: `URGENT ACTION MANDATE (Step ${step}): You have executed ${this.consecutiveCommands} shell commands in a row without progress! ${actionHint}`,
|
|
677
552
|
});
|
|
678
553
|
}
|
|
679
554
|
}
|
|
@@ -684,122 +559,84 @@ export class AgentExecutionLoop {
|
|
|
684
559
|
}
|
|
685
560
|
systemPrompt() {
|
|
686
561
|
const filesList = getDirectoryFiles(this.cwd).slice(0, 100);
|
|
687
|
-
|
|
562
|
+
const intentInfo = this.executionIntent ? `
|
|
563
|
+
Task Execution Intent & Constraints:
|
|
564
|
+
• Required Applications: ${this.executionIntent.requiredApplications.length > 0 ? this.executionIntent.requiredApplications.join(', ') : 'None specified'}
|
|
565
|
+
• Required Interaction Type: ${this.executionIntent.requiredInteraction}
|
|
566
|
+
• Allow Alternative Tools (e.g. direct filesystem): ${this.executionIntent.allowAlternativeTools ? 'YES' : 'NO (STRICT)'}
|
|
567
|
+
• Target Destination: ${this.executionIntent.targetDestination || 'Default'}
|
|
568
|
+
• Target Artifact: ${this.executionIntent.targetArtifactName || 'None'}
|
|
569
|
+
` : '';
|
|
570
|
+
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
571
|
+
const tierDirective = isPaid ? `
|
|
572
|
+
PLAN TIER & CAPABILITIES: ${this.userTier.toUpperCase()} (FULL ACCESS UNLOCKED)
|
|
573
|
+
• Autonomous Computer-Use, Browser Automation, and AI Scratchpad Reasoning are fully ENABLED.
|
|
574
|
+
• Use the 'scratchpad' tool to organize multi-step reasoning, break down complex goals, draft hypotheses, and track working memory across turns.
|
|
575
|
+
` : `
|
|
576
|
+
PLAN TIER & CAPABILITIES: DEVELOPER FREE (BASIC DEVELOPER OPS ONLY)
|
|
577
|
+
• Scratchpadding and Autonomous Multi-Step Desktop/Browser Control are RESTRICTED to Pro & Enterprise plans.
|
|
578
|
+
• As a Free Tier developer assistant, you ONLY perform basic developer operations:
|
|
579
|
+
1. Code editing: use edit_file or multi_edit_file to modify existing code.
|
|
580
|
+
2. Creating new files & folders: use write_file to create new files and parent directories.
|
|
581
|
+
3. Running commands: use run_command to execute terminal commands, builds, or tests.
|
|
582
|
+
4. Reading & exploring: use read_file, list_dir, grep_search to inspect files.
|
|
583
|
+
• Do NOT attempt to use scratchpad, computer, or browser tools. Execute the requested file edit, file creation, or command directly and call task_completed immediately.
|
|
584
|
+
`;
|
|
585
|
+
return `You are Scout Agent, an elite AI Principal Software Engineer & Autonomous Computer-Use Operator (developed by FrontTerrain).
|
|
688
586
|
|
|
689
|
-
Project Name: "${this.projectName}"
|
|
690
587
|
Working Directory: "${this.cwd}"
|
|
691
588
|
Host OS Platform: "${process.platform}"
|
|
692
589
|
Current Date & Time: "${new Date().toISOString()}" (Current Year: ${new Date().getFullYear()})
|
|
693
|
-
|
|
590
|
+
${tierDirective}
|
|
591
|
+
${intentInfo}
|
|
694
592
|
Workspace Overview (first 100 files):
|
|
695
593
|
${filesList.join('\n')}
|
|
696
594
|
|
|
697
|
-
|
|
698
|
-
1.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
6. COMPLETE FILE CREATION & EDITING: Whenever asked to write, edit, create, implement, or fix code, YOU MUST CREATE / MODIFY ALL NECESSARY FILES required to completely fulfill the prompt (e.g. main source files, component files, styles, configs, tests, documentation). CALL \`write_file\` or \`edit_file\` for EVERY necessary file to save changes directly to disk in the workspace! Always provide a clear \`reason\` argument for why the edit is made.
|
|
705
|
-
7. RUN & VERIFY COMMANDS: Execute shell commands, tests, builds, and formatters using \`run_command\`. If a command or build fails, read the full error log, diagnose the root cause, and auto-fix the failing code immediately.
|
|
706
|
-
8. NO CODE TRUNCATION OR PLACEHOLDERS: Maintain 100% code integrity. NEVER replace existing code or HTML tags with placeholder comments (e.g. \`<!-- remaining content -->\` or \`// TODO: rest of code\`) or stripped skeleton structures. Always output complete, functional code.
|
|
707
|
-
9. REFORMATTING MINIFIED / LARGE FILES: When asked to format, beautify, or un-minify HTML, JS, CSS, or JSON files, DO NOT attempt to rewrite the file content manually using \`write_file\` or \`edit_file\` (which causes token truncation and content loss). ALWAYS use \`run_command\` to execute CLI formatters (e.g. \`npx prettier --write <file>\`, \`npx html-beautify -r <file>\`, \`npx js-beautify <file>\`, or run a quick Node script) to reformat files in-place on disk.
|
|
708
|
-
10. FROM-SCRATCH PROJECT CREATION & AUTOMATED TESTING PROTOCOL: When asked to build/create a project from scratch:
|
|
709
|
-
a. Scaffold Architecture: Create configuration/manifest files (\`package.json\`, \`tsconfig.json\`, \`pyproject.toml\`, etc.) and folder structure using \`write_file\` or \`run_command\`.
|
|
710
|
-
b. Write Source Implementation: Create all necessary source files with fully functional, complete, production-ready code (no skeleton placeholders or TODOs).
|
|
711
|
-
c. Write Automated Tests: Create comprehensive unit/integration test suite files (e.g. \`test/*.test.ts\`, \`tests/test_*.py\`, etc.).
|
|
712
|
-
d. Execute Tests & Build Verification: Run test suites and builds via \`run_command\` (e.g. \`npm test\`, \`npx vitest run\`, \`pytest\`, \`cargo test\`, \`go test ./...\`).
|
|
713
|
-
e. Self-Heal & Pass: If tests or builds fail, inspect output logs, edit files using \`edit_file\` or \`write_file\` to resolve errors, re-run tests until 100% passing, and only call \`task_completed\` when all checks pass cleanly.
|
|
714
|
-
11. CRITICAL TERMINATION RULE: As soon as you have finished creating/modifying the necessary files, running verification checks, or answering the user request, YOU MUST CALL \`task_completed\` tool immediately with a clear summary of your work.
|
|
715
|
-
12. PRODUCTION-GRADE CODE QUALITY: Every line of code you write or edit MUST be clean, elegant, modular, production-ready, highly readable, self-documenting, formatted, and strictly typed.
|
|
716
|
-
13. HIGH AUTONOMY & NO DOUBLE PROMPTING: NEVER call \`ask_user\` to ask what code to write, what features to implement, or to re-confirm the prompt. Take immediate autonomous action using \`write_file\`, \`edit_file\`, \`run_command\`, \`read_file\`, or \`grep_search\` using best software engineering practices. ONLY call \`ask_user\` if a critical secret credential (API key/password) is strictly required.
|
|
717
|
-
14. EDITING & DEBUGGING MASTERY PROTOCOL:
|
|
718
|
-
a. Full Context Reading: When editing or fixing bugs in existing files, inspect full code context. For files under 250 lines, reference the complete file content before modifying.
|
|
719
|
-
b. Guaranteed Edit Success: Use \`edit_file\` for precise snippet updates. If \`edit_file\` returns a target snippet mismatch error, IMMEDIATELY call \`write_file\` with the complete corrected file code so the edit is 100% applied without getting stuck!
|
|
720
|
-
c. Automated Error Diagnostics: After editing, check compiler/syntax outputs and test runner logs. If an error is detected, auto-heal the code immediately before concluding.
|
|
721
|
-
15. THOUGHT TRANSPARENCY PROTOCOL: In EVERY step before calling any tools or ending a turn, you MUST provide a clear 1-2 sentence explanation in your text response describing your current reasoning, what file/action you are taking, and why you are taking it.
|
|
722
|
-
16. OS-COMPATIBLE SHELL COMMANDS: Always ensure shell commands passed to \`run_command\` are compatible with the host OS ("${process.platform}"). On Windows (\`win32\`), do NOT use Linux-only builtins like \`touch\`, \`rm -rf\`, \`cat\`, or \`ls -la\` directly. Use \`write_file\` to create files, standard \`npm\`/\`npx\`/\`node\`/\`git\`/\`python\` commands, or PowerShell syntax.
|
|
723
|
-
17. STRICT USER INSTRUCTION & DIRECTIVE ALIGNMENT: You MUST strictly follow all exact user instructions, quantitative constraints, file paths, feature requirements, and architectural preferences specified in the user prompt. Execute the request step-by-step with zero deviation or hallucinated shortcuts, ensuring complete implementation within the allowed step limit.
|
|
724
|
-
18. DIRECT FILE CREATION VIA write_file ONLY (NO SHELL COMMANDS): Do NOT use \`run_command\` with \`touch\`, \`mkdir\`, \`echo > file\`, \`cat > file\`, or shell scripts to create files or folders! ALWAYS call \`write_file\` or \`edit_file\` directly to write source code to disk. The \`write_file\` tool automatically creates all necessary parent directories in one step.
|
|
725
|
-
19. EXTERNAL APP TAKEOVER & CONTROL PROTOCOL: When requested by user prompt to open, take over, or work inside external applications (browser, terminal, VS Code, Notepad, social apps like Instagram, WhatsApp, Twitter/X, Telegram, or custom apps):
|
|
726
|
-
a. Launch App: Use \`open_app\` to launch or focus the target application with optional URL, file path, or initial script.
|
|
727
|
-
b. Social DM & Messaging Automation: For Instagram, WhatsApp, Twitter/X, or Telegram messaging requests (e.g. "open instagram and send message to @user"), immediately invoke \`app_action\` with action "send_dm" or "open_dm" (or \`open_app\`) specifying the target username/phone and message text so the agent automatically opens the direct messaging link in the browser!
|
|
728
|
-
c. Email & Gmail Automation: When requested to send or compose an email (e.g. "send mail to frontterrain@gmail.com saying...", "takeover mail.google.com and send mail"):
|
|
729
|
-
- Immediately invoke \`app_action\` with \`action: "send_mail"\` (or \`action: "send_dm"\`) specifying the target recipient email and body text. The agent automatically constructs the direct Gmail compose URL (\`https://mail.google.com/mail/?view=cm&fs=1&to=<recipient>&su=<subject>&body=<body>\`) which pre-populates the compose window and dispatches the email via Ctrl+Enter!
|
|
730
|
-
- Alternatively, navigate directly to \`https://mail.google.com/mail/?view=cm&fs=1&to=<recipient>&su=...&body=...\` and trigger hotkey \`app_action(action: "key_combo", payload: { keyCombo: "ctrl+enter" })\`. NEVER click blind coordinates like (20, 20) in a web browser!
|
|
731
|
-
d. Work Inside App: Use \`app_action\` or \`run_command\` to execute actions inside the app context (e.g. fetching browser page content, running commands inside terminal, searching web, opening files in editor).
|
|
732
|
-
e. Universal Gaming & External Application Protocol:
|
|
733
|
-
- Desktop Game/App Launch: To open or play ANY installed game or app on the PC (Steam games, Epic Games, Minecraft, Roblox, Discord, Spotify, etc.), call open_app("<game_name>"). The universal OS launcher automatically resolves installed Windows/macOS applications and store packages via system registry and launches them directly!
|
|
734
|
-
- Web Game Play: If the game is web-based (e.g. Chess.com, Slither.io, 2048, Poki) or not installed locally, NEVER guess speculative URL subpaths! Use app_action(app: "browser", action: "search", payload: { query: "<game_name> play online official" }) or navigate to the official domain homepage to find the verified play URL.
|
|
735
|
-
- Autonomous Gameplay Progression Loop:
|
|
736
|
-
1. Window Focus: Use app_action(action: "focus_window", payload: { app: "<game_name>" }) to bring the window front-and-center.
|
|
737
|
-
2. Screen Observation: Use app_action(action: "see_screen") to inspect the UI, loading screen, or active state.
|
|
738
|
-
3. Menu Traversal: Click menu buttons ("Play", "Start Game", "New Game", "Continue") using app_action(action: "click_element", payload: { element: "Play" }) .
|
|
739
|
-
4. Interactive Controls: Send gameplay controls using app_action(action: "send_keys" / "type_text" / "key_combo") with standard gaming keys (WASD, Arrow keys, Space, Enter, Escape, mouse clicks/drags).
|
|
740
|
-
5. Conclude task with task_completed when the requested gameplay actions or objectives are achieved.
|
|
741
|
-
20. DESKTOP APP TAKEOVER & AUTOMATION PROTOCOL:
|
|
742
|
-
a. STEP 1 SCREEN TAKEOVER: Whenever the user goal asks to take over the screen, control the desktop, or automate an external app (e.g. 'take over screen', 'open maps and click', 'take over desktop', 'open notepad'):
|
|
743
|
-
- Your VERY FIRST tool call in Step 1 MUST be \`app_action\` with \`action: "takeover"\` (e.g. \`app: "desktop"\` or the target app).
|
|
744
|
-
- Calling \`app_action\` with \`action: "takeover"\` immediately activates the full-screen sky-blue aura HUD, displays the warning banner "⚡ Scout is on the screen.", blocks external input interruptions, and takes over the mouse cursor!
|
|
745
|
-
- Then immediately proceed to launch/navigate with \`open_app\` or \`app_action\` (\`action: "click_element"\` / \`action: "click_app"\` / \`action: "type_text"\` / \`action: "send_keys"\`).
|
|
746
|
-
b. LIVE SCREEN SHARE & VISION STREAM DIRECTIVE: You have direct real-time live screen sharing with the model! Instead of taking, saving, and sending static screenshot files, stream the screen live in real-time or inspect the live screen share stream using \`app_action\` with \`action: "live_screen_share"\`, \`"start_screen_share"\`, \`"live_screen_stream"\`, \`"see_screen"\`, or \`"analyze_screen"\`. Screen frames are streamed in-memory directly to the model (zero temporary files written to disk).
|
|
747
|
-
c. VISUAL ELEMENT GROUNDING (click_element): Instead of guessing blind coordinates (x, y), click buttons, inputs, or menus by descriptive label using \`app_action(action: "click_element", payload: { element: "Search" })\`. Vision AI and native OS UI automation will locate the element and click it accurately.
|
|
748
|
-
d. WINDOW & SCROLL CONTROLS: Use \`app_action(action: "scroll", payload: { direction: "down", amount: 4 })\` to scroll pages. Use \`app_action(action: "list_windows")\` to see all open windows, and \`app_action(action: "focus_window", payload: { app: "chrome" })\` to bring a window front-and-center.
|
|
749
|
-
e. CLI Credential Input Prompt: If an application requires login credentials, passwords, 2FA codes, or secret tokens to proceed, call \`ask_user\` tool with a clear prompt. This presents a secure, interactive input bar directly in the user's running terminal CLI. Once the user enters the secret, take the received input, inject it into the target application window via \`app_action\` (\`action: "type_text"\`).
|
|
750
|
-
21. MANDATORY TAKEOVER TOOL INVOCATION MANDATE: Whenever the user goal requests to open, launch, take over, click, type, or interact with an external app or desktop screen (e.g. 'take over browser and open website', 'open notepad and type', 'take over desktop', 'take over screen'):
|
|
751
|
-
YOU MUST CALL \`app_action(action: "takeover")\` IN STEP 1. Then call \`open_app\` and \`app_action\` (\`click_element\`, \`click_app\`, \`type_text\`, \`send_keys\`). DO NOT call \`read_file\` or \`write_file\` for workspace code files when asked to take over external desktop apps! The takeover tool call is MANDATORY for executing the physical takeover.
|
|
752
|
-
22. BROWSER DIRECT URL NAVIGATION MANDATE: When asked to open or navigate to a specific website or web app (e.g. Apple Maps, GitHub, YouTube, etc.), NEVER call Google Search or issue repeated \`app_action: search\` calls with text queries! IMMEDIATELY pass the exact URL (e.g. "https://maps.apple.com") to \`open_app(app: "browser", target: "https://maps.apple.com")\` or \`app_action(app: "browser", action: "navigate", payload: { url: "https://maps.apple.com" })\`. Direct URL navigation must always target the exact site URL directly without putting queries into Google Search!
|
|
753
|
-
23. WORKING MEMORY & STRUCTURED SCRATCHPAD PROTOCOL:
|
|
754
|
-
Like Antigravity and leading autonomous agents, maintain disciplined working memory. In EVERY step, start your response with a structured <scratchpad> reasoning block before returning tool calls:
|
|
755
|
-
\`\`\`markdown
|
|
756
|
-
<scratchpad>
|
|
757
|
-
Thought: [1-2 sentences on what you are doing on this turn and why]
|
|
758
|
-
Plan:
|
|
759
|
-
[x] 1. [Completed step]
|
|
760
|
-
[/] 2. [In-progress step]
|
|
761
|
-
[ ] 3. [Next upcoming step]
|
|
762
|
-
Observations: [What you learned from the last tool result or screen capture]
|
|
763
|
-
Next Action: [The exact tool you are invoking now]
|
|
764
|
-
</scratchpad>
|
|
765
|
-
\`\`\`
|
|
766
|
-
This keeps your reasoning crystal-clear, ensures plan progression, and syncs automatically with .ft/scratchpad.md.
|
|
595
|
+
CORE ARCHITECTURAL PRINCIPLES:
|
|
596
|
+
1. USER INTENT > TOOL CONVENIENCE (CRITICAL MANDATE):
|
|
597
|
+
The easiest tool is NOT necessarily the correct tool. Scout optimizes for completing the user's ACTUAL REQUESTED WORKFLOW, not merely producing something that looks like the final artifact.
|
|
598
|
+
- Distinguish OUTCOME requirements (e.g. "Create a file containing X") from EXECUTION requirements (e.g. "Open Notepad, type X, save it as Y on Desktop").
|
|
599
|
+
- When the user explicitly specifies an application (e.g. Notepad, Excel, Chrome, VS Code) or computer interaction method, computer interaction tools (computer_open_app, computer_screenshot, computer_type, computer_hotkey, computer_switch_window, computer_click) MUST be used.
|
|
600
|
+
- NEVER silently substitute direct filesystem API calls (write_file) when computer/application interaction is required, unless the user explicitly permits alternative tools ("using any method you prefer").
|
|
601
|
+
- NEVER assume the repository root is the user's Desktop! If the user requests saving to Desktop, the file MUST be saved to the actual Desktop through the requested workflow.
|
|
767
602
|
|
|
768
|
-
|
|
769
|
-
a
|
|
770
|
-
|
|
603
|
+
2. COMPUTER ACTION FAILURE RECOVERY:
|
|
604
|
+
When a computer action fails or encounters a problem:
|
|
605
|
+
DO NOT immediately switch to an unrelated tool (like write_file).
|
|
606
|
+
Instead, follow the bounded computer recovery loop:
|
|
607
|
+
ACTION FAILED ➔ OBSERVE SCREEN (computer_screenshot / computer_inspect_ui) ➔ UNDERSTAND FAILURE (check if application is active/focused) ➔ RETRY / ALTERNATIVE COMPUTER ACTION (switch window, click text area, send hotkey/keystrokes) ➔ OBSERVE ➔ CONTINUE.
|
|
608
|
+
Only after repeated bounded computer recovery attempts fail should you report the failure to the user.
|
|
771
609
|
|
|
772
|
-
|
|
610
|
+
3. ACTIVE APPLICATION TRACKING:
|
|
611
|
+
Before sending keyboard or mouse input (computer_type, computer_click, computer_hotkey), ensure that the expected application (e.g. Notepad) is the active foreground window. If not, use computer_switch_window or computer_open_app to focus it first.
|
|
612
|
+
|
|
613
|
+
4. REAL-TIME DATE AWARENESS: You are aware of real-time date and time (${new Date().getFullYear()}). Always use the current year (${new Date().getFullYear()}) and date for web searches, documentation, commit messages, and references. NEVER hardcode obsolete past years like 2024.
|
|
614
|
+
5. HIGH AGENCY & AUTONOMY: You act autonomously through step-by-step tool invocation to solve complex coding tasks, debug issues, build features, create projects from scratch, or operate desktop applications.
|
|
615
|
+
6. MULTILINGUAL & INTENT COMPREHENSION: Understand user intent in any language (English, Hinglish like "likho", "bnao", "code karo", "fix karo", "samjha do", Hindi, Spanish, etc.).
|
|
616
|
+
7. FOR CODE & REPO TASKS: When the task is a coding, project refactoring, or repository maintenance task, use direct code modification tools (write_file, edit_file, run_command, read_file, grep_search).
|
|
617
|
+
8. RUN & VERIFY COMMANDS: Execute shell commands, tests, builds, and formatters using run_command.
|
|
618
|
+
9. CRITICAL TERMINATION RULE: As soon as you have finished creating/modifying the necessary files, running verification checks, or completing the desktop application workflow, YOU MUST CALL task_completed tool immediately with a clear summary of your work.
|
|
619
|
+
10. THOUGHT TRANSPARENCY PROTOCOL: In EVERY step before calling any tools or ending a turn, you MUST provide a clear 1-2 sentence explanation in your text response describing your current reasoning, what file/action you are taking, and why you are taking it.
|
|
773
620
|
|
|
774
621
|
When returning tool calls, use standard OpenAI function calling format or JSON tool call payload format:
|
|
775
622
|
\`\`\`json
|
|
776
623
|
{
|
|
777
|
-
|
|
778
|
-
|
|
624
|
+
"tool": "tool_name",
|
|
625
|
+
"args": { ... }
|
|
779
626
|
}
|
|
780
627
|
\`\`\`
|
|
781
628
|
`.trim();
|
|
782
629
|
}
|
|
783
|
-
getScratchpadPromptContext() {
|
|
784
|
-
if (this.scratchpadState.plan.length === 0 && !this.scratchpadState.notes) {
|
|
785
|
-
return '';
|
|
786
|
-
}
|
|
787
|
-
let str = `ACTIVE SCRATCHPAD STATE (.ft/scratchpad.md):\n`;
|
|
788
|
-
if (this.scratchpadState.plan.length > 0) {
|
|
789
|
-
str += `Plan Checklist:\n` +
|
|
790
|
-
this.scratchpadState.plan.map((item, idx) => ` ${this.scratchpadState.completedSteps.includes(idx) ? '[✓]' : '[ ]'} Step ${idx + 1}: ${item}`).join('\n') + '\n';
|
|
791
|
-
}
|
|
792
|
-
if (this.scratchpadState.notes) {
|
|
793
|
-
str += `Working Notes: ${this.scratchpadState.notes}\n`;
|
|
794
|
-
}
|
|
795
|
-
return str;
|
|
796
|
-
}
|
|
797
630
|
async executeGoal(userGoal) {
|
|
798
631
|
this.resetContext();
|
|
632
|
+
this.executionIntent = inferExecutionIntent(userGoal);
|
|
799
633
|
this.taskStateManager.setObjective(userGoal);
|
|
634
|
+
if (this.computerController.setExpectedApplication && this.executionIntent.requiredApplications.length > 0) {
|
|
635
|
+
this.computerController.setExpectedApplication(this.executionIntent.requiredApplications[0] ?? null);
|
|
636
|
+
}
|
|
800
637
|
const structuredPlan = this.taskPlanner.createPlan(userGoal);
|
|
801
638
|
this.taskStateManager.setPlan(structuredPlan.steps);
|
|
802
|
-
this.taskStateManager.setAvailableTools(this.toolRegistry.
|
|
639
|
+
this.taskStateManager.setAvailableTools(this.toolRegistry.getToolsForTier(this.userTier).map((t) => t.name));
|
|
803
640
|
if (this.mockMode) {
|
|
804
641
|
for (let i = 1; i <= structuredPlan.steps.length; i++) {
|
|
805
642
|
this.taskStateManager.startStep(i);
|
|
@@ -926,233 +763,222 @@ When returning tool calls, use standard OpenAI function calling format or JSON t
|
|
|
926
763
|
let step = 0;
|
|
927
764
|
let finalSummary = '';
|
|
928
765
|
const stepDurations = [];
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
766
|
+
while (step < this.maxSteps) {
|
|
767
|
+
if (this.isCancelled) {
|
|
768
|
+
return {
|
|
769
|
+
success: false,
|
|
770
|
+
summary: `Task cancelled: ${this.cancelReason || 'User abort'}`,
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
step++;
|
|
774
|
+
this.currentStep = step;
|
|
775
|
+
this.taskStateManager.startStep(step);
|
|
776
|
+
const stepStartTime = Date.now();
|
|
777
|
+
const avgStepMs = stepDurations.length > 0
|
|
778
|
+
? stepDurations.reduce((a, b) => a + b, 0) / stepDurations.length
|
|
779
|
+
: 12000;
|
|
780
|
+
const remainingSteps = (this.maxSteps - step + 1);
|
|
781
|
+
const estSecsLeft = Math.max(5, Math.round((remainingSteps * avgStepMs) / 1000));
|
|
782
|
+
const estLeftStr = estSecsLeft >= 60
|
|
783
|
+
? `${Math.floor(estSecsLeft / 60)}m ${estSecsLeft % 60}s`
|
|
784
|
+
: `${estSecsLeft}s`;
|
|
785
|
+
const stepSpinner = spinner();
|
|
786
|
+
stepSpinner.start(chalk.cyan(`Scout is Working (Step ${step}/${this.maxSteps} • Est. completion: ~${estLeftStr} left)`));
|
|
787
|
+
try {
|
|
788
|
+
let response;
|
|
945
789
|
try {
|
|
946
|
-
|
|
947
|
-
|
|
790
|
+
response = await callOpenAIWithRetry(async (model) => {
|
|
791
|
+
return await openai.chat.completions.create({
|
|
792
|
+
model,
|
|
793
|
+
messages: sanitizeMessages(this.historyMessages),
|
|
794
|
+
tools: this.toolRegistry.getToolsForTier(this.userTier).map((t) => ({ type: 'function', function: t })),
|
|
795
|
+
tool_choice: 'auto',
|
|
796
|
+
temperature: 0.1,
|
|
797
|
+
presence_penalty: 0.1,
|
|
798
|
+
frequency_penalty: 0.1,
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
catch (err) {
|
|
803
|
+
const errStr = String(err?.message || err?.error || err || '').toLowerCase();
|
|
804
|
+
if (errStr.includes('tool') || errStr.includes('400') || errStr.includes('not supported') || errStr.includes('reasoning')) {
|
|
805
|
+
// Model doesn't support native function calling — inject tool-call formatting hint
|
|
806
|
+
// so extractJsonToolCall can parse the response as a structured tool invocation
|
|
807
|
+
const toolHintMsg = {
|
|
808
|
+
role: 'user',
|
|
809
|
+
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:
|
|
810
|
+
\`\`\`json
|
|
811
|
+
{ "tool": "tool_name", "args": { ... } }
|
|
812
|
+
\`\`\`
|
|
813
|
+
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.
|
|
814
|
+
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!`,
|
|
815
|
+
};
|
|
816
|
+
const fallbackMessages = [...this.historyMessages, toolHintMsg];
|
|
948
817
|
response = await callOpenAIWithRetry(async (model) => {
|
|
949
818
|
return await openai.chat.completions.create({
|
|
950
819
|
model,
|
|
951
|
-
messages: sanitizeMessages(
|
|
952
|
-
tools: this.toolRegistry.getAllTools().map((t) => ({ type: 'function', function: t })),
|
|
953
|
-
tool_choice: 'auto',
|
|
820
|
+
messages: sanitizeMessages(fallbackMessages),
|
|
954
821
|
temperature: 0.1,
|
|
955
822
|
presence_penalty: 0.1,
|
|
956
823
|
frequency_penalty: 0.1,
|
|
957
824
|
});
|
|
958
825
|
});
|
|
959
826
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
if (errStr.includes('tool') || errStr.includes('400') || errStr.includes('not supported') || errStr.includes('reasoning')) {
|
|
963
|
-
// Model doesn't support native function calling — inject tool-call formatting hint
|
|
964
|
-
// so extractJsonToolCall can parse the response as a structured tool invocation
|
|
965
|
-
const toolHintMsg = {
|
|
966
|
-
role: 'user',
|
|
967
|
-
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:
|
|
968
|
-
\`\`\`json
|
|
969
|
-
{ "tool": "tool_name", "args": { ... } }
|
|
970
|
-
\`\`\`
|
|
971
|
-
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, open_app, app_action, agent_scratchpad, ask_user, speak_text, task_completed.
|
|
972
|
-
For app takeover/control use: { "tool": "app_action", "args": { "app": "browser", "action": "takeover" } }
|
|
973
|
-
For opening apps use: { "tool": "open_app", "args": { "app": "chrome", "target": "https://..." } }
|
|
974
|
-
For clicking use: { "tool": "app_action", "args": { "app": "desktop", "action": "click_app", "payload": { "x": 500, "y": 300 } } }
|
|
975
|
-
For typing text use: { "tool": "app_action", "args": { "app": "browser", "action": "type_text", "payload": { "text": "...", "enter": true } } }
|
|
976
|
-
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!`,
|
|
977
|
-
};
|
|
978
|
-
const fallbackMessages = [...this.historyMessages, toolHintMsg];
|
|
979
|
-
response = await callOpenAIWithRetry(async (model) => {
|
|
980
|
-
return await openai.chat.completions.create({
|
|
981
|
-
model,
|
|
982
|
-
messages: sanitizeMessages(fallbackMessages),
|
|
983
|
-
temperature: 0.1,
|
|
984
|
-
presence_penalty: 0.1,
|
|
985
|
-
frequency_penalty: 0.1,
|
|
986
|
-
});
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
else {
|
|
990
|
-
throw err;
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
const choice = response.choices[0];
|
|
994
|
-
if (!choice) {
|
|
995
|
-
stepSpinner.stop(chalk.yellow('No response from Scout. Retrying step...'));
|
|
996
|
-
continue;
|
|
827
|
+
else {
|
|
828
|
+
throw err;
|
|
997
829
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
830
|
+
}
|
|
831
|
+
const choice = response.choices[0];
|
|
832
|
+
if (!choice) {
|
|
833
|
+
stepSpinner.stop(chalk.yellow('No response from Scout. Retrying step...'));
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
const msg = sanitizeMessage(choice.message);
|
|
837
|
+
this.historyMessages.push(msg);
|
|
838
|
+
// Render assistant's thought reasoning text if provided on this turn
|
|
839
|
+
const textContent = msg.content || '';
|
|
840
|
+
if (textContent.trim()) {
|
|
841
|
+
safeNote(renderMarkdown(textContent), `Scout Thought (Step ${step})`);
|
|
842
|
+
}
|
|
843
|
+
// Check native tool calls
|
|
844
|
+
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
845
|
+
stepSpinner.stop(chalk.green(`Step ${step}: Scout issued ${msg.tool_calls.length} tool call(s).`));
|
|
846
|
+
for (const tc of msg.tool_calls) {
|
|
847
|
+
if (tc.type === 'function' && tc.function) {
|
|
848
|
+
const fnName = tc.function.name;
|
|
849
|
+
let args = {};
|
|
850
|
+
try {
|
|
851
|
+
args = JSON.parse(tc.function.arguments || '{}');
|
|
1011
852
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
role: 'user',
|
|
1031
|
-
tool_call_id: tc.id,
|
|
1032
|
-
content: `Tool Execution Result (${fnName}):\n${toolResult.result}`,
|
|
1033
|
-
});
|
|
1034
|
-
this.handleToolConsecutiveTracking(fnName, args, step);
|
|
1035
|
-
if (fnName === 'task_completed') {
|
|
1036
|
-
finalSummary = args.summary || toolResult.result;
|
|
1037
|
-
if (this.modifiedFiles.size > 0) {
|
|
1038
|
-
const filesList = getDirectoryFiles(this.cwd);
|
|
1039
|
-
const healRes = await verifyAndSelfHealFiles(Array.from(this.modifiedFiles), this.cwd, this.projectName, filesList, { maxRetries: 3 });
|
|
1040
|
-
if (healRes.verifiedFiles.length > 0) {
|
|
1041
|
-
safeNote(chalk.green(` Self-Healing Verification Confirmed: ${healRes.verifiedFiles.length} file(s) syntax & build clean!`), ' Code Verification Clean');
|
|
1042
|
-
}
|
|
1043
|
-
if (healRes.remainingErrors.length > 0) {
|
|
1044
|
-
safeNote(chalk.yellow(`️ Remaining verification issues:\n${healRes.remainingErrors.join('\n')}`), '️ Verification Warning');
|
|
1045
|
-
}
|
|
853
|
+
catch { }
|
|
854
|
+
const toolResult = await this.dispatchToolCall(fnName, args);
|
|
855
|
+
this.historyMessages.push({
|
|
856
|
+
role: 'user',
|
|
857
|
+
tool_call_id: tc.id,
|
|
858
|
+
content: `Tool Execution Result (${fnName}):\n${toolResult.result}`,
|
|
859
|
+
});
|
|
860
|
+
this.handleToolConsecutiveTracking(fnName, args, step);
|
|
861
|
+
if (fnName === 'task_completed') {
|
|
862
|
+
finalSummary = args.summary || toolResult.result;
|
|
863
|
+
if (this.modifiedFiles.size > 0) {
|
|
864
|
+
const filesList = getDirectoryFiles(this.cwd);
|
|
865
|
+
const healRes = await verifyAndSelfHealFiles(Array.from(this.modifiedFiles), this.cwd, path.basename(this.cwd), filesList, { maxRetries: 3 });
|
|
866
|
+
if (healRes.verifiedFiles.length > 0) {
|
|
867
|
+
safeNote(chalk.green(` Self-Healing Verification Confirmed: ${healRes.verifiedFiles.length} file(s) syntax & build clean!`), ' Code Verification Clean');
|
|
868
|
+
}
|
|
869
|
+
if (healRes.remainingErrors.length > 0) {
|
|
870
|
+
safeNote(chalk.yellow(`️ Remaining verification issues:\n${healRes.remainingErrors.join('\n')}`), '️ Verification Warning');
|
|
1046
871
|
}
|
|
1047
|
-
saveAgentSession({
|
|
1048
|
-
goal: userGoal,
|
|
1049
|
-
summary: finalSummary,
|
|
1050
|
-
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1051
|
-
});
|
|
1052
|
-
return { success: true, summary: finalSummary };
|
|
1053
872
|
}
|
|
873
|
+
saveAgentSession({
|
|
874
|
+
goal: userGoal,
|
|
875
|
+
summary: finalSummary,
|
|
876
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
877
|
+
});
|
|
878
|
+
return { success: true, summary: finalSummary };
|
|
1054
879
|
}
|
|
1055
880
|
}
|
|
1056
|
-
continue;
|
|
1057
|
-
}
|
|
1058
|
-
// Check text response or fallback JSON tool call
|
|
1059
|
-
stepSpinner.stop(chalk.blue(`Step ${step} thinking complete.`));
|
|
1060
|
-
const parsedJsonTool = this.extractJsonToolCall(textContent);
|
|
1061
|
-
if (parsedJsonTool) {
|
|
1062
|
-
const toolResult = await this.dispatchToolCall(parsedJsonTool.tool, parsedJsonTool.args);
|
|
1063
|
-
this.historyMessages.push({
|
|
1064
|
-
role: 'user',
|
|
1065
|
-
content: `Tool Execution Result (${parsedJsonTool.tool}):\n${toolResult.result}`,
|
|
1066
|
-
});
|
|
1067
|
-
this.handleToolConsecutiveTracking(parsedJsonTool.tool, parsedJsonTool.args, step);
|
|
1068
|
-
if (parsedJsonTool.tool === 'task_completed') {
|
|
1069
|
-
finalSummary = parsedJsonTool.args?.summary || toolResult.result;
|
|
1070
|
-
saveAgentSession({
|
|
1071
|
-
goal: userGoal,
|
|
1072
|
-
summary: finalSummary,
|
|
1073
|
-
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1074
|
-
});
|
|
1075
|
-
return { success: true, summary: finalSummary };
|
|
1076
|
-
}
|
|
1077
|
-
continue;
|
|
1078
|
-
}
|
|
1079
|
-
if (textContent.trim()) {
|
|
1080
|
-
let cleanedThought = textContent
|
|
1081
|
-
.replace(/[\u0600-\u06FF\u0750-\u077F\uAC00-\uD7AF\u3040-\u30FF\u4E00-\u9FFF\u0D80-\u0DFF]+/g, '')
|
|
1082
|
-
.trim();
|
|
1083
|
-
if (!cleanedThought || cleanedThought.length < 5) {
|
|
1084
|
-
cleanedThought = 'Analyzing codebase files and executing next tool operation...';
|
|
1085
|
-
}
|
|
1086
|
-
safeNote(renderMarkdown(cleanedThought), ` Scout Agent Thought (Step ${step})`);
|
|
1087
|
-
const lowerText = textContent.toLowerCase();
|
|
1088
|
-
const isExplicitCompletion = lowerText.includes('task is complete') ||
|
|
1089
|
-
lowerText.includes('task complete') ||
|
|
1090
|
-
lowerText.includes('goal completed') ||
|
|
1091
|
-
lowerText.includes('goal is completed') ||
|
|
1092
|
-
lowerText.includes('all tasks completed') ||
|
|
1093
|
-
lowerText.includes('i have completed') ||
|
|
1094
|
-
lowerText.includes('no further changes needed') ||
|
|
1095
|
-
lowerText.includes('the fix is complete') ||
|
|
1096
|
-
lowerText.includes('has been created') ||
|
|
1097
|
-
lowerText.includes('successfully created') ||
|
|
1098
|
-
lowerText.includes('created the file') ||
|
|
1099
|
-
lowerText.includes('file created') ||
|
|
1100
|
-
lowerText.includes('implementation complete') ||
|
|
1101
|
-
lowerText.includes('work is complete');
|
|
1102
|
-
// If explicit completion phrase found, OR files have already been modified and assistant returned a final summary without calling tools
|
|
1103
|
-
if (isExplicitCompletion || (this.modifiedFiles.size > 0 && !lowerText.includes('?') && textContent.length > 50)) {
|
|
1104
|
-
saveAgentSession({
|
|
1105
|
-
goal: userGoal,
|
|
1106
|
-
summary: textContent,
|
|
1107
|
-
modifiedFiles: Array.from(this.modifiedFiles),
|
|
1108
|
-
});
|
|
1109
|
-
return { success: true, summary: textContent };
|
|
1110
|
-
}
|
|
1111
881
|
}
|
|
1112
|
-
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
// Check text response or fallback JSON tool call
|
|
885
|
+
stepSpinner.stop(chalk.blue(`Step ${step} thinking complete.`));
|
|
886
|
+
const parsedJsonTool = this.extractJsonToolCall(textContent);
|
|
887
|
+
if (parsedJsonTool) {
|
|
888
|
+
const toolResult = await this.dispatchToolCall(parsedJsonTool.tool, parsedJsonTool.args);
|
|
1113
889
|
this.historyMessages.push({
|
|
1114
890
|
role: 'user',
|
|
1115
|
-
content:
|
|
891
|
+
content: `Tool Execution Result (${parsedJsonTool.tool}):\n${toolResult.result}`,
|
|
1116
892
|
});
|
|
1117
|
-
|
|
893
|
+
this.handleToolConsecutiveTracking(parsedJsonTool.tool, parsedJsonTool.args, step);
|
|
894
|
+
if (parsedJsonTool.tool === 'task_completed') {
|
|
895
|
+
finalSummary = parsedJsonTool.args?.summary || toolResult.result;
|
|
896
|
+
saveAgentSession({
|
|
897
|
+
goal: userGoal,
|
|
898
|
+
summary: finalSummary,
|
|
899
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
900
|
+
});
|
|
901
|
+
return { success: true, summary: finalSummary };
|
|
902
|
+
}
|
|
903
|
+
continue;
|
|
1118
904
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
905
|
+
if (textContent.trim()) {
|
|
906
|
+
let cleanedThought = textContent
|
|
907
|
+
.replace(/[\u0600-\u06FF\u0750-\u077F\uAC00-\uD7AF\u3040-\u30FF\u4E00-\u9FFF\u0D80-\u0DFF]+/g, '')
|
|
908
|
+
.trim();
|
|
909
|
+
if (!cleanedThought || cleanedThought.length < 5) {
|
|
910
|
+
cleanedThought = 'Analyzing codebase files and executing next tool operation...';
|
|
911
|
+
}
|
|
912
|
+
safeNote(renderMarkdown(cleanedThought), ` Scout Agent Thought (Step ${step})`);
|
|
913
|
+
const lowerText = textContent.toLowerCase();
|
|
914
|
+
const isExplicitCompletion = lowerText.includes('task is complete') ||
|
|
915
|
+
lowerText.includes('task complete') ||
|
|
916
|
+
lowerText.includes('goal completed') ||
|
|
917
|
+
lowerText.includes('goal is completed') ||
|
|
918
|
+
lowerText.includes('all tasks completed') ||
|
|
919
|
+
lowerText.includes('i have completed') ||
|
|
920
|
+
lowerText.includes('no further changes needed') ||
|
|
921
|
+
lowerText.includes('the fix is complete') ||
|
|
922
|
+
lowerText.includes('has been created') ||
|
|
923
|
+
lowerText.includes('successfully created') ||
|
|
924
|
+
lowerText.includes('created the file') ||
|
|
925
|
+
lowerText.includes('file created') ||
|
|
926
|
+
lowerText.includes('implementation complete') ||
|
|
927
|
+
lowerText.includes('work is complete');
|
|
928
|
+
// If explicit completion phrase found, OR files have already been modified and assistant returned a final summary without calling tools
|
|
929
|
+
if (isExplicitCompletion || (this.modifiedFiles.size > 0 && !lowerText.includes('?') && textContent.length > 50)) {
|
|
930
|
+
saveAgentSession({
|
|
931
|
+
goal: userGoal,
|
|
932
|
+
summary: textContent,
|
|
933
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
934
|
+
});
|
|
935
|
+
return { success: true, summary: textContent };
|
|
1132
936
|
}
|
|
1133
|
-
stepSpinner.stop(chalk.red(`Step ${step} execution error: ${err?.message || String(err)}`));
|
|
1134
|
-
this.historyMessages.push({
|
|
1135
|
-
role: 'user',
|
|
1136
|
-
content: `Error in previous turn: ${err?.message || String(err)}. Please try alternative steps or call tools.`,
|
|
1137
|
-
});
|
|
1138
937
|
}
|
|
1139
|
-
// If
|
|
1140
|
-
|
|
938
|
+
// If assistant responded with text without calling tools, prompt it to execute tools to complete the goal
|
|
939
|
+
this.historyMessages.push({
|
|
940
|
+
role: 'user',
|
|
941
|
+
content: 'You provided a text response but have not called any tools (write_file, edit_file, run_command, task_completed). Please execute necessary tool calls to complete the user goal, or invoke task_completed if finished.',
|
|
942
|
+
});
|
|
943
|
+
stepDurations.push(Date.now() - stepStartTime);
|
|
944
|
+
}
|
|
945
|
+
catch (err) {
|
|
946
|
+
stepDurations.push(Date.now() - stepStartTime);
|
|
947
|
+
if (isQuotaExceededError(err)) {
|
|
948
|
+
stepSpinner.stop(chalk.red(`Oops! it\'s not you, it\'s us`));
|
|
949
|
+
safeNote(`${chalk.bold.red(`Something went wrong in (Step ${step}), please try again in a moment`)}\n\n` +
|
|
950
|
+
`${chalk.yellow('The configured AI provider has reached its API usage limit or rate cap.')}\n` +
|
|
951
|
+
`${chalk.dim('This is separate from your Scout credit balance shown by `scout quota`.')}\n\n` +
|
|
952
|
+
`${chalk.bold.cyan(' Please come back and try again in a few hours (or check back later today).')}\n\n` +
|
|
953
|
+
`${chalk.dim('Scout Agent session has ended gracefully to protect remaining workflow.')}`, '️ API Quota Limit Reached');
|
|
954
|
+
return {
|
|
955
|
+
success: false,
|
|
956
|
+
summary: 'Something went wrong, please try again in a moment.',
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
stepSpinner.stop(chalk.red(`Step ${step} execution error: ${err?.message || String(err)}`));
|
|
960
|
+
this.historyMessages.push({
|
|
961
|
+
role: 'user',
|
|
962
|
+
content: `Error in previous turn: ${err?.message || String(err)}. Please try alternative steps or call tools.`,
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
// Step limit check: Auto-extend by +15 steps ONLY for Pro & Enterprise (up to 60 max threshold)
|
|
966
|
+
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
967
|
+
if (step >= this.maxSteps) {
|
|
968
|
+
if (isPaid && this.maxSteps < 60) {
|
|
1141
969
|
this.maxSteps += 15;
|
|
1142
970
|
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');
|
|
1143
971
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
};
|
|
1149
|
-
}
|
|
1150
|
-
finally {
|
|
1151
|
-
cleanupScreenshots();
|
|
1152
|
-
if (isLiveScreenShareActive()) {
|
|
1153
|
-
stopLiveScreenShare();
|
|
972
|
+
else if (!isPaid) {
|
|
973
|
+
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://tryscout.web.app/plans or run 'scout dashboard'.`), ' Free Tier Step Limit');
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
1154
976
|
}
|
|
1155
977
|
}
|
|
978
|
+
return {
|
|
979
|
+
success: false,
|
|
980
|
+
summary: `Reached max iteration steps limit (${this.maxSteps}). Modified files: ${Array.from(this.modifiedFiles).join(', ')}`,
|
|
981
|
+
};
|
|
1156
982
|
}
|
|
1157
983
|
extractJsonToolCall(content) {
|
|
1158
984
|
if (!content)
|
|
@@ -1172,10 +998,8 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1172
998
|
const knownTools = [
|
|
1173
999
|
'read_file', 'write_file', 'edit_file', 'run_command', 'list_dir',
|
|
1174
1000
|
'grep_search', 'glob_search', 'tree_view', 'file_info', 'multi_edit_file',
|
|
1175
|
-
'fetch_url', 'git_diff',
|
|
1176
|
-
'ask_user', 'speak_text', 'task_completed',
|
|
1177
|
-
'desktop_action', 'type_text', 'click_app', 'send_keys', 'takeover',
|
|
1178
|
-
'capture_screen', 'see_screen', 'analyze_screen', 'scratchpad',
|
|
1001
|
+
'fetch_url', 'git_diff',
|
|
1002
|
+
'ask_user', 'speak_text', 'task_completed', 'scratchpad',
|
|
1179
1003
|
];
|
|
1180
1004
|
for (const toolName of knownTools) {
|
|
1181
1005
|
const tagRegex = new RegExp(`<${toolName}>([\\s\\S]*?)<\\/${toolName}>`, 'i');
|
|
@@ -1231,10 +1055,6 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1231
1055
|
actionDesc = `Fetching URL: ${args.url || ''}`;
|
|
1232
1056
|
else if (name === 'git_diff')
|
|
1233
1057
|
actionDesc = `Retrieving workspace git diff`;
|
|
1234
|
-
else if (name === 'open_app')
|
|
1235
|
-
actionDesc = `Launching app: ${args.app || ''} (${args.target || 'default'})`;
|
|
1236
|
-
else if (name === 'app_action')
|
|
1237
|
-
actionDesc = `Executing app action: ${args.app || ''} -> ${args.action || ''}`;
|
|
1238
1058
|
else if (name === 'browser_navigate')
|
|
1239
1059
|
actionDesc = `Navigating browser: ${args.url || ''}`;
|
|
1240
1060
|
else if (name === 'browser_inspect')
|
|
@@ -1248,24 +1068,97 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1248
1068
|
else if (name === 'browser_content')
|
|
1249
1069
|
actionDesc = `Extracting web page content`;
|
|
1250
1070
|
else if (name === 'computer_screenshot')
|
|
1251
|
-
actionDesc = `
|
|
1071
|
+
actionDesc = `Observing computer screen`;
|
|
1252
1072
|
else if (name === 'computer_click')
|
|
1253
|
-
actionDesc = `Clicking
|
|
1073
|
+
actionDesc = `Clicking screen coordinate: (${args.x}, ${args.y})`;
|
|
1074
|
+
else if (name === 'computer_right_click')
|
|
1075
|
+
actionDesc = `Right clicking coordinate: (${args.x}, ${args.y})`;
|
|
1076
|
+
else if (name === 'computer_double_click')
|
|
1077
|
+
actionDesc = `Double clicking coordinate: (${args.x}, ${args.y})`;
|
|
1078
|
+
else if (name === 'computer_mouse_down')
|
|
1079
|
+
actionDesc = `Mouse down (${args.button || 'left'})`;
|
|
1080
|
+
else if (name === 'computer_mouse_up')
|
|
1081
|
+
actionDesc = `Mouse up (${args.button || 'left'})`;
|
|
1082
|
+
else if (name === 'computer_mouse_move')
|
|
1083
|
+
actionDesc = `Moving mouse to: (${args.x}, ${args.y})`;
|
|
1084
|
+
else if (name === 'computer_drag')
|
|
1085
|
+
actionDesc = `Dragging from (${args.fromX ?? args.startX}, ${args.fromY ?? args.startY}) to (${args.toX ?? args.endX}, ${args.toY ?? args.endY})`;
|
|
1254
1086
|
else if (name === 'computer_type')
|
|
1255
|
-
actionDesc = `Typing text
|
|
1087
|
+
actionDesc = `Typing text: "${String(args.text || '').slice(0, 30)}"`;
|
|
1088
|
+
else if (name === 'computer_key_press')
|
|
1089
|
+
actionDesc = `Pressing key: ${args.key || ''}`;
|
|
1256
1090
|
else if (name === 'computer_hotkey')
|
|
1257
|
-
actionDesc = `
|
|
1091
|
+
actionDesc = `Executing hotkey: ${(args.keys || []).join('+')}`;
|
|
1258
1092
|
else if (name === 'computer_open_app')
|
|
1259
|
-
actionDesc = `
|
|
1260
|
-
else if (name === '
|
|
1261
|
-
actionDesc = `
|
|
1262
|
-
else if (name === '
|
|
1263
|
-
actionDesc = `
|
|
1093
|
+
actionDesc = `Launching application: ${args.appName || args.app || ''}`;
|
|
1094
|
+
else if (name === 'computer_close_app')
|
|
1095
|
+
actionDesc = `Closing application: ${args.appName || args.app || ''}`;
|
|
1096
|
+
else if (name === 'computer_switch_window')
|
|
1097
|
+
actionDesc = `Switching window: ${args.windowTitleOrApp || ''}`;
|
|
1098
|
+
else if (name === 'computer_inspect_ui')
|
|
1099
|
+
actionDesc = `Inspecting visible desktop UI`;
|
|
1100
|
+
else if (name === 'computer_diagnostic')
|
|
1101
|
+
actionDesc = `Running computer control diagnostics`;
|
|
1102
|
+
else if (name === 'computer')
|
|
1103
|
+
actionDesc = `Computer action: ${args.action || 'action'}`;
|
|
1104
|
+
else if (name === 'scratchpad')
|
|
1105
|
+
actionDesc = `Scratchpad (${args.action || 'read'})`;
|
|
1264
1106
|
else if (name === 'task_completed')
|
|
1265
1107
|
actionDesc = `Task finished`;
|
|
1266
1108
|
const badge = chalk.bgCyan.black.bold(` 🛠️ TOOL: ${name} `);
|
|
1267
1109
|
const descText = actionDesc ? chalk.yellow.bold(` → ${actionDesc}`) : '';
|
|
1268
1110
|
console.log(`\n${badge}${descText} ${chalk.dim(JSON.stringify(args))}`);
|
|
1111
|
+
const isPaid = this.mockMode || this.userTier === 'pro' || this.userTier === 'enterprise';
|
|
1112
|
+
// 1. Scratchpad Feature Gating (Pro & Enterprise only)
|
|
1113
|
+
if (name === 'scratchpad') {
|
|
1114
|
+
if (!isPaid) {
|
|
1115
|
+
const blockedMsg = `[PLAN RESTRICTION: PRO / ENTERPRISE ONLY] The Scratchpad reasoning memory is exclusive to Pro and Enterprise tiers. Free tier developers can perform basic code editing (edit_file), creating files and folders (write_file), and running commands (run_command). To unlock the AI scratchpad, upgrade at https://tryscout.web.app/plans or run 'scout dashboard'.`;
|
|
1116
|
+
console.log(chalk.red.bold(`\n⛔ ${blockedMsg}`));
|
|
1117
|
+
this.taskStateManager.addObservation(name, blockedMsg, false);
|
|
1118
|
+
return { result: blockedMsg };
|
|
1119
|
+
}
|
|
1120
|
+
const action = String(args.action || 'read').toLowerCase().trim();
|
|
1121
|
+
const content = String(args.content || '');
|
|
1122
|
+
const ftDir = path.join(this.cwd, '.ft');
|
|
1123
|
+
const scratchPath = path.join(ftDir, 'scratchpad.md');
|
|
1124
|
+
fs.mkdirSync(ftDir, { recursive: true });
|
|
1125
|
+
let currentScratch = '';
|
|
1126
|
+
if (fs.existsSync(scratchPath)) {
|
|
1127
|
+
try {
|
|
1128
|
+
currentScratch = fs.readFileSync(scratchPath, 'utf-8');
|
|
1129
|
+
}
|
|
1130
|
+
catch { }
|
|
1131
|
+
}
|
|
1132
|
+
if (action === 'write') {
|
|
1133
|
+
fs.writeFileSync(scratchPath, content, 'utf-8');
|
|
1134
|
+
this.taskStateManager.addObservation(name, `Scratchpad updated (${content.length} chars)`, true);
|
|
1135
|
+
return { result: `Scratchpad updated successfully:\n${content}` };
|
|
1136
|
+
}
|
|
1137
|
+
else if (action === 'append') {
|
|
1138
|
+
const updated = currentScratch ? `${currentScratch}\n\n---\n<!-- ${new Date().toISOString()} -->\n${content}` : content;
|
|
1139
|
+
fs.writeFileSync(scratchPath, updated, 'utf-8');
|
|
1140
|
+
this.taskStateManager.addObservation(name, `Appended to scratchpad (${content.length} chars)`, true);
|
|
1141
|
+
return { result: `Appended to scratchpad. Current contents:\n${updated}` };
|
|
1142
|
+
}
|
|
1143
|
+
else if (action === 'clear') {
|
|
1144
|
+
fs.writeFileSync(scratchPath, '', 'utf-8');
|
|
1145
|
+
this.taskStateManager.addObservation(name, `Cleared scratchpad`, true);
|
|
1146
|
+
return { result: `Scratchpad cleared.` };
|
|
1147
|
+
}
|
|
1148
|
+
else {
|
|
1149
|
+
this.taskStateManager.addObservation(name, `Read scratchpad (${currentScratch.length} chars)`, true);
|
|
1150
|
+
return { result: currentScratch ? `Scratchpad Contents:\n${currentScratch}` : `Scratchpad is currently empty.` };
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
// 2. Autonomous Computer & Browser Automation Gating (Pro & Enterprise only)
|
|
1154
|
+
if (name === 'computer' || name.startsWith('computer_') || name.startsWith('browser_')) {
|
|
1155
|
+
if (!isPaid) {
|
|
1156
|
+
const blockedMsg = `[PLAN RESTRICTION: PRO / ENTERPRISE ONLY] Autonomous Computer-Use & Browser Automation features are exclusive to Pro and Enterprise tiers. Free tier developers can perform basic code editing (edit_file), creating files and folders (write_file), and running commands (run_command) in the workspace. Upgrade to Pro at https://tryscout.web.app/plans or run 'scout dashboard'.`;
|
|
1157
|
+
console.log(chalk.red.bold(`\n⛔ ${blockedMsg}`));
|
|
1158
|
+
this.taskStateManager.addObservation(name, blockedMsg, false);
|
|
1159
|
+
return { result: blockedMsg };
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1269
1162
|
// Safety and human approval classification layer
|
|
1270
1163
|
const risk = this.approvalManager.checkAction(name, args);
|
|
1271
1164
|
if (risk.requiresApproval && !this.autoApprove) {
|
|
@@ -1284,6 +1177,85 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1284
1177
|
return { result: `[ACTION REJECTED BY USER]: ${decision.reason || 'User did not approve executing this action.'}` };
|
|
1285
1178
|
}
|
|
1286
1179
|
}
|
|
1180
|
+
// Validate against execution intent (USER INTENT > TOOL CONVENIENCE)
|
|
1181
|
+
if (this.executionIntent && !this.executionIntent.allowAlternativeTools) {
|
|
1182
|
+
if (this.executionIntent.requiredInteraction === 'computer') {
|
|
1183
|
+
if (['write_file', 'edit_file', 'multi_edit_file'].includes(name)) {
|
|
1184
|
+
const reqApps = this.executionIntent.requiredApplications.join(', ') || 'the requested desktop application';
|
|
1185
|
+
const violationMsg = `[EXECUTION INTENT VIOLATION BLOCKED]: User explicitly required computer/application interaction using ${reqApps}. You cannot silently substitute direct filesystem API calls (${name}). You must use computer interaction tools (computer_open_app, computer_switch_window, computer_click, computer_type, computer_hotkey) to perform this task in ${reqApps}.`;
|
|
1186
|
+
console.log(chalk.red.bold(`\n⛔ ${violationMsg}`));
|
|
1187
|
+
this.taskStateManager.addObservation(name, violationMsg, false);
|
|
1188
|
+
return {
|
|
1189
|
+
result: JSON.stringify({
|
|
1190
|
+
success: false,
|
|
1191
|
+
action: name,
|
|
1192
|
+
error: 'ExecutionIntentViolation',
|
|
1193
|
+
message: violationMsg,
|
|
1194
|
+
recoverable: true,
|
|
1195
|
+
suggestedAction: `Use computer interaction tools with ${reqApps}. Focus the window, type text into the app, and save through the application UI.`
|
|
1196
|
+
}, null, 2)
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (name === 'computer') {
|
|
1202
|
+
const action = String(args.action || 'screenshot').toLowerCase().trim();
|
|
1203
|
+
if (action === 'click' || action === 'left_click') {
|
|
1204
|
+
name = 'computer_click';
|
|
1205
|
+
}
|
|
1206
|
+
else if (action === 'right_click') {
|
|
1207
|
+
name = 'computer_right_click';
|
|
1208
|
+
}
|
|
1209
|
+
else if (action === 'double_click') {
|
|
1210
|
+
name = 'computer_double_click';
|
|
1211
|
+
}
|
|
1212
|
+
else if (action === 'move' || action === 'mouse_move') {
|
|
1213
|
+
name = 'computer_mouse_move';
|
|
1214
|
+
}
|
|
1215
|
+
else if (action === 'mouse_down') {
|
|
1216
|
+
name = 'computer_mouse_down';
|
|
1217
|
+
}
|
|
1218
|
+
else if (action === 'mouse_up') {
|
|
1219
|
+
name = 'computer_mouse_up';
|
|
1220
|
+
}
|
|
1221
|
+
else if (action === 'drag') {
|
|
1222
|
+
name = 'computer_drag';
|
|
1223
|
+
args.fromX = args.startX ?? args.fromX;
|
|
1224
|
+
args.fromY = args.startY ?? args.fromY;
|
|
1225
|
+
args.toX = args.endX ?? args.toX;
|
|
1226
|
+
args.toY = args.endY ?? args.toY;
|
|
1227
|
+
}
|
|
1228
|
+
else if (action === 'scroll') {
|
|
1229
|
+
name = 'computer_scroll';
|
|
1230
|
+
}
|
|
1231
|
+
else if (action === 'screenshot') {
|
|
1232
|
+
name = 'computer_screenshot';
|
|
1233
|
+
}
|
|
1234
|
+
else if (action === 'type') {
|
|
1235
|
+
name = 'computer_type';
|
|
1236
|
+
}
|
|
1237
|
+
else if (action === 'key_press' || action === 'key') {
|
|
1238
|
+
name = 'computer_key_press';
|
|
1239
|
+
}
|
|
1240
|
+
else if (action === 'hotkey') {
|
|
1241
|
+
name = 'computer_hotkey';
|
|
1242
|
+
}
|
|
1243
|
+
else if (action === 'open_app') {
|
|
1244
|
+
name = 'computer_open_app';
|
|
1245
|
+
}
|
|
1246
|
+
else if (action === 'close_app') {
|
|
1247
|
+
name = 'computer_close_app';
|
|
1248
|
+
}
|
|
1249
|
+
else if (action === 'switch_window') {
|
|
1250
|
+
name = 'computer_switch_window';
|
|
1251
|
+
}
|
|
1252
|
+
else if (action === 'inspect_ui') {
|
|
1253
|
+
name = 'computer_inspect_ui';
|
|
1254
|
+
}
|
|
1255
|
+
else if (action === 'diagnostic') {
|
|
1256
|
+
name = 'computer_diagnostic';
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1287
1259
|
switch (name) {
|
|
1288
1260
|
case 'read_file': {
|
|
1289
1261
|
const targetPath = String(args.path || '').trim();
|
|
@@ -1318,12 +1290,16 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1318
1290
|
return { result: `File: ${targetPath} (Lines ${start}-${end}/${lines.length}):\n${sliced}${minifiedNotice}` };
|
|
1319
1291
|
}
|
|
1320
1292
|
case 'write_file': {
|
|
1321
|
-
|
|
1293
|
+
let targetPath = String(args.path || '').trim();
|
|
1322
1294
|
let content = String(args.content || '');
|
|
1323
1295
|
const reason = String(args.reason || args.why || args.description || 'Created/updated file content to fulfill user prompt.');
|
|
1324
1296
|
if (content.includes('\\n') && !content.includes('\n')) {
|
|
1325
1297
|
content = content.replace(/\\n/g, '\n').replace(/\\t/g, '\t');
|
|
1326
1298
|
}
|
|
1299
|
+
if (this.executionIntent?.targetDestination === 'Desktop' || targetPath.toLowerCase().includes('desktop')) {
|
|
1300
|
+
const fileName = path.basename(targetPath);
|
|
1301
|
+
targetPath = resolveDesktopPath(fileName);
|
|
1302
|
+
}
|
|
1327
1303
|
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
1328
1304
|
const fileExists = fs.existsSync(fullPath);
|
|
1329
1305
|
const actionText = fileExists ? 'Editing file' : 'Creating file';
|
|
@@ -1663,49 +1639,6 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1663
1639
|
return { result: `Could not retrieve git diff: ${err?.message || String(err)}` };
|
|
1664
1640
|
}
|
|
1665
1641
|
}
|
|
1666
|
-
case 'agent_scratchpad': {
|
|
1667
|
-
const action = String(args.action || 'update').toLowerCase();
|
|
1668
|
-
if (action === 'clear') {
|
|
1669
|
-
this.scratchpadState = { plan: [], completedSteps: [], notes: '' };
|
|
1670
|
-
try {
|
|
1671
|
-
const scratchpadPath = path.join(this.cwd, '.ft', 'scratchpad.md');
|
|
1672
|
-
if (fs.existsSync(scratchpadPath))
|
|
1673
|
-
fs.unlinkSync(scratchpadPath);
|
|
1674
|
-
}
|
|
1675
|
-
catch { }
|
|
1676
|
-
return { result: 'Agent Scratchpad cleared.' };
|
|
1677
|
-
}
|
|
1678
|
-
else if (action === 'read') {
|
|
1679
|
-
const text = this.getScratchpadPromptContext() || 'Scratchpad is currently empty.';
|
|
1680
|
-
return { result: text };
|
|
1681
|
-
}
|
|
1682
|
-
else {
|
|
1683
|
-
const res = this.updateScratchpad(Array.isArray(args.plan) ? args.plan : undefined, Array.isArray(args.completedSteps) ? args.completedSteps : undefined, args.notes !== undefined ? String(args.notes) : undefined);
|
|
1684
|
-
return { result: res };
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
case 'open_app': {
|
|
1688
|
-
const appName = String(args.app || '').trim();
|
|
1689
|
-
const target = args.target ? String(args.target) : undefined;
|
|
1690
|
-
const launchRes = await openApp(appName, target, args);
|
|
1691
|
-
return { result: launchRes.output };
|
|
1692
|
-
}
|
|
1693
|
-
case 'scratchpad':
|
|
1694
|
-
case 'desktop_action':
|
|
1695
|
-
case 'type_text':
|
|
1696
|
-
case 'click_app':
|
|
1697
|
-
case 'send_keys':
|
|
1698
|
-
case 'takeover':
|
|
1699
|
-
case 'capture_screen':
|
|
1700
|
-
case 'see_screen':
|
|
1701
|
-
case 'analyze_screen':
|
|
1702
|
-
case 'app_action': {
|
|
1703
|
-
const appName = String(args.app || args.appName || 'desktop').trim();
|
|
1704
|
-
const action = String(args.action || (name !== 'app_action' ? name : 'type_text')).trim();
|
|
1705
|
-
const payload = args.payload !== undefined ? args.payload : (args.text || args.content || args.target || args.query || args.element || args.url || args.command || args);
|
|
1706
|
-
const actionRes = await executeInApp(appName, action, payload);
|
|
1707
|
-
return { result: actionRes.output };
|
|
1708
|
-
}
|
|
1709
1642
|
case 'ask_user': {
|
|
1710
1643
|
const questionText = String(args.question || args.prompt || 'Please provide input:').trim();
|
|
1711
1644
|
const askSpin = spinner();
|
|
@@ -1785,56 +1718,271 @@ You MUST output exactly ONE JSON code block per tool call. Do NOT describe what
|
|
|
1785
1718
|
}
|
|
1786
1719
|
case 'computer_screenshot': {
|
|
1787
1720
|
const res = await this.computerController.screenshot();
|
|
1788
|
-
|
|
1789
|
-
|
|
1721
|
+
if (res.path) {
|
|
1722
|
+
this.taskStateManager.recordArtifact({
|
|
1723
|
+
filename: path.basename(res.path),
|
|
1724
|
+
path: res.path,
|
|
1725
|
+
type: 'image',
|
|
1726
|
+
description: `Screen observation (${res.width}x${res.height})`,
|
|
1727
|
+
application: res.activeWindow || 'Desktop',
|
|
1728
|
+
});
|
|
1729
|
+
}
|
|
1730
|
+
if (res.activeWindow) {
|
|
1731
|
+
this.taskStateManager.setActiveWindow(res.activeWindow);
|
|
1732
|
+
}
|
|
1733
|
+
const summary = `Screen captured (${res.width}x${res.height}), Active window: "${res.activeWindow || 'Desktop'}"${res.path ? `, Saved: ${res.path}` : ''}`;
|
|
1734
|
+
this.taskStateManager.addObservation('computer_screenshot', summary, true);
|
|
1735
|
+
return { result: summary };
|
|
1790
1736
|
}
|
|
1791
1737
|
case 'computer_click': {
|
|
1792
|
-
const x = Number(args.x)
|
|
1793
|
-
const y = Number(args.y)
|
|
1794
|
-
const
|
|
1795
|
-
|
|
1796
|
-
: await this.computerController.click(x, y, args.button || 'left');
|
|
1738
|
+
const x = Number(args.x);
|
|
1739
|
+
const y = Number(args.y);
|
|
1740
|
+
const btn = args.button || 'left';
|
|
1741
|
+
const res = await this.computerController.click(x, y, { button: btn, clicks: 1 });
|
|
1797
1742
|
this.taskStateManager.addObservation('computer_click', res.output, res.success, `(${x}, ${y})`);
|
|
1798
|
-
|
|
1743
|
+
const structuredRes = {
|
|
1744
|
+
success: res.success,
|
|
1745
|
+
action: 'click',
|
|
1746
|
+
coordinates: res.coordinates || { x, y },
|
|
1747
|
+
button: btn,
|
|
1748
|
+
output: res.output,
|
|
1749
|
+
...(res.error ? { error: res.error } : {}),
|
|
1750
|
+
recoverable: res.recoverable ?? true,
|
|
1751
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
1752
|
+
};
|
|
1753
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1754
|
+
}
|
|
1755
|
+
case 'computer_right_click': {
|
|
1756
|
+
const x = Number(args.x);
|
|
1757
|
+
const y = Number(args.y);
|
|
1758
|
+
const res = await this.computerController.rightClick(x, y);
|
|
1759
|
+
this.taskStateManager.addObservation('computer_right_click', res.output, res.success, `(${x}, ${y})`);
|
|
1760
|
+
const structuredRes = {
|
|
1761
|
+
success: res.success,
|
|
1762
|
+
action: 'right_click',
|
|
1763
|
+
coordinates: res.coordinates || { x, y },
|
|
1764
|
+
button: 'right',
|
|
1765
|
+
output: res.output,
|
|
1766
|
+
...(res.error ? { error: res.error } : {}),
|
|
1767
|
+
recoverable: res.recoverable ?? true,
|
|
1768
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
1769
|
+
};
|
|
1770
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1771
|
+
}
|
|
1772
|
+
case 'computer_double_click': {
|
|
1773
|
+
const x = Number(args.x);
|
|
1774
|
+
const y = Number(args.y);
|
|
1775
|
+
const res = await this.computerController.doubleClick(x, y);
|
|
1776
|
+
this.taskStateManager.addObservation('computer_double_click', res.output, res.success, `(${x}, ${y})`);
|
|
1777
|
+
const structuredRes = {
|
|
1778
|
+
success: res.success,
|
|
1779
|
+
action: 'double_click',
|
|
1780
|
+
coordinates: res.coordinates || { x, y },
|
|
1781
|
+
button: 'left',
|
|
1782
|
+
output: res.output,
|
|
1783
|
+
...(res.error ? { error: res.error } : {}),
|
|
1784
|
+
recoverable: res.recoverable ?? true,
|
|
1785
|
+
};
|
|
1786
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1787
|
+
}
|
|
1788
|
+
case 'computer_mouse_down': {
|
|
1789
|
+
const x = args.x !== undefined ? Number(args.x) : undefined;
|
|
1790
|
+
const y = args.y !== undefined ? Number(args.y) : undefined;
|
|
1791
|
+
const btn = args.button || 'left';
|
|
1792
|
+
const res = await this.computerController.mouseDown(x, y, btn);
|
|
1793
|
+
this.taskStateManager.addObservation('computer_mouse_down', res.output, res.success);
|
|
1794
|
+
const structuredRes = {
|
|
1795
|
+
success: res.success,
|
|
1796
|
+
action: 'mouse_down',
|
|
1797
|
+
coordinates: res.coordinates || (x !== undefined && y !== undefined ? { x, y } : undefined),
|
|
1798
|
+
button: btn,
|
|
1799
|
+
output: res.output,
|
|
1800
|
+
...(res.error ? { error: res.error } : {}),
|
|
1801
|
+
recoverable: res.recoverable ?? true,
|
|
1802
|
+
};
|
|
1803
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1804
|
+
}
|
|
1805
|
+
case 'computer_mouse_up': {
|
|
1806
|
+
const x = args.x !== undefined ? Number(args.x) : undefined;
|
|
1807
|
+
const y = args.y !== undefined ? Number(args.y) : undefined;
|
|
1808
|
+
const btn = args.button || 'left';
|
|
1809
|
+
const res = await this.computerController.mouseUp(x, y, btn);
|
|
1810
|
+
this.taskStateManager.addObservation('computer_mouse_up', res.output, res.success);
|
|
1811
|
+
const structuredRes = {
|
|
1812
|
+
success: res.success,
|
|
1813
|
+
action: 'mouse_up',
|
|
1814
|
+
coordinates: res.coordinates || (x !== undefined && y !== undefined ? { x, y } : undefined),
|
|
1815
|
+
button: btn,
|
|
1816
|
+
output: res.output,
|
|
1817
|
+
...(res.error ? { error: res.error } : {}),
|
|
1818
|
+
recoverable: res.recoverable ?? true,
|
|
1819
|
+
};
|
|
1820
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1799
1821
|
}
|
|
1800
1822
|
case 'computer_type': {
|
|
1801
|
-
const
|
|
1802
|
-
const res = await this.computerController.type(
|
|
1823
|
+
const textVal = String(args.text || '');
|
|
1824
|
+
const res = await this.computerController.type(textVal);
|
|
1803
1825
|
this.taskStateManager.addObservation('computer_type', res.output, res.success);
|
|
1804
|
-
|
|
1826
|
+
const structuredRes = {
|
|
1827
|
+
success: res.success,
|
|
1828
|
+
action: 'type',
|
|
1829
|
+
output: res.output,
|
|
1830
|
+
...(res.error ? { error: res.error } : {}),
|
|
1831
|
+
recoverable: res.recoverable ?? true,
|
|
1832
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
1833
|
+
...(res.expectedWindow ? { expectedWindow: res.expectedWindow } : {}),
|
|
1834
|
+
};
|
|
1835
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1836
|
+
}
|
|
1837
|
+
case 'computer_key_press': {
|
|
1838
|
+
const key = String(args.key || '');
|
|
1839
|
+
const res = await this.computerController.keyPress(key);
|
|
1840
|
+
this.taskStateManager.addObservation('computer_key_press', res.output, res.success);
|
|
1841
|
+
const structuredRes = {
|
|
1842
|
+
success: res.success,
|
|
1843
|
+
action: 'key_press',
|
|
1844
|
+
output: res.output,
|
|
1845
|
+
...(res.error ? { error: res.error } : {}),
|
|
1846
|
+
recoverable: res.recoverable ?? true,
|
|
1847
|
+
};
|
|
1848
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1805
1849
|
}
|
|
1806
1850
|
case 'computer_hotkey': {
|
|
1807
|
-
const keys =
|
|
1851
|
+
const keys = Array.isArray(args.keys) ? args.keys.map(String) : [String(args.keys)];
|
|
1808
1852
|
const res = await this.computerController.hotkey(keys);
|
|
1809
|
-
this.taskStateManager.addObservation('computer_hotkey', res.output, res.success
|
|
1853
|
+
this.taskStateManager.addObservation('computer_hotkey', res.output, res.success);
|
|
1854
|
+
const structuredRes = {
|
|
1855
|
+
success: res.success,
|
|
1856
|
+
action: 'hotkey',
|
|
1857
|
+
output: res.output,
|
|
1858
|
+
...(res.error ? { error: res.error } : {}),
|
|
1859
|
+
recoverable: res.recoverable ?? true,
|
|
1860
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
1861
|
+
};
|
|
1862
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1863
|
+
}
|
|
1864
|
+
case 'computer_scroll': {
|
|
1865
|
+
const deltaY = Number(args.deltaY || 0);
|
|
1866
|
+
const deltaX = Number(args.deltaX || 0);
|
|
1867
|
+
const res = await this.computerController.scroll(deltaX, deltaY);
|
|
1868
|
+
this.taskStateManager.addObservation('computer_scroll', res.output, res.success);
|
|
1810
1869
|
return { result: res.output };
|
|
1811
1870
|
}
|
|
1871
|
+
case 'computer_mouse_move': {
|
|
1872
|
+
const x = Number(args.x);
|
|
1873
|
+
const y = Number(args.y);
|
|
1874
|
+
const res = await this.computerController.move(x, y);
|
|
1875
|
+
this.taskStateManager.addObservation('computer_mouse_move', res.output, res.success);
|
|
1876
|
+
const structuredRes = {
|
|
1877
|
+
success: res.success,
|
|
1878
|
+
action: 'move',
|
|
1879
|
+
coordinates: res.coordinates || { x, y },
|
|
1880
|
+
output: res.output,
|
|
1881
|
+
...(res.error ? { error: res.error } : {}),
|
|
1882
|
+
recoverable: res.recoverable ?? true,
|
|
1883
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
1884
|
+
};
|
|
1885
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1886
|
+
}
|
|
1887
|
+
case 'computer_drag': {
|
|
1888
|
+
const fromX = Number(args.fromX ?? args.startX ?? 0);
|
|
1889
|
+
const fromY = Number(args.fromY ?? args.startY ?? 0);
|
|
1890
|
+
const toX = Number(args.toX ?? args.endX ?? 0);
|
|
1891
|
+
const toY = Number(args.toY ?? args.endY ?? 0);
|
|
1892
|
+
const res = await this.computerController.drag(fromX, fromY, toX, toY);
|
|
1893
|
+
this.taskStateManager.addObservation('computer_drag', res.output, res.success);
|
|
1894
|
+
const structuredRes = {
|
|
1895
|
+
success: res.success,
|
|
1896
|
+
action: 'drag',
|
|
1897
|
+
from: { x: fromX, y: fromY },
|
|
1898
|
+
to: { x: toX, y: toY },
|
|
1899
|
+
coordinates: res.coordinates || { x: toX, y: toY },
|
|
1900
|
+
output: res.output,
|
|
1901
|
+
...(res.error ? { error: res.error } : {}),
|
|
1902
|
+
recoverable: res.recoverable ?? true,
|
|
1903
|
+
};
|
|
1904
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1905
|
+
}
|
|
1906
|
+
case 'computer_diagnostic': {
|
|
1907
|
+
const diag = await this.computerController.runDiagnostic();
|
|
1908
|
+
return { result: JSON.stringify(diag, null, 2) };
|
|
1909
|
+
}
|
|
1910
|
+
case 'computer_wait': {
|
|
1911
|
+
const ms = Math.min(10000, Math.max(100, Number(args.ms) || 1000));
|
|
1912
|
+
await this.computerController.wait(ms);
|
|
1913
|
+
return { result: `Waited for ${ms}ms` };
|
|
1914
|
+
}
|
|
1812
1915
|
case 'computer_open_app': {
|
|
1813
|
-
const
|
|
1814
|
-
const
|
|
1815
|
-
this.taskStateManager.setActiveApplication(
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1916
|
+
const appName = String(args.appName || args.app || '');
|
|
1917
|
+
const res = await this.computerController.openApplication(appName, args.args);
|
|
1918
|
+
this.taskStateManager.setActiveApplication(appName);
|
|
1919
|
+
this.taskStateManager.addObservation('computer_open_app', res.output, res.success);
|
|
1920
|
+
const structuredRes = {
|
|
1921
|
+
success: res.success,
|
|
1922
|
+
action: 'open_app',
|
|
1923
|
+
output: res.output,
|
|
1924
|
+
...(res.error ? { error: res.error } : {}),
|
|
1925
|
+
recoverable: res.recoverable ?? true,
|
|
1926
|
+
activeWindow: res.activeWindow || appName,
|
|
1927
|
+
expectedWindow: res.expectedWindow || appName,
|
|
1928
|
+
};
|
|
1929
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1819
1930
|
}
|
|
1820
|
-
case '
|
|
1821
|
-
const
|
|
1822
|
-
this.
|
|
1823
|
-
|
|
1824
|
-
this.taskStateManager.addObservation('computer_switch_app', res.output, res.success, name);
|
|
1931
|
+
case 'computer_close_app': {
|
|
1932
|
+
const appName = String(args.appName || args.app || '');
|
|
1933
|
+
const res = await this.computerController.closeApplication(appName);
|
|
1934
|
+
this.taskStateManager.addObservation('computer_close_app', res.output, res.success);
|
|
1825
1935
|
return { result: res.output };
|
|
1826
1936
|
}
|
|
1827
|
-
case '
|
|
1828
|
-
const
|
|
1829
|
-
const
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1937
|
+
case 'computer_switch_window': {
|
|
1938
|
+
const targetWin = String(args.windowTitleOrApp || args.target || '');
|
|
1939
|
+
const res = await this.computerController.switchWindow(targetWin);
|
|
1940
|
+
if (res.success) {
|
|
1941
|
+
this.taskStateManager.setActiveWindow(targetWin);
|
|
1942
|
+
}
|
|
1943
|
+
this.taskStateManager.addObservation('computer_switch_window', res.output, res.success);
|
|
1944
|
+
const structuredRes = {
|
|
1945
|
+
success: res.success,
|
|
1946
|
+
action: 'switch_window',
|
|
1947
|
+
output: res.output,
|
|
1948
|
+
...(res.error ? { error: res.error } : {}),
|
|
1949
|
+
recoverable: res.recoverable ?? true,
|
|
1950
|
+
activeWindow: res.activeWindow || targetWin,
|
|
1951
|
+
expectedWindow: res.expectedWindow,
|
|
1952
|
+
};
|
|
1953
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1954
|
+
}
|
|
1955
|
+
case 'computer_inspect_ui': {
|
|
1956
|
+
const ui = await this.computerController.inspectUI();
|
|
1957
|
+
this.taskStateManager.setActiveWindow(ui.activeWindow);
|
|
1958
|
+
const outStr = `Active Window: "${ui.activeWindow}"\nVisible Windows: ${ui.windows.map((w) => `"${w}"`).join(', ')}`;
|
|
1959
|
+
this.taskStateManager.addObservation('computer_inspect_ui', outStr, true);
|
|
1960
|
+
const structuredRes = {
|
|
1961
|
+
success: true,
|
|
1962
|
+
action: 'inspect_ui',
|
|
1963
|
+
activeWindow: ui.activeWindow,
|
|
1964
|
+
windows: ui.windows,
|
|
1965
|
+
output: outStr,
|
|
1966
|
+
};
|
|
1967
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
1833
1968
|
}
|
|
1834
1969
|
case 'task_completed': {
|
|
1835
1970
|
const summaryStr = String(args.summary || 'Task completed successfully.');
|
|
1971
|
+
if (this.executionIntent?.targetDestination === 'Desktop' && this.executionIntent?.targetArtifactName) {
|
|
1972
|
+
const checkRes = await this.actionVerifier.verifyFileCreated(this.executionIntent.targetArtifactName, 1, 'Desktop');
|
|
1973
|
+
if (!checkRes.verified) {
|
|
1974
|
+
return {
|
|
1975
|
+
result: JSON.stringify({
|
|
1976
|
+
success: false,
|
|
1977
|
+
error: 'VerificationFailed',
|
|
1978
|
+
message: `Cannot conclude task as completed: ${checkRes.message}`,
|
|
1979
|
+
recoverable: true,
|
|
1980
|
+
suggestedAction: 'The file was not found on the Desktop (or was placed in a substitute path). Save the file to the user\'s Desktop via the application interface before completing.'
|
|
1981
|
+
}, null, 2)
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1836
1985
|
speakText(summaryStr, { async: true });
|
|
1837
|
-
cleanupScreenshots();
|
|
1838
1986
|
this.taskStateManager.setStatus('completed');
|
|
1839
1987
|
this.taskStateManager.completeStep(this.currentStep, summaryStr);
|
|
1840
1988
|
return { result: summaryStr };
|