explorbot 0.1.9 → 0.1.11
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/README.md +27 -1
- package/bin/explorbot-cli.ts +86 -15
- package/boat/api-tester/src/ai/curler-tools.ts +3 -3
- package/boat/api-tester/src/ai/curler.ts +1 -1
- package/boat/api-tester/src/apibot.ts +2 -2
- package/boat/api-tester/src/config.ts +1 -1
- package/dist/bin/explorbot-cli.js +85 -14
- package/dist/boat/api-tester/src/ai/curler-tools.js +2 -2
- package/dist/boat/api-tester/src/apibot.js +2 -2
- package/dist/package.json +2 -2
- package/dist/rules/navigator/output.md +9 -0
- package/dist/rules/navigator/verification-actions.md +2 -0
- package/dist/src/action-result.js +23 -1
- package/dist/src/action.js +46 -38
- package/dist/src/ai/bosun.js +16 -2
- package/dist/src/ai/conversation.js +39 -0
- package/dist/src/ai/experience-compactor.js +235 -50
- package/dist/src/ai/historian/codeceptjs.js +109 -0
- package/dist/src/ai/historian/experience.js +320 -0
- package/dist/src/ai/historian/mixin.js +2 -0
- package/dist/src/ai/historian/playwright.js +145 -0
- package/dist/src/ai/historian/utils.js +18 -0
- package/dist/src/ai/historian.js +19 -398
- package/dist/src/ai/navigator.js +133 -80
- package/dist/src/ai/pilot.js +254 -13
- package/dist/src/ai/planner/subpages.js +1 -30
- package/dist/src/ai/planner.js +33 -13
- package/dist/src/ai/provider.js +55 -18
- package/dist/src/ai/rerunner.js +3 -3
- package/dist/src/ai/researcher/deep-analysis.js +1 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +1 -1
- package/dist/src/ai/researcher/locators.js +1 -1
- package/dist/src/ai/researcher/sections.js +8 -1
- package/dist/src/ai/researcher.js +43 -41
- package/dist/src/ai/rules.js +26 -14
- package/dist/src/ai/tester.js +90 -26
- package/dist/src/ai/tools.js +18 -10
- package/dist/src/api/request-store.js +20 -0
- package/dist/src/api/xhr-capture.js +19 -3
- package/dist/src/browser-server.js +16 -3
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/add-rule-command.js +12 -9
- package/dist/src/commands/base-command.js +20 -0
- package/dist/src/commands/clean-command.js +3 -2
- package/dist/src/commands/compact-command.js +138 -0
- package/dist/src/commands/context-command.js +7 -1
- package/dist/src/commands/drill-command.js +4 -1
- package/dist/src/commands/experience-command.js +104 -0
- package/dist/src/commands/explore-command.js +54 -19
- package/dist/src/commands/freesail-command.js +2 -0
- package/dist/src/commands/index.js +7 -3
- package/dist/src/commands/init-command.js +11 -10
- package/dist/src/commands/learn-command.js +1 -1
- package/dist/src/commands/navigate-command.js +4 -1
- package/dist/src/commands/plan-clear-command.js +4 -1
- package/dist/src/commands/plan-command.js +43 -4
- package/dist/src/commands/plan-edit-command.js +1 -1
- package/dist/src/commands/plan-load-command.js +4 -1
- package/dist/src/commands/plan-reload-command.js +4 -1
- package/dist/src/commands/plan-save-command.js +20 -8
- package/dist/src/commands/rerun-command.js +4 -0
- package/dist/src/commands/research-command.js +5 -2
- package/dist/src/commands/start-command.js +5 -1
- package/dist/src/commands/test-command.js +7 -1
- package/dist/src/components/App.js +15 -5
- package/dist/src/execution-controller.js +13 -2
- package/dist/src/experience-tracker.js +174 -83
- package/dist/src/explorbot.js +31 -22
- package/dist/src/explorer.js +12 -5
- package/dist/src/observability.js +50 -99
- package/dist/src/playwright-recorder.js +309 -0
- package/dist/src/reporter.js +17 -2
- package/dist/src/stats.js +2 -0
- package/dist/src/suite.js +1 -1
- package/dist/src/test-plan.js +12 -0
- package/dist/src/utils/aria.js +37 -1
- package/dist/src/utils/error-page.js +30 -7
- package/dist/src/utils/logger.js +1 -1
- package/dist/src/utils/next-steps.js +37 -0
- package/dist/src/utils/rules-loader.js +1 -1
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/url-matcher.js +50 -0
- package/package.json +2 -2
- package/rules/navigator/output.md +9 -0
- package/rules/navigator/verification-actions.md +2 -0
- package/src/action-result.ts +26 -1
- package/src/action.ts +44 -37
- package/src/ai/bosun.ts +16 -2
- package/src/ai/conversation.ts +37 -0
- package/src/ai/experience-compactor.ts +270 -63
- package/src/ai/historian/codeceptjs.ts +130 -0
- package/src/ai/historian/experience.ts +383 -0
- package/src/ai/historian/mixin.ts +4 -0
- package/src/ai/historian/playwright.ts +169 -0
- package/src/ai/historian/utils.ts +23 -0
- package/src/ai/historian.ts +35 -468
- package/src/ai/navigator.ts +140 -85
- package/src/ai/pilot.ts +259 -14
- package/src/ai/planner/subpages.ts +1 -24
- package/src/ai/planner.ts +34 -14
- package/src/ai/provider.ts +52 -18
- package/src/ai/rerunner.ts +3 -3
- package/src/ai/researcher/deep-analysis.ts +1 -1
- package/src/ai/researcher/fingerprint-worker.ts +1 -1
- package/src/ai/researcher/locators.ts +2 -2
- package/src/ai/researcher/sections.ts +7 -1
- package/src/ai/researcher.ts +47 -42
- package/src/ai/rules.ts +27 -14
- package/src/ai/task-agent.ts +1 -1
- package/src/ai/tester.ts +94 -26
- package/src/ai/tools.ts +53 -29
- package/src/api/request-store.ts +22 -0
- package/src/api/xhr-capture.ts +21 -3
- package/src/browser-server.ts +17 -3
- package/src/command-handler.ts +1 -1
- package/src/commands/add-rule-command.ts +13 -9
- package/src/commands/base-command.ts +26 -1
- package/src/commands/clean-command.ts +4 -3
- package/src/commands/compact-command.ts +156 -0
- package/src/commands/context-command.ts +8 -2
- package/src/commands/drill-command.ts +5 -2
- package/src/commands/experience-command.ts +125 -0
- package/src/commands/explore-command.ts +58 -21
- package/src/commands/freesail-command.ts +2 -0
- package/src/commands/index.ts +7 -3
- package/src/commands/init-command.ts +11 -10
- package/src/commands/learn-command.ts +2 -2
- package/src/commands/navigate-command.ts +5 -2
- package/src/commands/plan-clear-command.ts +5 -2
- package/src/commands/plan-command.ts +47 -5
- package/src/commands/plan-edit-command.ts +2 -2
- package/src/commands/plan-load-command.ts +5 -2
- package/src/commands/plan-reload-command.ts +5 -2
- package/src/commands/plan-save-command.ts +20 -9
- package/src/commands/rerun-command.ts +5 -0
- package/src/commands/research-command.ts +6 -3
- package/src/commands/start-command.ts +6 -2
- package/src/commands/test-command.ts +8 -2
- package/src/components/App.tsx +16 -5
- package/src/config.ts +6 -1
- package/src/execution-controller.ts +14 -3
- package/src/experience-tracker.ts +198 -100
- package/src/explorbot.ts +33 -23
- package/src/explorer.ts +14 -5
- package/src/observability.ts +50 -109
- package/src/playwright-recorder.ts +305 -0
- package/src/reporter.ts +17 -3
- package/src/stats.ts +4 -0
- package/src/suite.ts +1 -1
- package/src/test-plan.ts +12 -0
- package/src/utils/aria.ts +38 -1
- package/src/utils/error-page.ts +32 -7
- package/src/utils/logger.ts +1 -1
- package/src/utils/next-steps.ts +51 -0
- package/src/utils/rules-loader.ts +1 -1
- package/src/utils/test-files.ts +1 -1
- package/src/utils/url-matcher.ts +43 -0
package/src/ai/navigator.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
2
|
import { ActionResult } from '../action-result.js';
|
|
3
|
-
import
|
|
3
|
+
import type Action from '../action.ts';
|
|
4
|
+
import { ExperienceTracker, renderExperienceToc } from '../experience-tracker.js';
|
|
4
5
|
import Explorer from '../explorer.ts';
|
|
5
6
|
import { KnowledgeTracker } from '../knowledge-tracker.js';
|
|
6
7
|
import { type WebPageState, normalizeUrl } from '../state-manager.js';
|
|
@@ -8,14 +9,16 @@ import { extractCodeBlocks } from '../utils/code-extractor.js';
|
|
|
8
9
|
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
9
10
|
import { createDebug, pluralize, tag } from '../utils/logger.js';
|
|
10
11
|
import { loop, pause } from '../utils/loop.js';
|
|
12
|
+
import { RulesLoader } from '../utils/rules-loader.ts';
|
|
13
|
+
import { extractStatePath } from '../utils/url-matcher.js';
|
|
11
14
|
import type { Agent } from './agent.js';
|
|
12
15
|
import type { Conversation } from './conversation.js';
|
|
13
16
|
import { ExperienceCompactor } from './experience-compactor.js';
|
|
14
17
|
import type { Provider } from './provider.js';
|
|
15
18
|
import { Researcher } from './researcher.ts';
|
|
16
|
-
import { actionRule, locatorRule } from './rules.js';
|
|
17
|
-
import { RulesLoader } from '../utils/rules-loader.ts';
|
|
19
|
+
import { actionRule, locatorRule, unexpectedPopupRule } from './rules.js';
|
|
18
20
|
import { isInteractive } from './task-agent.js';
|
|
21
|
+
import { createAgentTools } from './tools.ts';
|
|
19
22
|
|
|
20
23
|
const debugLog = createDebug('explorbot:navigator');
|
|
21
24
|
|
|
@@ -25,8 +28,6 @@ class Navigator implements Agent {
|
|
|
25
28
|
private experienceCompactor: ExperienceCompactor;
|
|
26
29
|
private knowledgeTracker: KnowledgeTracker;
|
|
27
30
|
private experienceTracker: ExperienceTracker;
|
|
28
|
-
private currentAction: any = null;
|
|
29
|
-
private currentUrl: string | null = null;
|
|
30
31
|
private hooksRunner: HooksRunner;
|
|
31
32
|
|
|
32
33
|
private MAX_ATTEMPTS = Number.parseInt(process.env.MAX_ATTEMPTS || '5');
|
|
@@ -102,9 +103,7 @@ class Navigator implements Agent {
|
|
|
102
103
|
const actionResult = action.actionResult || ActionResult.fromState(action.stateManager.getCurrentState()!);
|
|
103
104
|
const originalMessage = `Navigate to: ${url}. Current page: ${actualPath}`;
|
|
104
105
|
|
|
105
|
-
this.
|
|
106
|
-
this.currentUrl = url;
|
|
107
|
-
const resolved = await this.resolveState(originalMessage, actionResult);
|
|
106
|
+
const resolved = await this.resolveState(originalMessage, actionResult, { action, expectedUrl: url });
|
|
108
107
|
if (!resolved) {
|
|
109
108
|
throw new Error(`Navigation to ${url} failed: redirected to ${actualPath} and could not resolve`);
|
|
110
109
|
}
|
|
@@ -116,9 +115,7 @@ class Navigator implements Agent {
|
|
|
116
115
|
But I got error: ${action.lastError?.message || 'Navigation failed'}.
|
|
117
116
|
`.trim();
|
|
118
117
|
|
|
119
|
-
this.
|
|
120
|
-
this.currentUrl = url;
|
|
121
|
-
const resolved = await this.resolveState(originalMessage, actionResult);
|
|
118
|
+
const resolved = await this.resolveState(originalMessage, actionResult, { action, expectedUrl: url });
|
|
122
119
|
if (!resolved) {
|
|
123
120
|
throw new Error(`Navigation to ${url} failed: ${action.lastError?.message}`);
|
|
124
121
|
}
|
|
@@ -136,10 +133,13 @@ class Navigator implements Agent {
|
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
|
|
139
|
-
async resolveState(message: string, actionResult: ActionResult): Promise<boolean> {
|
|
136
|
+
async resolveState(message: string, actionResult: ActionResult, opts?: { action?: Action; expectedUrl?: string }): Promise<boolean> {
|
|
140
137
|
tag('info').log('AI Navigator resolving state at', actionResult.url);
|
|
141
138
|
debugLog('Resolution message:', message);
|
|
142
139
|
|
|
140
|
+
const action = opts?.action ?? this.explorer.createAction();
|
|
141
|
+
const expectedUrl = opts?.expectedUrl;
|
|
142
|
+
|
|
143
143
|
let knowledge = '';
|
|
144
144
|
let experience = '';
|
|
145
145
|
|
|
@@ -153,26 +153,12 @@ class Navigator implements Agent {
|
|
|
153
153
|
</hint>`;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
experience = dedent`
|
|
164
|
-
<experience>
|
|
165
|
-
Here is the experience of interacting with the page.
|
|
166
|
-
Learn from it AND DO NOT REPEAT THE SAME MISTAKES.
|
|
167
|
-
If there was found successful solution to an issue, propose it as a first solution.
|
|
168
|
-
If there are no successful solutions, analyze failed intentions and actions to avoid them.
|
|
169
|
-
Do not try again same failed solutions
|
|
170
|
-
|
|
171
|
-
Focus on successful solutions and avoid actions and locators that caused errors in past.
|
|
172
|
-
|
|
173
|
-
${experienceContent}
|
|
174
|
-
|
|
175
|
-
</experience>`;
|
|
156
|
+
if (!actionResult.isInsideIframe) {
|
|
157
|
+
const successful = this.experienceTracker.getSuccessfulExperience(actionResult);
|
|
158
|
+
if (successful.length > 0) {
|
|
159
|
+
tag('substep').log(`Found ${successful.length} experience ${pluralize(successful.length, 'file')} for: ${actionResult.url}`);
|
|
160
|
+
experience = `<experience>\nPast successful recipes recorded from prior runs for this page. Prefer these solutions first if they match the goal.\n\n${successful.join('\n\n')}\n</experience>`;
|
|
161
|
+
}
|
|
176
162
|
}
|
|
177
163
|
|
|
178
164
|
const prompt = dedent`
|
|
@@ -200,6 +186,8 @@ class Navigator implements Agent {
|
|
|
200
186
|
|
|
201
187
|
${actionRule}
|
|
202
188
|
|
|
189
|
+
${unexpectedPopupRule}
|
|
190
|
+
|
|
203
191
|
${RulesLoader.loadRules('navigator', ['multiple-locator', 'output'], actionResult.url || '').replace('{{maxAttempts}}', String(this.MAX_ATTEMPTS))}
|
|
204
192
|
|
|
205
193
|
${experience}
|
|
@@ -210,20 +198,24 @@ class Navigator implements Agent {
|
|
|
210
198
|
const conversation = this.provider.startConversation(this.systemPrompt, 'navigator');
|
|
211
199
|
conversation.addUserText(prompt);
|
|
212
200
|
|
|
201
|
+
const tools = undefined;
|
|
202
|
+
|
|
213
203
|
let codeBlocks: string[] = [];
|
|
214
204
|
let htmlContextAdded = false;
|
|
215
205
|
let codeBlockIndex = 0;
|
|
216
206
|
let totalAttempts = 0;
|
|
207
|
+
const progressBlocks: string[] = [];
|
|
208
|
+
const batchFailures: Array<{ code: string; error: string }> = [];
|
|
217
209
|
|
|
218
210
|
let resolved = false;
|
|
219
211
|
await loop(
|
|
220
212
|
async ({ stop }) => {
|
|
221
213
|
if (codeBlocks.length === 0) {
|
|
222
|
-
const result = await this.provider.invokeConversation(conversation);
|
|
214
|
+
const result = await this.provider.invokeConversation(conversation, tools);
|
|
223
215
|
if (!result) return;
|
|
224
216
|
const aiResponse = result?.response?.text;
|
|
225
217
|
debugLog('AI:', aiResponse?.split('\n')[0]);
|
|
226
|
-
debugLog('Received AI response:', aiResponse
|
|
218
|
+
debugLog('Received AI response:', aiResponse?.length ?? 0, 'characters');
|
|
227
219
|
codeBlocks = extractCodeBlocks(aiResponse ?? '');
|
|
228
220
|
codeBlockIndex = 0;
|
|
229
221
|
}
|
|
@@ -235,45 +227,94 @@ class Navigator implements Agent {
|
|
|
235
227
|
|
|
236
228
|
const codeBlock = codeBlocks[codeBlockIndex];
|
|
237
229
|
if (!codeBlock) {
|
|
230
|
+
if (batchFailures.length === 0 && htmlContextAdded) {
|
|
231
|
+
stop();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
tag('substep').log('Feeding failures back to AI for a new batch...');
|
|
235
|
+
let contextMsg = 'Previous solutions did not work. Analyze the failures and try DIFFERENT strategies (not syntactic variants of the same locator).\n\n';
|
|
236
|
+
if (batchFailures.length > 0) {
|
|
237
|
+
const lines = batchFailures.map((f) => `- \`${f.code.split('\n')[0]}\` → ${f.error}`).join('\n');
|
|
238
|
+
contextMsg += `<previous_failures>\n${lines}\n</previous_failures>\n\n`;
|
|
239
|
+
}
|
|
238
240
|
if (!htmlContextAdded) {
|
|
239
241
|
htmlContextAdded = true;
|
|
240
|
-
|
|
241
|
-
conversation.addUserText(dedent`
|
|
242
|
-
Previous solutions did not work. Here is the full HTML context:
|
|
243
|
-
|
|
244
|
-
<page_html>
|
|
245
|
-
${await actionResult.combinedHtml()}
|
|
246
|
-
</page_html>
|
|
247
|
-
|
|
248
|
-
Please suggest new solutions based on this additional context.
|
|
249
|
-
`);
|
|
250
|
-
codeBlocks = [];
|
|
251
|
-
return;
|
|
242
|
+
contextMsg += `Full HTML context:\n\n<page_html>\n${await actionResult.combinedHtml()}\n</page_html>\n\n`;
|
|
252
243
|
}
|
|
253
|
-
|
|
244
|
+
contextMsg += 'Propose new solutions. If errors mention "intercepts pointer events" or timeouts on visible elements, an overlay is blocking — dismiss it first (Escape, click outside, Close button) before retrying the original action.';
|
|
245
|
+
conversation.addUserText(contextMsg);
|
|
246
|
+
codeBlocks = [];
|
|
247
|
+
batchFailures.length = 0;
|
|
254
248
|
return;
|
|
255
249
|
}
|
|
256
250
|
codeBlockIndex++;
|
|
257
251
|
totalAttempts++;
|
|
258
252
|
|
|
253
|
+
await this.explorer.switchToMainFrame();
|
|
254
|
+
|
|
255
|
+
const prevHash = action.actionResult?.getStateHash() ?? actionResult.getStateHash();
|
|
256
|
+
|
|
259
257
|
debugLog(`Attempting resolution: ${codeBlock}`);
|
|
260
|
-
|
|
258
|
+
const attemptOk = await action.attempt(codeBlock, message);
|
|
259
|
+
|
|
260
|
+
const page = action.playwrightHelper?.page;
|
|
261
|
+
if (page) {
|
|
262
|
+
try {
|
|
263
|
+
await page.waitForLoadState('load', { timeout: 5000 });
|
|
264
|
+
} catch {
|
|
265
|
+
// Navigation did not reach 'load' state within timeout; continue and verify URL
|
|
266
|
+
}
|
|
267
|
+
}
|
|
261
268
|
|
|
262
|
-
if (
|
|
263
|
-
|
|
264
|
-
const
|
|
269
|
+
if (!attemptOk) {
|
|
270
|
+
const raw = action.lastError?.message || 'attempt failed';
|
|
271
|
+
const firstMeaningful = raw.split('\n').find((l) => l.trim() && !l.trim().startsWith('at ')) || raw;
|
|
272
|
+
const shortErr = firstMeaningful.replace(/\s+/g, ' ').trim().slice(0, 220);
|
|
273
|
+
batchFailures.push({ code: codeBlock, error: shortErr });
|
|
274
|
+
}
|
|
265
275
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
if (expectedUrl) {
|
|
277
|
+
if (page) {
|
|
278
|
+
try {
|
|
279
|
+
await page.waitForURL((url: URL) => normalizeUrl(url.pathname) === normalizeUrl(expectedUrl), { timeout: 5000 });
|
|
280
|
+
} catch {
|
|
281
|
+
// URL did not transition to expectedUrl within timeout
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const freshState = await action.capturePageState();
|
|
285
|
+
const urlMatches = normalizeUrl(freshState.url || '') === normalizeUrl(expectedUrl);
|
|
286
|
+
const stateChanged = freshState.getStateHash() !== actionResult.getStateHash();
|
|
287
|
+
resolved = urlMatches && stateChanged;
|
|
288
|
+
|
|
289
|
+
if (!resolved && attemptOk) {
|
|
290
|
+
tag('warning').log(`URL verification failed: expected ${expectedUrl}, got ${freshState.url}`);
|
|
291
|
+
}
|
|
292
|
+
if (freshState.getStateHash() !== prevHash && (attemptOk || urlMatches)) {
|
|
293
|
+
progressBlocks.push(codeBlock);
|
|
271
294
|
}
|
|
295
|
+
} else {
|
|
296
|
+
resolved = attemptOk;
|
|
297
|
+
if (attemptOk) progressBlocks.push(codeBlock);
|
|
272
298
|
}
|
|
273
299
|
|
|
274
300
|
if (resolved) {
|
|
275
301
|
tag('success').log('Navigation resolved successfully');
|
|
276
|
-
|
|
302
|
+
let scenario = message.split('\n')[0];
|
|
303
|
+
if (expectedUrl) {
|
|
304
|
+
const fromPath = extractStatePath(actionResult.url || '');
|
|
305
|
+
const toPath = extractStatePath(expectedUrl);
|
|
306
|
+
scenario = `reach ${toPath} from ${fromPath}`;
|
|
307
|
+
}
|
|
308
|
+
const recipe = progressBlocks
|
|
309
|
+
.join('\n')
|
|
310
|
+
.split('\n')
|
|
311
|
+
.filter((line) => !/^\s*I\.amOnPage\s*\(/.test(line))
|
|
312
|
+
.join('\n')
|
|
313
|
+
.trim();
|
|
314
|
+
if (recipe) {
|
|
315
|
+
const body = `## FLOW: ${scenario}\n\n* ${scenario}\n\n\`\`\`js\n${recipe}\n\`\`\`\n\n---\n`;
|
|
316
|
+
this.experienceTracker.writeFlow(actionResult, body);
|
|
317
|
+
}
|
|
277
318
|
stop();
|
|
278
319
|
return;
|
|
279
320
|
}
|
|
@@ -290,9 +331,9 @@ class Navigator implements Agent {
|
|
|
290
331
|
}
|
|
291
332
|
);
|
|
292
333
|
|
|
293
|
-
if (!resolved &&
|
|
294
|
-
await
|
|
295
|
-
if (this.isOnExpectedPage(
|
|
334
|
+
if (!resolved && expectedUrl) {
|
|
335
|
+
await (action.getActor() as any).wait(1);
|
|
336
|
+
if (this.isOnExpectedPage(expectedUrl, action.stateManager)) {
|
|
296
337
|
resolved = true;
|
|
297
338
|
tag('success').log('Navigation resolved after delayed redirect');
|
|
298
339
|
}
|
|
@@ -303,13 +344,13 @@ class Navigator implements Agent {
|
|
|
303
344
|
}
|
|
304
345
|
|
|
305
346
|
if (!resolved && isInteractive()) {
|
|
306
|
-
const userInput = await pause(`Navigator failed to resolve. Current: ${
|
|
347
|
+
const userInput = await pause(`Navigator failed to resolve. Current: ${action.stateManager.getCurrentState()?.url}\n` + `Target: ${expectedUrl ?? '(none)'}\nEnter CodeceptJS commands (or press Enter to skip):`);
|
|
307
348
|
|
|
308
349
|
if (userInput?.trim()) {
|
|
309
|
-
resolved = await
|
|
310
|
-
if (resolved &&
|
|
311
|
-
await
|
|
312
|
-
if (!this.isOnExpectedPage(
|
|
350
|
+
resolved = await action.attempt(userInput, message);
|
|
351
|
+
if (resolved && expectedUrl) {
|
|
352
|
+
await (action.getActor() as any).wait(1);
|
|
353
|
+
if (!this.isOnExpectedPage(expectedUrl, action.stateManager)) {
|
|
313
354
|
resolved = false;
|
|
314
355
|
}
|
|
315
356
|
}
|
|
@@ -319,6 +360,23 @@ class Navigator implements Agent {
|
|
|
319
360
|
return resolved;
|
|
320
361
|
}
|
|
321
362
|
|
|
363
|
+
private buildExperienceTools(): { learn_experience: unknown } | undefined {
|
|
364
|
+
const stateManager = this.explorer.getStateManager();
|
|
365
|
+
const getState = () => {
|
|
366
|
+
const s = stateManager.getCurrentState();
|
|
367
|
+
return s ? ActionResult.fromState(s) : null;
|
|
368
|
+
};
|
|
369
|
+
const { learn_experience } = createAgentTools({
|
|
370
|
+
explorer: this.explorer,
|
|
371
|
+
researcher: null as unknown as Researcher,
|
|
372
|
+
navigator: this,
|
|
373
|
+
experienceTracker: this.experienceTracker,
|
|
374
|
+
getState,
|
|
375
|
+
});
|
|
376
|
+
if (!learn_experience) return undefined;
|
|
377
|
+
return { learn_experience };
|
|
378
|
+
}
|
|
379
|
+
|
|
322
380
|
async freeSail(opts?: { strategy?: 'deep' | 'shallow'; scope?: string; visitedUrls?: Set<string> }, actionResult?: ActionResult): Promise<{ target: string; reason: string } | null> {
|
|
323
381
|
const stateManager = this.explorer.getStateManager();
|
|
324
382
|
const state = stateManager.getCurrentState();
|
|
@@ -448,7 +506,7 @@ class Navigator implements Agent {
|
|
|
448
506
|
return suggestion;
|
|
449
507
|
}
|
|
450
508
|
|
|
451
|
-
async verifyState(message: string, actionResult: ActionResult): Promise<{ verified: boolean; successfulCodes: string[]; totalAttempted: number }> {
|
|
509
|
+
async verifyState(message: string, actionResult: ActionResult): Promise<{ verified: boolean; successfulCodes: string[]; assertionSteps: Array<{ name: string; args: any[] }>; totalAttempted: number }> {
|
|
452
510
|
tag('info').log('AI Navigator verifying state at', actionResult.url);
|
|
453
511
|
debugLog('Verification message:', message);
|
|
454
512
|
|
|
@@ -465,22 +523,13 @@ class Navigator implements Agent {
|
|
|
465
523
|
</hint>`;
|
|
466
524
|
}
|
|
467
525
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
experience = dedent`
|
|
476
|
-
<experience>
|
|
477
|
-
Here is the experience of interacting with the page.
|
|
478
|
-
Learn from it AND DO NOT REPEAT THE SAME MISTAKES.
|
|
479
|
-
If there was found successful solution to an issue, propose it as a first solution.
|
|
480
|
-
|
|
481
|
-
${experienceContent}
|
|
482
|
-
|
|
483
|
-
</experience>`;
|
|
526
|
+
if (!actionResult.isInsideIframe) {
|
|
527
|
+
const toc = this.experienceTracker.getExperienceTableOfContents(actionResult);
|
|
528
|
+
if (toc.length > 0) {
|
|
529
|
+
const totalSections = toc.reduce((sum, entry) => sum + entry.sections.length, 0);
|
|
530
|
+
tag('substep').log(`Found ${toc.length} experience ${pluralize(toc.length, 'file')} (${totalSections} sections) for: ${actionResult.url}`);
|
|
531
|
+
experience = renderExperienceToc(toc);
|
|
532
|
+
}
|
|
484
533
|
}
|
|
485
534
|
|
|
486
535
|
const prompt = dedent`
|
|
@@ -522,18 +571,21 @@ class Navigator implements Agent {
|
|
|
522
571
|
const conversation = this.provider.startConversation(this.systemPrompt, 'navigator');
|
|
523
572
|
conversation.addUserText(prompt);
|
|
524
573
|
|
|
574
|
+
const tools = this.buildExperienceTools();
|
|
575
|
+
|
|
525
576
|
let codeBlocks: string[] = [];
|
|
526
577
|
const successfulCodes: string[] = [];
|
|
578
|
+
const assertionSteps: Array<{ name: string; args: any[] }> = [];
|
|
527
579
|
|
|
528
580
|
const action = this.explorer.createAction();
|
|
529
581
|
|
|
530
582
|
await loop(
|
|
531
583
|
async ({ stop, iteration }) => {
|
|
532
584
|
if (codeBlocks.length === 0) {
|
|
533
|
-
const result = await this.provider.invokeConversation(conversation);
|
|
585
|
+
const result = await this.provider.invokeConversation(conversation, tools);
|
|
534
586
|
if (!result) return;
|
|
535
587
|
const aiResponse = result?.response?.text;
|
|
536
|
-
debugLog('Received AI response:', aiResponse
|
|
588
|
+
debugLog('Received AI response:', aiResponse?.length ?? 0, 'characters');
|
|
537
589
|
tag('step').log('Verifying assertion...');
|
|
538
590
|
codeBlocks = extractCodeBlocks(aiResponse ?? '');
|
|
539
591
|
}
|
|
@@ -548,11 +600,14 @@ class Navigator implements Agent {
|
|
|
548
600
|
return;
|
|
549
601
|
}
|
|
550
602
|
|
|
603
|
+
await this.explorer.switchToMainFrame();
|
|
604
|
+
|
|
551
605
|
const verified = await action.attempt(codeBlock, message, false);
|
|
552
606
|
|
|
553
607
|
if (verified) {
|
|
554
608
|
tag('success').log('Verification passed');
|
|
555
609
|
successfulCodes.push(codeBlock);
|
|
610
|
+
assertionSteps.push(...action.assertionSteps);
|
|
556
611
|
}
|
|
557
612
|
},
|
|
558
613
|
{
|
|
@@ -572,7 +627,7 @@ class Navigator implements Agent {
|
|
|
572
627
|
actionResult.addVerification(message, verified);
|
|
573
628
|
this.explorer.getStateManager().updateState(actionResult);
|
|
574
629
|
|
|
575
|
-
return { verified, successfulCodes, totalAttempted };
|
|
630
|
+
return { verified, successfulCodes, assertionSteps, totalAttempted };
|
|
576
631
|
}
|
|
577
632
|
}
|
|
578
633
|
|