ft-scout 9.0.2 → 9.0.4
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 +13 -223
- 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 +459 -547
- 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 +479 -269
- package/bin/src/engine/computer/computerController.js.map +1 -1
- package/bin/src/engine/computer/index.d.ts.map +1 -1
- package/bin/src/engine/computer/index.js +1 -0
- package/bin/src/engine/computer/index.js.map +1 -1
- package/bin/src/engine/computer/mockComputer.d.ts.map +1 -0
- package/bin/src/engine/computer/mockComputer.js +164 -0
- package/bin/src/engine/computer/mockComputer.js.map +1 -0
- package/bin/src/engine/runtime/agentRuntime.d.ts.map +1 -0
- package/bin/src/engine/runtime/agentRuntime.js +829 -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 +49 -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 +162 -81
- 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 +484 -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 +67 -49
- 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 -2320
- 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
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { spinner, confirm, isCancel, text } from '@clack/prompts';
|
|
5
|
+
import { safeNote, renderMarkdown } from '../../utils/markdown.js';
|
|
6
|
+
import { openai, callOpenAIWithRetry, sanitizeMessage, sanitizeMessages } from '../llm.js';
|
|
7
|
+
import { checkFileSyntax, verifyAndSelfHealFiles } from '../verifier.js';
|
|
8
|
+
import { speakText } from '../voiceEngine.js';
|
|
9
|
+
import { getDirectoryFiles, recordHistoryAction } from '../../utils/config.js';
|
|
10
|
+
import { saveAgentSession, getRecentSessionsSummary } from '../../utils/session.js';
|
|
11
|
+
import { AgentWorkspace, resolveDesktopPath } from '../workspace/agentWorkspace.js';
|
|
12
|
+
import { SystemComputerController } from '../computer/computerController.js';
|
|
13
|
+
import { MockComputerController } from '../computer/mockComputer.js';
|
|
14
|
+
import { SemanticBrowserController } from '../browser/index.js';
|
|
15
|
+
import { ActionVerifier } from '../actionVerifier.js';
|
|
16
|
+
import { ApprovalManager, RiskClassifier } from '../safety.js';
|
|
17
|
+
import { TaskPlanner } from '../planner.js';
|
|
18
|
+
import { UnifiedToolRegistry } from '../toolRegistry.js';
|
|
19
|
+
import { robustSnippetReplace, buildDirectoryTree } from '../agentEngine.js';
|
|
20
|
+
export class AgentRuntime {
|
|
21
|
+
cwd;
|
|
22
|
+
autoApprove;
|
|
23
|
+
maxSteps;
|
|
24
|
+
mockMode;
|
|
25
|
+
language;
|
|
26
|
+
abortController = new AbortController();
|
|
27
|
+
isCancelled = false;
|
|
28
|
+
cancelReason;
|
|
29
|
+
workspace;
|
|
30
|
+
computerController;
|
|
31
|
+
browserController;
|
|
32
|
+
actionVerifier;
|
|
33
|
+
approvalManager;
|
|
34
|
+
planner;
|
|
35
|
+
toolRegistry;
|
|
36
|
+
historyMessages = [];
|
|
37
|
+
modifiedFiles = new Set();
|
|
38
|
+
backups = [];
|
|
39
|
+
totalToolCalls = 0;
|
|
40
|
+
currentStep = 0;
|
|
41
|
+
startTime = Date.now();
|
|
42
|
+
readFilesHistory = new Set();
|
|
43
|
+
toolCallHistory = [];
|
|
44
|
+
constructor(options) {
|
|
45
|
+
this.cwd = options?.workspaceDir || process.cwd();
|
|
46
|
+
this.autoApprove = Boolean(options?.autoApprove);
|
|
47
|
+
this.maxSteps = options?.maxSteps || 30;
|
|
48
|
+
this.mockMode = Boolean(options?.mockMode);
|
|
49
|
+
this.language = options?.language;
|
|
50
|
+
this.workspace = new AgentWorkspace(options?.taskId, '', this.cwd);
|
|
51
|
+
this.computerController = options?.computerController || (this.mockMode ? new MockComputerController() : new SystemComputerController(this.cwd));
|
|
52
|
+
this.browserController = options?.browserController || new SemanticBrowserController(this.cwd);
|
|
53
|
+
this.actionVerifier = new ActionVerifier(this.cwd);
|
|
54
|
+
this.approvalManager = new ApprovalManager(this.autoApprove);
|
|
55
|
+
this.planner = new TaskPlanner();
|
|
56
|
+
this.toolRegistry = new UnifiedToolRegistry();
|
|
57
|
+
}
|
|
58
|
+
getWorkspace() {
|
|
59
|
+
return this.workspace;
|
|
60
|
+
}
|
|
61
|
+
getComputerController() {
|
|
62
|
+
return this.computerController;
|
|
63
|
+
}
|
|
64
|
+
getBrowserController() {
|
|
65
|
+
return this.browserController;
|
|
66
|
+
}
|
|
67
|
+
getActionVerifier() {
|
|
68
|
+
return this.actionVerifier;
|
|
69
|
+
}
|
|
70
|
+
getApprovalManager() {
|
|
71
|
+
return this.approvalManager;
|
|
72
|
+
}
|
|
73
|
+
getPlanner() {
|
|
74
|
+
return this.planner;
|
|
75
|
+
}
|
|
76
|
+
getToolRegistry() {
|
|
77
|
+
return this.toolRegistry;
|
|
78
|
+
}
|
|
79
|
+
cancel(reason = 'User cancelled task') {
|
|
80
|
+
this.isCancelled = true;
|
|
81
|
+
this.cancelReason = reason;
|
|
82
|
+
this.abortController.abort();
|
|
83
|
+
this.workspace.setStatus('cancelled');
|
|
84
|
+
this.workspace.recordError(`Task execution cancelled: ${reason}`, 'runtime', undefined, false);
|
|
85
|
+
}
|
|
86
|
+
isTaskCancelled() {
|
|
87
|
+
return this.isCancelled;
|
|
88
|
+
}
|
|
89
|
+
getModifiedFiles() {
|
|
90
|
+
return Array.from(this.modifiedFiles);
|
|
91
|
+
}
|
|
92
|
+
getBackups() {
|
|
93
|
+
return this.backups;
|
|
94
|
+
}
|
|
95
|
+
getStats() {
|
|
96
|
+
return {
|
|
97
|
+
totalSteps: this.currentStep,
|
|
98
|
+
totalToolCalls: this.totalToolCalls,
|
|
99
|
+
modifiedFilesCount: this.modifiedFiles.size,
|
|
100
|
+
elapsedMs: Date.now() - this.startTime,
|
|
101
|
+
taskId: this.workspace.getData().taskId,
|
|
102
|
+
status: this.workspace.getData().status,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
resetContext() {
|
|
106
|
+
this.historyMessages = [];
|
|
107
|
+
this.modifiedFiles.clear();
|
|
108
|
+
this.readFilesHistory.clear();
|
|
109
|
+
this.backups = [];
|
|
110
|
+
this.startTime = Date.now();
|
|
111
|
+
this.totalToolCalls = 0;
|
|
112
|
+
this.currentStep = 0;
|
|
113
|
+
this.toolCallHistory = [];
|
|
114
|
+
this.isCancelled = false;
|
|
115
|
+
this.cancelReason = undefined;
|
|
116
|
+
this.abortController = new AbortController();
|
|
117
|
+
}
|
|
118
|
+
buildSystemPrompt() {
|
|
119
|
+
const filesList = getDirectoryFiles(this.cwd).slice(0, 100);
|
|
120
|
+
const workspaceState = this.workspace.compactWorkspaceForPrompt();
|
|
121
|
+
return `You are Scout Agent, an elite AI Principal Software Engineer & Autonomous Computer-Use Operator (developed by FrontTerrain).
|
|
122
|
+
You do not merely explain or output chat answers—you autonomously operate the computer, applications, browser, and workspace tools to fulfill the user's objective end-to-end.
|
|
123
|
+
|
|
124
|
+
Execution Cycle:
|
|
125
|
+
UNDERSTAND ➔ PLAN ➔ ACT ➔ OBSERVE ➔ UPDATE WORKSPACE ➔ REASON ABOUT STATE ➔ ACT AGAIN ➔ VERIFY ➔ COMPLETE
|
|
126
|
+
|
|
127
|
+
Working Directory: "${this.cwd}"
|
|
128
|
+
Host OS Platform: "${process.platform}"
|
|
129
|
+
Current Year: ${new Date().getFullYear()}
|
|
130
|
+
|
|
131
|
+
${workspaceState}
|
|
132
|
+
|
|
133
|
+
Available Workspace Files (sample):
|
|
134
|
+
${filesList.join('\n')}
|
|
135
|
+
|
|
136
|
+
CORE ARCHITECTURAL PRINCIPLES:
|
|
137
|
+
1. USER INTENT > TOOL CONVENIENCE (MANDATORY):
|
|
138
|
+
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.
|
|
139
|
+
- 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").
|
|
140
|
+
- 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.
|
|
141
|
+
- 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").
|
|
142
|
+
- 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.
|
|
143
|
+
|
|
144
|
+
2. COMPUTER ACTION FAILURE RECOVERY:
|
|
145
|
+
When a computer action fails or encounters a problem:
|
|
146
|
+
DO NOT immediately switch to an unrelated tool (like write_file).
|
|
147
|
+
Instead, follow the bounded computer recovery loop:
|
|
148
|
+
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.
|
|
149
|
+
Only after repeated bounded computer recovery attempts fail should you report the failure to the user.
|
|
150
|
+
|
|
151
|
+
3. ACTIVE APPLICATION TRACKING:
|
|
152
|
+
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.
|
|
153
|
+
|
|
154
|
+
Tool Selection Guidelines:
|
|
155
|
+
- Level 1 - COMPUTER & DESKTOP APPS: When interacting with desktop GUI applications (VS Code, Notepad, File Explorer, Terminal), observing the screen, clicking desktop UI coordinates, or switching windows, use: computer_screenshot, computer_click, computer_type, computer_hotkey, computer_open_app, computer_switch_window, computer_inspect_ui.
|
|
156
|
+
- Level 2 - BROWSER AUTOMATION: For web browsing, scraping, filling web forms, or downloading online files, use browser tools: browser_navigate, browser_click, browser_type, browser_download, browser_content.
|
|
157
|
+
- Level 3 - DIRECT TOOLS: For file editing, creating, reading, searching, or shell commands in coding/repository tasks (when computer GUI is NOT required), use: write_file, edit_file, read_file, run_command, glob_search, grep_search.
|
|
158
|
+
|
|
159
|
+
Action Rules:
|
|
160
|
+
- Structured Tool Invocations: Return exactly one tool call per turn via native function calling or \`\`\`json { "tool": "tool_name", "args": { ... } } \`\`\`.
|
|
161
|
+
- Screen Observations: Request \`computer_screenshot\` when you need to inspect desktop application UI or verify visible screen state.
|
|
162
|
+
- Verification Mandate: Never assume an action succeeded. After creating files, verify them. After opening an app, verify its window appeared.
|
|
163
|
+
- Termination: Call \`task_completed\` as soon as the objective has been achieved with a full summary.
|
|
164
|
+
`.trim();
|
|
165
|
+
}
|
|
166
|
+
async executeGoal(userGoal) {
|
|
167
|
+
this.resetContext();
|
|
168
|
+
this.workspace.setObjective(userGoal);
|
|
169
|
+
const expectedApp = this.workspace.getData().environment.expectedApplication;
|
|
170
|
+
if (expectedApp && this.computerController.setExpectedApplication) {
|
|
171
|
+
this.computerController.setExpectedApplication(expectedApp);
|
|
172
|
+
}
|
|
173
|
+
const structuredPlan = this.planner.createPlan(userGoal);
|
|
174
|
+
this.workspace.setPlan(structuredPlan.steps);
|
|
175
|
+
const taskId = this.workspace.getData().taskId;
|
|
176
|
+
if (this.mockMode) {
|
|
177
|
+
for (let i = 1; i <= structuredPlan.steps.length; i++) {
|
|
178
|
+
this.workspace.startStep(i);
|
|
179
|
+
this.workspace.completeStep(i, `Completed step ${i} in mock mode`);
|
|
180
|
+
}
|
|
181
|
+
this.workspace.setStatus('completed');
|
|
182
|
+
return {
|
|
183
|
+
success: true,
|
|
184
|
+
summary: `Mock execution completed successfully for goal: "${userGoal}".`,
|
|
185
|
+
taskId,
|
|
186
|
+
stepsTaken: structuredPlan.steps.length,
|
|
187
|
+
modifiedFiles: [],
|
|
188
|
+
status: 'completed',
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
this.historyMessages.push({ role: 'system', content: this.buildSystemPrompt() });
|
|
192
|
+
// Memory persistence
|
|
193
|
+
const recentMemory = getRecentSessionsSummary(3);
|
|
194
|
+
if (recentMemory) {
|
|
195
|
+
this.historyMessages.push({ role: 'user', content: recentMemory });
|
|
196
|
+
}
|
|
197
|
+
// Add user goal
|
|
198
|
+
this.historyMessages.push({ role: 'user', content: userGoal });
|
|
199
|
+
let step = 0;
|
|
200
|
+
let finalSummary = '';
|
|
201
|
+
while (step < this.maxSteps) {
|
|
202
|
+
if (this.isCancelled) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
summary: `Task cancelled: ${this.cancelReason || 'User abort'}`,
|
|
206
|
+
taskId,
|
|
207
|
+
stepsTaken: step,
|
|
208
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
209
|
+
status: 'cancelled',
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
step++;
|
|
213
|
+
this.currentStep = step;
|
|
214
|
+
this.workspace.startStep(step);
|
|
215
|
+
// Context Compaction check: If history is long, compact middle turns using workspace state
|
|
216
|
+
if (this.historyMessages.length > 10) {
|
|
217
|
+
this.historyMessages = this.workspace.compactHistoryMessages(this.historyMessages, 6);
|
|
218
|
+
}
|
|
219
|
+
const stepSpinner = spinner();
|
|
220
|
+
stepSpinner.start(chalk.cyan(`Scout Operating (Step ${step}/${this.maxSteps})`));
|
|
221
|
+
try {
|
|
222
|
+
let response;
|
|
223
|
+
try {
|
|
224
|
+
response = await callOpenAIWithRetry(async (model) => {
|
|
225
|
+
return await openai.chat.completions.create({
|
|
226
|
+
model,
|
|
227
|
+
messages: sanitizeMessages(this.historyMessages),
|
|
228
|
+
tools: this.toolRegistry.getAllTools().map((t) => ({ type: 'function', function: t })),
|
|
229
|
+
tool_choice: 'auto',
|
|
230
|
+
temperature: 0.1,
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
const errStr = String(err?.message || err || '').toLowerCase();
|
|
236
|
+
if (errStr.includes('tool') || errStr.includes('400') || errStr.includes('not supported')) {
|
|
237
|
+
const fallbackMsg = {
|
|
238
|
+
role: 'user',
|
|
239
|
+
content: `Format your tool call as a JSON code block:\n\`\`\`json\n{ "tool": "tool_name", "args": { ... } }\n\`\`\`\nAvailable tools: ${this.toolRegistry.getAllTools().map((t) => t.name).join(', ')}`,
|
|
240
|
+
};
|
|
241
|
+
response = await callOpenAIWithRetry(async (model) => {
|
|
242
|
+
return await openai.chat.completions.create({
|
|
243
|
+
model,
|
|
244
|
+
messages: sanitizeMessages([...this.historyMessages, fallbackMsg]),
|
|
245
|
+
temperature: 0.1,
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const choice = response.choices[0];
|
|
254
|
+
if (!choice) {
|
|
255
|
+
stepSpinner.stop(chalk.yellow('No response from model. Retrying step...'));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const msg = sanitizeMessage(choice.message);
|
|
259
|
+
this.historyMessages.push(msg);
|
|
260
|
+
if (msg.content && msg.content.trim()) {
|
|
261
|
+
safeNote(renderMarkdown(msg.content), `Scout Step ${step}`);
|
|
262
|
+
}
|
|
263
|
+
let toolCallsToExecute = [];
|
|
264
|
+
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
265
|
+
for (const tc of msg.tool_calls) {
|
|
266
|
+
if (tc.type === 'function' && tc.function) {
|
|
267
|
+
try {
|
|
268
|
+
toolCallsToExecute.push({
|
|
269
|
+
name: tc.function.name,
|
|
270
|
+
args: JSON.parse(tc.function.arguments || '{}'),
|
|
271
|
+
id: tc.id,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
toolCallsToExecute.push({ name: tc.function.name, args: {}, id: tc.id });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else if (msg.content) {
|
|
281
|
+
const parsed = this.extractJsonToolCall(msg.content);
|
|
282
|
+
if (parsed) {
|
|
283
|
+
toolCallsToExecute.push({ name: parsed.tool, args: parsed.args });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
stepSpinner.stop(chalk.green(`Step ${step}: Executing ${toolCallsToExecute.length} action(s).`));
|
|
287
|
+
for (const tc of toolCallsToExecute) {
|
|
288
|
+
if (this.isCancelled)
|
|
289
|
+
break;
|
|
290
|
+
const toolResult = await this.dispatchTool(tc.name, tc.args);
|
|
291
|
+
this.historyMessages.push({
|
|
292
|
+
role: 'user',
|
|
293
|
+
tool_call_id: tc.id,
|
|
294
|
+
content: `Tool Execution Result (${tc.name}):\n${toolResult.result}`,
|
|
295
|
+
});
|
|
296
|
+
if (tc.name === 'task_completed') {
|
|
297
|
+
finalSummary = tc.args.summary || toolResult.result;
|
|
298
|
+
if (this.modifiedFiles.size > 0) {
|
|
299
|
+
const filesList = getDirectoryFiles(this.cwd);
|
|
300
|
+
await verifyAndSelfHealFiles(Array.from(this.modifiedFiles), this.cwd, path.basename(this.cwd), filesList, { maxRetries: 3 });
|
|
301
|
+
}
|
|
302
|
+
this.workspace.setStatus('completed');
|
|
303
|
+
this.workspace.completeStep(step, finalSummary);
|
|
304
|
+
saveAgentSession({
|
|
305
|
+
goal: userGoal,
|
|
306
|
+
summary: finalSummary,
|
|
307
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
308
|
+
});
|
|
309
|
+
return {
|
|
310
|
+
success: true,
|
|
311
|
+
summary: finalSummary,
|
|
312
|
+
taskId,
|
|
313
|
+
stepsTaken: step,
|
|
314
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
315
|
+
status: 'completed',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
stepSpinner.stop(chalk.red(`Step ${step} error: ${err?.message || String(err)}`));
|
|
322
|
+
this.workspace.recordError(err?.message || String(err), 'runtime', undefined, false);
|
|
323
|
+
this.historyMessages.push({
|
|
324
|
+
role: 'user',
|
|
325
|
+
content: `System Error encountered on step ${step}: ${err?.message || String(err)}. Diagnose and adapt action.`,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
this.workspace.setStatus('failed');
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
summary: `Reached max iteration steps limit (${this.maxSteps}). Modified files: ${Array.from(this.modifiedFiles).join(', ')}`,
|
|
333
|
+
taskId,
|
|
334
|
+
stepsTaken: step,
|
|
335
|
+
modifiedFiles: Array.from(this.modifiedFiles),
|
|
336
|
+
status: 'failed',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
async dispatchTool(name, args) {
|
|
340
|
+
this.totalToolCalls++;
|
|
341
|
+
const risk = this.approvalManager.checkAction(name, args);
|
|
342
|
+
if (risk.requiresApproval && !this.autoApprove) {
|
|
343
|
+
this.workspace.setPendingApproval({
|
|
344
|
+
id: `act_${Date.now()}`,
|
|
345
|
+
tool: name,
|
|
346
|
+
args,
|
|
347
|
+
riskLevel: risk.level,
|
|
348
|
+
riskReason: risk.reason,
|
|
349
|
+
requestedAt: new Date().toISOString(),
|
|
350
|
+
});
|
|
351
|
+
const decision = await this.approvalManager.requestApproval(this.workspace.getData().taskId, name, args, risk);
|
|
352
|
+
this.workspace.setPendingApproval(null);
|
|
353
|
+
if (!decision.approved) {
|
|
354
|
+
this.workspace.addObservation({
|
|
355
|
+
tool: name,
|
|
356
|
+
resultSummary: `[ACTION BLOCKED: User rejected approval: ${decision.reason || 'Not approved'}]`,
|
|
357
|
+
verified: false,
|
|
358
|
+
});
|
|
359
|
+
return { result: `[ACTION REJECTED BY USER]: ${decision.reason || 'User rejected this action.'}` };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Validate against execution intent (USER INTENT > TOOL CONVENIENCE)
|
|
363
|
+
const validation = this.workspace.validateToolExecution(name, args);
|
|
364
|
+
if (!validation.allowed) {
|
|
365
|
+
const blockedSummary = `[EXECUTION INTENT VIOLATION BLOCKED]: ${validation.reason}`;
|
|
366
|
+
this.workspace.addObservation({
|
|
367
|
+
tool: name,
|
|
368
|
+
resultSummary: blockedSummary,
|
|
369
|
+
verified: false,
|
|
370
|
+
});
|
|
371
|
+
return {
|
|
372
|
+
result: JSON.stringify({
|
|
373
|
+
success: false,
|
|
374
|
+
action: name,
|
|
375
|
+
error: 'ExecutionIntentViolation',
|
|
376
|
+
message: validation.reason,
|
|
377
|
+
recoverable: true,
|
|
378
|
+
suggestedAction: 'Preserve the user\'s required execution method using computer interaction tools (computer_open_app, computer_switch_window, computer_click, computer_type, computer_hotkey). Do NOT substitute direct filesystem APIs.'
|
|
379
|
+
}, null, 2)
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
// 1. Computer Control Tools
|
|
383
|
+
if (name.startsWith('computer_')) {
|
|
384
|
+
return await this.dispatchComputerTool(name, args);
|
|
385
|
+
}
|
|
386
|
+
// 2. Browser Automation Tools
|
|
387
|
+
if (name.startsWith('browser_')) {
|
|
388
|
+
return await this.dispatchBrowserTool(name, args);
|
|
389
|
+
}
|
|
390
|
+
// 3. Core Direct Workspace Tools
|
|
391
|
+
return await this.dispatchCoreTool(name, args);
|
|
392
|
+
}
|
|
393
|
+
async dispatchComputerTool(name, args) {
|
|
394
|
+
switch (name) {
|
|
395
|
+
case 'computer_screenshot': {
|
|
396
|
+
const res = await this.computerController.screenshot();
|
|
397
|
+
if (res.path) {
|
|
398
|
+
this.workspace.recordArtifact({
|
|
399
|
+
filename: path.basename(res.path),
|
|
400
|
+
path: res.path,
|
|
401
|
+
type: 'image',
|
|
402
|
+
description: `Screen observation (${res.width}x${res.height})`,
|
|
403
|
+
application: res.activeWindow || 'Desktop',
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (res.activeWindow) {
|
|
407
|
+
this.workspace.setActiveWindow(res.activeWindow);
|
|
408
|
+
}
|
|
409
|
+
const summary = `Screen captured: ${res.width}x${res.height}, Active window: "${res.activeWindow || 'Desktop'}"${res.path ? `, Saved: ${res.path}` : ''}`;
|
|
410
|
+
this.workspace.addObservation({ tool: name, resultSummary: summary, verified: true });
|
|
411
|
+
return { result: summary };
|
|
412
|
+
}
|
|
413
|
+
case 'computer_click': {
|
|
414
|
+
const x = Number(args.x);
|
|
415
|
+
const y = Number(args.y);
|
|
416
|
+
const res = await this.computerController.click(x, y, { button: args.button, clicks: 1 });
|
|
417
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
418
|
+
const structuredRes = {
|
|
419
|
+
success: res.success,
|
|
420
|
+
action: 'click',
|
|
421
|
+
output: res.output,
|
|
422
|
+
...(res.error ? { error: res.error } : {}),
|
|
423
|
+
recoverable: res.recoverable ?? true,
|
|
424
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
425
|
+
};
|
|
426
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
427
|
+
}
|
|
428
|
+
case 'computer_double_click': {
|
|
429
|
+
const x = Number(args.x);
|
|
430
|
+
const y = Number(args.y);
|
|
431
|
+
const res = await this.computerController.doubleClick(x, y);
|
|
432
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
433
|
+
const structuredRes = {
|
|
434
|
+
success: res.success,
|
|
435
|
+
action: 'double_click',
|
|
436
|
+
output: res.output,
|
|
437
|
+
...(res.error ? { error: res.error } : {}),
|
|
438
|
+
recoverable: res.recoverable ?? true,
|
|
439
|
+
};
|
|
440
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
441
|
+
}
|
|
442
|
+
case 'computer_type': {
|
|
443
|
+
const textVal = String(args.text || '');
|
|
444
|
+
const res = await this.computerController.type(textVal);
|
|
445
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
446
|
+
const structuredRes = {
|
|
447
|
+
success: res.success,
|
|
448
|
+
action: 'type',
|
|
449
|
+
output: res.output,
|
|
450
|
+
...(res.error ? { error: res.error } : {}),
|
|
451
|
+
recoverable: res.recoverable ?? true,
|
|
452
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
453
|
+
...(res.expectedWindow ? { expectedWindow: res.expectedWindow } : {}),
|
|
454
|
+
};
|
|
455
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
456
|
+
}
|
|
457
|
+
case 'computer_key_press': {
|
|
458
|
+
const key = String(args.key || '');
|
|
459
|
+
const res = await this.computerController.keyPress(key);
|
|
460
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
461
|
+
const structuredRes = {
|
|
462
|
+
success: res.success,
|
|
463
|
+
action: 'key_press',
|
|
464
|
+
output: res.output,
|
|
465
|
+
...(res.error ? { error: res.error } : {}),
|
|
466
|
+
recoverable: res.recoverable ?? true,
|
|
467
|
+
};
|
|
468
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
469
|
+
}
|
|
470
|
+
case 'computer_hotkey': {
|
|
471
|
+
const keys = Array.isArray(args.keys) ? args.keys.map(String) : [String(args.keys)];
|
|
472
|
+
const res = await this.computerController.hotkey(keys);
|
|
473
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
474
|
+
const structuredRes = {
|
|
475
|
+
success: res.success,
|
|
476
|
+
action: 'hotkey',
|
|
477
|
+
output: res.output,
|
|
478
|
+
...(res.error ? { error: res.error } : {}),
|
|
479
|
+
recoverable: res.recoverable ?? true,
|
|
480
|
+
...(res.activeWindow ? { activeWindow: res.activeWindow } : {}),
|
|
481
|
+
};
|
|
482
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
483
|
+
}
|
|
484
|
+
case 'computer_scroll': {
|
|
485
|
+
const deltaY = Number(args.deltaY || 0);
|
|
486
|
+
const deltaX = Number(args.deltaX || 0);
|
|
487
|
+
const res = await this.computerController.scroll(deltaX, deltaY);
|
|
488
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
489
|
+
return { result: res.output };
|
|
490
|
+
}
|
|
491
|
+
case 'computer_mouse_move': {
|
|
492
|
+
const x = Number(args.x);
|
|
493
|
+
const y = Number(args.y);
|
|
494
|
+
const res = await this.computerController.mouseMove(x, y);
|
|
495
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
496
|
+
return { result: res.output };
|
|
497
|
+
}
|
|
498
|
+
case 'computer_drag': {
|
|
499
|
+
const res = await this.computerController.drag(Number(args.fromX), Number(args.fromY), Number(args.toX), Number(args.toY));
|
|
500
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
501
|
+
return { result: res.output };
|
|
502
|
+
}
|
|
503
|
+
case 'computer_wait': {
|
|
504
|
+
const ms = Math.min(10000, Math.max(100, Number(args.ms) || 1000));
|
|
505
|
+
await this.computerController.wait(ms);
|
|
506
|
+
return { result: `Waited for ${ms}ms` };
|
|
507
|
+
}
|
|
508
|
+
case 'computer_open_app': {
|
|
509
|
+
const appName = String(args.appName || args.app || '');
|
|
510
|
+
const res = await this.computerController.openApplication(appName, args.args);
|
|
511
|
+
this.workspace.setActiveApplication(appName);
|
|
512
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
513
|
+
const structuredRes = {
|
|
514
|
+
success: res.success,
|
|
515
|
+
action: 'open_app',
|
|
516
|
+
output: res.output,
|
|
517
|
+
...(res.error ? { error: res.error } : {}),
|
|
518
|
+
recoverable: res.recoverable ?? true,
|
|
519
|
+
activeWindow: res.activeWindow || appName,
|
|
520
|
+
expectedWindow: res.expectedWindow || appName,
|
|
521
|
+
};
|
|
522
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
523
|
+
}
|
|
524
|
+
case 'computer_close_app': {
|
|
525
|
+
const appName = String(args.appName || args.app || '');
|
|
526
|
+
const res = await this.computerController.closeApplication(appName);
|
|
527
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
528
|
+
return { result: res.output };
|
|
529
|
+
}
|
|
530
|
+
case 'computer_switch_window': {
|
|
531
|
+
const targetWin = String(args.windowTitleOrApp || args.target || '');
|
|
532
|
+
const res = await this.computerController.switchWindow(targetWin);
|
|
533
|
+
if (res.success) {
|
|
534
|
+
this.workspace.setActiveWindow(targetWin);
|
|
535
|
+
}
|
|
536
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success });
|
|
537
|
+
const structuredRes = {
|
|
538
|
+
success: res.success,
|
|
539
|
+
action: 'switch_window',
|
|
540
|
+
output: res.output,
|
|
541
|
+
...(res.error ? { error: res.error } : {}),
|
|
542
|
+
recoverable: res.recoverable ?? true,
|
|
543
|
+
activeWindow: res.activeWindow || targetWin,
|
|
544
|
+
expectedWindow: res.expectedWindow,
|
|
545
|
+
};
|
|
546
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
547
|
+
}
|
|
548
|
+
case 'computer_inspect_ui': {
|
|
549
|
+
const ui = await this.computerController.inspectUI();
|
|
550
|
+
this.workspace.setActiveWindow(ui.activeWindow);
|
|
551
|
+
const outStr = `Active Window: "${ui.activeWindow}"\nVisible Windows: ${ui.windows.map((w) => `"${w}"`).join(', ')}`;
|
|
552
|
+
this.workspace.addObservation({ tool: name, resultSummary: outStr, verified: true });
|
|
553
|
+
const structuredRes = {
|
|
554
|
+
success: true,
|
|
555
|
+
action: 'inspect_ui',
|
|
556
|
+
activeWindow: ui.activeWindow,
|
|
557
|
+
windows: ui.windows,
|
|
558
|
+
output: outStr,
|
|
559
|
+
};
|
|
560
|
+
return { result: JSON.stringify(structuredRes, null, 2) };
|
|
561
|
+
}
|
|
562
|
+
default:
|
|
563
|
+
return { result: `Unknown computer tool: "${name}"` };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
async dispatchBrowserTool(name, args) {
|
|
567
|
+
switch (name) {
|
|
568
|
+
case 'browser_navigate': {
|
|
569
|
+
const url = String(args.url || '');
|
|
570
|
+
this.workspace.setActiveUrl(url);
|
|
571
|
+
this.workspace.setActiveApplication('Browser');
|
|
572
|
+
const res = await this.browserController.navigate(url);
|
|
573
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success, target: url });
|
|
574
|
+
return { result: res.output };
|
|
575
|
+
}
|
|
576
|
+
case 'browser_inspect': {
|
|
577
|
+
const res = await this.browserController.inspect_page();
|
|
578
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.summary, verified: res.success, target: res.url });
|
|
579
|
+
return { result: res.summary };
|
|
580
|
+
}
|
|
581
|
+
case 'browser_click': {
|
|
582
|
+
const target = String(args.selectorOrText || args.target || '');
|
|
583
|
+
const res = await this.browserController.click(target);
|
|
584
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success, target });
|
|
585
|
+
return { result: res.output };
|
|
586
|
+
}
|
|
587
|
+
case 'browser_type': {
|
|
588
|
+
const target = String(args.selectorOrText || args.target || '');
|
|
589
|
+
const textVal = String(args.text || '');
|
|
590
|
+
const res = await this.browserController.type(target, textVal);
|
|
591
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: res.success, target });
|
|
592
|
+
return { result: res.output };
|
|
593
|
+
}
|
|
594
|
+
case 'browser_download': {
|
|
595
|
+
const target = String(args.urlOrSelector || args.url || '');
|
|
596
|
+
const res = await this.browserController.download_file(target, args.destPath);
|
|
597
|
+
if (res.success && res.details?.path) {
|
|
598
|
+
const vRes = await this.actionVerifier.verifyFileCreated(res.details.path);
|
|
599
|
+
const artifact = this.workspace.recordArtifact({
|
|
600
|
+
filename: res.details.filename || path.basename(res.details.path),
|
|
601
|
+
path: res.details.path,
|
|
602
|
+
application: 'Browser',
|
|
603
|
+
verified: vRes.verified,
|
|
604
|
+
sizeBytes: res.details.sizeBytes,
|
|
605
|
+
});
|
|
606
|
+
this.workspace.addObservation({ tool: name, resultSummary: `${res.output} (Verified on disk: ${vRes.verified})`, verified: vRes.verified });
|
|
607
|
+
return { result: `${res.output}\nTracked Artifact: ${artifact.filename} (${artifact.path})` };
|
|
608
|
+
}
|
|
609
|
+
this.workspace.addObservation({ tool: name, resultSummary: res.output, verified: false });
|
|
610
|
+
return { result: res.output };
|
|
611
|
+
}
|
|
612
|
+
case 'browser_content': {
|
|
613
|
+
const res = await this.browserController.extract_content(args.selector);
|
|
614
|
+
this.workspace.addObservation({ tool: name, resultSummary: `Extracted content (${res.output.length} chars)`, verified: res.success });
|
|
615
|
+
return { result: res.output };
|
|
616
|
+
}
|
|
617
|
+
default:
|
|
618
|
+
return { result: `Unknown browser tool: "${name}"` };
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
async dispatchCoreTool(name, args) {
|
|
622
|
+
switch (name) {
|
|
623
|
+
case 'read_file': {
|
|
624
|
+
const targetPath = String(args.path || '').trim();
|
|
625
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
626
|
+
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
|
|
627
|
+
return { result: `Error: File not found at path "${targetPath}"` };
|
|
628
|
+
}
|
|
629
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
630
|
+
const lines = content.split('\n');
|
|
631
|
+
const start = Math.max(1, Number(args.startLine) || 1);
|
|
632
|
+
const end = Math.min(lines.length, Number(args.endLine) || lines.length);
|
|
633
|
+
const sliced = lines.slice(start - 1, end).map((l, i) => `${start + i} | ${l}`).join('\n');
|
|
634
|
+
this.workspace.addObservation({ tool: name, resultSummary: `Read ${targetPath} (${lines.length} lines)`, verified: true, target: targetPath });
|
|
635
|
+
return { result: `File: ${targetPath} (Lines ${start}-${end}/${lines.length}):\n${sliced}` };
|
|
636
|
+
}
|
|
637
|
+
case 'write_file': {
|
|
638
|
+
let targetPath = String(args.path || '').trim();
|
|
639
|
+
const intent = this.workspace.getExecutionIntent();
|
|
640
|
+
if (intent.targetDestination === 'Desktop' || targetPath.toLowerCase().includes('desktop')) {
|
|
641
|
+
const fileName = path.basename(targetPath);
|
|
642
|
+
targetPath = resolveDesktopPath(fileName);
|
|
643
|
+
}
|
|
644
|
+
const content = String(args.content || '');
|
|
645
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
646
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
647
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
648
|
+
this.modifiedFiles.add(targetPath);
|
|
649
|
+
this.workspace.recordArtifact({
|
|
650
|
+
filename: path.basename(targetPath),
|
|
651
|
+
path: targetPath,
|
|
652
|
+
type: 'file',
|
|
653
|
+
verified: true,
|
|
654
|
+
sizeBytes: Buffer.byteLength(content, 'utf-8'),
|
|
655
|
+
});
|
|
656
|
+
this.workspace.addObservation({ tool: name, resultSummary: `Wrote file ${targetPath} (${content.length} chars)`, verified: true, target: targetPath });
|
|
657
|
+
return { result: `Successfully wrote file "${targetPath}".` };
|
|
658
|
+
}
|
|
659
|
+
case 'edit_file': {
|
|
660
|
+
const targetPath = String(args.path || '').trim();
|
|
661
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
662
|
+
if (!fs.existsSync(fullPath))
|
|
663
|
+
return { result: `Error: File "${targetPath}" does not exist.` };
|
|
664
|
+
const currentContent = fs.readFileSync(fullPath, 'utf-8');
|
|
665
|
+
const replaceRes = robustSnippetReplace(currentContent, args.target, args.replacement);
|
|
666
|
+
if (!replaceRes.success) {
|
|
667
|
+
return { result: `Error: ${replaceRes.error}` };
|
|
668
|
+
}
|
|
669
|
+
fs.writeFileSync(fullPath, replaceRes.updatedContent, 'utf-8');
|
|
670
|
+
this.modifiedFiles.add(targetPath);
|
|
671
|
+
this.workspace.addObservation({ tool: name, resultSummary: `Edited file ${targetPath}`, verified: true, target: targetPath });
|
|
672
|
+
return { result: `Successfully updated "${targetPath}".` };
|
|
673
|
+
}
|
|
674
|
+
case 'run_command': {
|
|
675
|
+
const cmd = String(args.command || args.cmd || '').trim();
|
|
676
|
+
try {
|
|
677
|
+
const { execSync } = await import('child_process');
|
|
678
|
+
const out = execSync(cmd, { cwd: this.cwd, encoding: 'utf-8', maxBuffer: 1024 * 1024 * 5 });
|
|
679
|
+
const preview = out.slice(0, 5000);
|
|
680
|
+
this.workspace.addObservation({ tool: name, resultSummary: `Ran command "${cmd}"`, verified: true, target: cmd });
|
|
681
|
+
return { result: `Command Output:\n${preview}` };
|
|
682
|
+
}
|
|
683
|
+
catch (err) {
|
|
684
|
+
const errOutput = (err?.stdout || '') + '\n' + (err?.stderr || err?.message || String(err));
|
|
685
|
+
this.workspace.addObservation({ tool: name, resultSummary: `Command "${cmd}" failed`, verified: false, target: cmd });
|
|
686
|
+
return { result: `Command Failed:\n${errOutput.slice(0, 5000)}` };
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
case 'list_dir': {
|
|
690
|
+
const relPath = String(args.path || '.').trim();
|
|
691
|
+
const fullPath = path.isAbsolute(relPath) ? relPath : path.resolve(this.cwd, relPath);
|
|
692
|
+
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isDirectory()) {
|
|
693
|
+
return { result: `Error: Directory not found "${relPath}"` };
|
|
694
|
+
}
|
|
695
|
+
const entries = fs.readdirSync(fullPath);
|
|
696
|
+
return { result: `Directory contents of "${relPath}":\n${entries.slice(0, 100).join('\n')}` };
|
|
697
|
+
}
|
|
698
|
+
case 'grep_search': {
|
|
699
|
+
const query = String(args.query || '').trim();
|
|
700
|
+
const files = getDirectoryFiles(this.cwd);
|
|
701
|
+
const matches = [];
|
|
702
|
+
for (const f of files.slice(0, 200)) {
|
|
703
|
+
try {
|
|
704
|
+
const c = fs.readFileSync(path.resolve(this.cwd, f), 'utf-8');
|
|
705
|
+
if (c.includes(query))
|
|
706
|
+
matches.push(f);
|
|
707
|
+
}
|
|
708
|
+
catch { }
|
|
709
|
+
}
|
|
710
|
+
return { result: `Files containing "${query}" (${matches.length}):\n${matches.slice(0, 30).join('\n')}` };
|
|
711
|
+
}
|
|
712
|
+
case 'glob_search': {
|
|
713
|
+
const pattern = String(args.pattern || '').trim().toLowerCase();
|
|
714
|
+
const files = getDirectoryFiles(this.cwd);
|
|
715
|
+
const matched = files.filter((f) => f.toLowerCase().includes(pattern.replace(/\*/g, '')));
|
|
716
|
+
return { result: `Matching files (${matched.length}):\n${matched.slice(0, 30).join('\n')}` };
|
|
717
|
+
}
|
|
718
|
+
case 'tree_view': {
|
|
719
|
+
const maxDepth = Number(args.maxDepth) || 3;
|
|
720
|
+
const tree = buildDirectoryTree(this.cwd, '', 0, maxDepth);
|
|
721
|
+
return { result: `Workspace Tree:\n${tree}` };
|
|
722
|
+
}
|
|
723
|
+
case 'file_info': {
|
|
724
|
+
const targetPath = String(args.path || '').trim();
|
|
725
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
726
|
+
if (!fs.existsSync(fullPath))
|
|
727
|
+
return { result: `Error: File not found "${targetPath}"` };
|
|
728
|
+
const st = fs.statSync(fullPath);
|
|
729
|
+
return {
|
|
730
|
+
result: `File Info for "${targetPath}": Size: ${st.size} bytes, Modified: ${st.mtime.toISOString()}, IsDirectory: ${st.isDirectory()}`
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
case 'multi_edit_file': {
|
|
734
|
+
const targetPath = String(args.path || '').trim();
|
|
735
|
+
const edits = Array.isArray(args.edits) ? args.edits : [];
|
|
736
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(this.cwd, targetPath);
|
|
737
|
+
if (!fs.existsSync(fullPath))
|
|
738
|
+
return { result: `Error: File not found "${targetPath}"` };
|
|
739
|
+
let content = fs.readFileSync(fullPath, 'utf-8');
|
|
740
|
+
let count = 0;
|
|
741
|
+
for (const ed of edits) {
|
|
742
|
+
const res = robustSnippetReplace(content, ed.target, ed.replacement);
|
|
743
|
+
if (res.success) {
|
|
744
|
+
content = res.updatedContent;
|
|
745
|
+
count++;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
fs.writeFileSync(fullPath, content, 'utf-8');
|
|
749
|
+
this.modifiedFiles.add(targetPath);
|
|
750
|
+
return { result: `Applied ${count}/${edits.length} edits to "${targetPath}"` };
|
|
751
|
+
}
|
|
752
|
+
case 'fetch_url': {
|
|
753
|
+
const url = String(args.url || '').trim();
|
|
754
|
+
const resp = await fetch(url);
|
|
755
|
+
const textOut = await resp.text();
|
|
756
|
+
return { result: `Fetched ${url} (${textOut.length} chars):\n${textOut.slice(0, 4000)}` };
|
|
757
|
+
}
|
|
758
|
+
case 'git_diff': {
|
|
759
|
+
const { execSync } = await import('child_process');
|
|
760
|
+
try {
|
|
761
|
+
const diff = execSync('git diff', { cwd: this.cwd, encoding: 'utf-8' });
|
|
762
|
+
return { result: diff ? `Git diff:\n${diff.slice(0, 5000)}` : 'No git changes' };
|
|
763
|
+
}
|
|
764
|
+
catch {
|
|
765
|
+
return { result: 'git diff not available' };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
case 'ask_user': {
|
|
769
|
+
const q = String(args.question || 'Input required:');
|
|
770
|
+
safeNote(q, 'User Input Required');
|
|
771
|
+
const ans = await text({ message: chalk.cyan(q) });
|
|
772
|
+
const ansStr = typeof ans === 'symbol' ? 'cancelled' : String(ans);
|
|
773
|
+
return { result: `User answered: ${ansStr}` };
|
|
774
|
+
}
|
|
775
|
+
case 'speak_text': {
|
|
776
|
+
const txt = String(args.text || '');
|
|
777
|
+
speakText(txt, { async: true });
|
|
778
|
+
return { result: `Spoke: "${txt}"` };
|
|
779
|
+
}
|
|
780
|
+
case 'task_completed': {
|
|
781
|
+
const summary = String(args.summary || 'Completed');
|
|
782
|
+
const intent = this.workspace.getExecutionIntent();
|
|
783
|
+
if (intent.targetDestination === 'Desktop' && intent.targetArtifactName) {
|
|
784
|
+
const checkRes = await this.actionVerifier.verifyFileCreated(intent.targetArtifactName, 1, 'Desktop');
|
|
785
|
+
if (!checkRes.verified) {
|
|
786
|
+
return {
|
|
787
|
+
result: JSON.stringify({
|
|
788
|
+
success: false,
|
|
789
|
+
error: 'VerificationFailed',
|
|
790
|
+
message: `Cannot conclude task as completed: ${checkRes.message}`,
|
|
791
|
+
recoverable: true,
|
|
792
|
+
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.'
|
|
793
|
+
}, null, 2)
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
speakText(summary, { async: true });
|
|
798
|
+
return { result: summary };
|
|
799
|
+
}
|
|
800
|
+
default:
|
|
801
|
+
return { result: `Unknown tool call: "${name}"` };
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
extractJsonToolCall(content) {
|
|
805
|
+
if (!content)
|
|
806
|
+
return null;
|
|
807
|
+
const funcMatch = content.match(/<function=([a-zA-Z0-9_]+)>([\s\S]*?)<\/function>/i);
|
|
808
|
+
if (funcMatch && funcMatch[1] && funcMatch[2]) {
|
|
809
|
+
try {
|
|
810
|
+
return { tool: funcMatch[1].trim(), args: JSON.parse(funcMatch[2].trim()) };
|
|
811
|
+
}
|
|
812
|
+
catch {
|
|
813
|
+
return { tool: funcMatch[1].trim(), args: { raw: funcMatch[2].trim() } };
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/) || content.match(/\{[\s\S]*"tool"[\s\S]*\}/);
|
|
817
|
+
if (jsonMatch) {
|
|
818
|
+
try {
|
|
819
|
+
const obj = JSON.parse(jsonMatch[1] || jsonMatch[0]);
|
|
820
|
+
if (obj && typeof obj.tool === 'string') {
|
|
821
|
+
return { tool: obj.tool, args: obj.args || obj.parameters || {} };
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
catch { }
|
|
825
|
+
}
|
|
826
|
+
return null;
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
//# sourceMappingURL=agentRuntime.js.map
|