explorbot 0.2.4 → 0.3.0
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/bin/explorbot-cli.ts +19 -7
- package/boat/api-tester/src/cli.ts +17 -0
- package/boat/doc-collector/src/cli.ts +14 -1
- package/boat/prima/README.md +96 -0
- package/boat/prima/package.json +14 -10
- package/boat/prima/src/cli.ts +29 -12
- package/boat/prima/src/envelope.ts +35 -13
- package/boat/prima/src/prima.ts +78 -45
- package/dist/bin/explorbot-cli.js +19 -7
- package/dist/boat/api-tester/src/cli.js +17 -0
- package/dist/boat/doc-collector/src/cli.js +14 -1
- package/dist/boat/prima/src/cli.js +26 -7
- package/dist/boat/prima/src/envelope.js +32 -8
- package/dist/boat/prima/src/prima.js +75 -43
- package/dist/models.json +4 -4
- package/dist/package.json +6 -2
- package/dist/src/action-result.d.ts +13 -0
- package/dist/src/action-result.js +46 -15
- package/dist/src/action.d.ts +5 -2
- package/dist/src/action.js +53 -18
- package/dist/src/ai/captain/web-mode.js +1 -2
- package/dist/src/ai/captain.d.ts +20 -0
- package/dist/src/ai/captain.js +10 -1
- package/dist/src/ai/driller.js +6 -2
- package/dist/src/ai/fisherman-tools.d.ts +40 -1
- package/dist/src/ai/fisherman-tools.js +39 -0
- package/dist/src/ai/fisherman.js +2 -1
- package/dist/src/ai/navigator.d.ts +28 -0
- package/dist/src/ai/navigator.js +223 -175
- package/dist/src/ai/pilot.d.ts +7 -4
- package/dist/src/ai/pilot.js +89 -30
- package/dist/src/ai/planner/subpages.js +2 -16
- package/dist/src/ai/planner.js +1 -1
- package/dist/src/ai/provider.d.ts +2 -2
- package/dist/src/ai/provider.js +28 -22
- package/dist/src/ai/researcher/cache.d.ts +10 -3
- package/dist/src/ai/researcher/cache.js +23 -10
- package/dist/src/ai/researcher/deep-analysis.js +1 -1
- package/dist/src/ai/researcher/fingerprint-worker.js +21 -4
- package/dist/src/ai/researcher.js +6 -4
- package/dist/src/ai/rules.js +1 -5
- package/dist/src/ai/session-analyst.js +2 -0
- package/dist/src/ai/tester.d.ts +6 -3
- package/dist/src/ai/tester.js +30 -35
- package/dist/src/ai/tools.d.ts +8 -5
- package/dist/src/ai/tools.js +83 -57
- package/dist/src/commands/config-command.d.ts +51 -0
- package/dist/src/commands/config-command.js +117 -0
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init-command.js +13 -20
- package/dist/src/config.d.ts +8 -1
- package/dist/src/config.js +43 -1
- package/dist/src/experience-tracker.d.ts +2 -0
- package/dist/src/experience-tracker.js +12 -0
- package/dist/src/explorbot.js +5 -2
- package/dist/src/playwright-recorder.js +6 -12
- package/dist/src/remote.d.ts +3 -2
- package/dist/src/remote.js +8 -2
- package/dist/src/state-manager.d.ts +1 -1
- package/dist/src/state-manager.js +3 -1
- package/dist/src/test-plan.d.ts +9 -0
- package/dist/src/test-plan.js +30 -0
- package/dist/src/utils/html-diff.d.ts +5 -0
- package/dist/src/utils/html-diff.js +65 -6
- package/dist/src/utils/logger.d.ts +1 -1
- package/dist/src/utils/logger.js +8 -0
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +32 -0
- package/dist/src/utils/url-matcher.d.ts +1 -0
- package/dist/src/utils/url-matcher.js +31 -2
- package/docs/basics/getting-started.md +33 -10
- package/docs/basics/providers.md +6 -4
- package/docs/contributing/npm-package.md +73 -4
- package/docs/index.json +2 -1
- package/docs/reference/commands.md +3 -0
- package/docs/reference/websocket.md +50 -0
- package/docs/superpowers/specs/2026-08-18-prima-false-verdicts.md +159 -0
- package/models.json +4 -4
- package/package.json +6 -2
- package/src/action-result.ts +61 -16
- package/src/action.ts +56 -18
- package/src/ai/captain/web-mode.ts +1 -2
- package/src/ai/captain.ts +9 -1
- package/src/ai/driller.ts +6 -2
- package/src/ai/fisherman-tools.ts +35 -0
- package/src/ai/fisherman.ts +2 -1
- package/src/ai/navigator.ts +238 -179
- package/src/ai/pilot.ts +104 -36
- package/src/ai/planner/subpages.ts +2 -13
- package/src/ai/planner.ts +1 -1
- package/src/ai/provider.ts +29 -21
- package/src/ai/researcher/cache.ts +29 -11
- package/src/ai/researcher/deep-analysis.ts +1 -1
- package/src/ai/researcher/fingerprint-worker.ts +23 -5
- package/src/ai/researcher.ts +6 -4
- package/src/ai/rules.ts +1 -5
- package/src/ai/session-analyst.ts +2 -0
- package/src/ai/tester.ts +33 -34
- package/src/ai/tools.ts +88 -61
- package/src/commands/config-command.ts +146 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/init-command.ts +14 -20
- package/src/config.ts +47 -2
- package/src/experience-tracker.ts +13 -0
- package/src/explorbot.ts +4 -2
- package/src/playwright-recorder.ts +6 -11
- package/src/remote.ts +8 -2
- package/src/state-manager.ts +5 -2
- package/src/test-plan.ts +38 -0
- package/src/utils/html-diff.ts +72 -7
- package/src/utils/logger.ts +9 -1
- package/src/utils/strings.ts +36 -0
- package/src/utils/url-matcher.ts +27 -2
package/dist/src/action.js
CHANGED
|
@@ -17,6 +17,8 @@ const debugLog = createDebug('explorbot:action');
|
|
|
17
17
|
const CAPTURE_NAVIGATION_TRANSITION_ATTEMPTS = 3;
|
|
18
18
|
const DEFAULT_ACTION_TIMEOUT = 3000;
|
|
19
19
|
const DEFAULT_PAGE_TIMEOUT = 3000;
|
|
20
|
+
const MAX_NETWORK_CALLS = 10;
|
|
21
|
+
const IMPORTANT_LOG_LEVELS = new Set(['info', 'error', 'warning', 'warn']);
|
|
20
22
|
class Action {
|
|
21
23
|
actor;
|
|
22
24
|
stateManager;
|
|
@@ -32,6 +34,8 @@ class Action {
|
|
|
32
34
|
recorder;
|
|
33
35
|
recovery;
|
|
34
36
|
mainDocumentStatus = undefined;
|
|
37
|
+
networkRequests = [];
|
|
38
|
+
baseOrigin;
|
|
35
39
|
constructor(actor, stateManager, recorder, recovery) {
|
|
36
40
|
this.actor = actor;
|
|
37
41
|
this.stateManager = stateManager;
|
|
@@ -39,6 +43,7 @@ class Action {
|
|
|
39
43
|
this.playwrightHelper = container.helpers('Playwright');
|
|
40
44
|
this.recorder = recorder;
|
|
41
45
|
this.recovery = recovery || ((fn) => fn());
|
|
46
|
+
this.baseOrigin = URL.parse(this.config.playwright?.url || '')?.origin || '';
|
|
42
47
|
}
|
|
43
48
|
async saveScreenshot() {
|
|
44
49
|
const currentState = this.stateManager.getCurrentState();
|
|
@@ -50,6 +55,7 @@ class Action {
|
|
|
50
55
|
await this.actor.saveScreenshot(filename);
|
|
51
56
|
if (currentState)
|
|
52
57
|
currentState.screenshotFile = filename;
|
|
58
|
+
tag('data').log('screenshot', { path: outputPath('states', filename) });
|
|
53
59
|
return filename;
|
|
54
60
|
}
|
|
55
61
|
catch (err) {
|
|
@@ -97,7 +103,10 @@ class Action {
|
|
|
97
103
|
const screenshotPath = join(statesDir, filename);
|
|
98
104
|
screenshotFile = await page
|
|
99
105
|
?.screenshot({ path: screenshotPath, fullPage: true })
|
|
100
|
-
.then(() =>
|
|
106
|
+
.then(() => {
|
|
107
|
+
tag('data').log('screenshot', { path: screenshotPath });
|
|
108
|
+
return filename;
|
|
109
|
+
})
|
|
101
110
|
.catch((err) => {
|
|
102
111
|
debugLog('Screenshot failed, continuing without it:', err);
|
|
103
112
|
return undefined;
|
|
@@ -113,9 +122,7 @@ class Action {
|
|
|
113
122
|
const logPath = join(statesDir, logFile);
|
|
114
123
|
const formattedLogs = browserLogs.map((log) => {
|
|
115
124
|
const logTimestamp = new Date().toISOString();
|
|
116
|
-
|
|
117
|
-
const message = log.text || log.message || String(log);
|
|
118
|
-
return `[${logTimestamp}] ${level}: ${message}`;
|
|
125
|
+
return `[${logTimestamp}] ${log.type.toUpperCase()}: ${log.text}`;
|
|
119
126
|
});
|
|
120
127
|
fs.writeFileSync(logPath, `${formattedLogs.join('\n')}\n`, 'utf8');
|
|
121
128
|
debugLog('Page:', { url, title, size: html.length, html: html.substring(0, 100) });
|
|
@@ -138,12 +145,15 @@ class Action {
|
|
|
138
145
|
fs.writeFileSync(ariaPath, ariaSnapshot, 'utf8');
|
|
139
146
|
ariaSnapshotFile = ariaFileName;
|
|
140
147
|
}
|
|
148
|
+
const networkRequests = this.networkRequests;
|
|
149
|
+
this.networkRequests = [];
|
|
141
150
|
const result = new ActionResult({
|
|
142
151
|
html,
|
|
143
152
|
title,
|
|
144
153
|
httpStatus: await this.captureMainDocumentStatus(),
|
|
145
154
|
url,
|
|
146
155
|
browserLogs,
|
|
156
|
+
networkRequests,
|
|
147
157
|
htmlFile,
|
|
148
158
|
logFile,
|
|
149
159
|
screenshotFile,
|
|
@@ -188,27 +198,52 @@ class Action {
|
|
|
188
198
|
return undefined;
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
|
-
|
|
201
|
+
captureResponses() {
|
|
192
202
|
const page = this.playwrightHelper.page;
|
|
193
203
|
if (!page?.on || !page?.off)
|
|
194
204
|
return () => { };
|
|
195
205
|
this.mainDocumentStatus = undefined;
|
|
206
|
+
this.networkRequests = [];
|
|
196
207
|
const handler = (response) => {
|
|
197
208
|
const request = response.request();
|
|
198
|
-
if (request.resourceType() !== 'document')
|
|
199
|
-
return;
|
|
200
|
-
if (response.frame() !== page.mainFrame())
|
|
201
|
-
return;
|
|
202
209
|
const status = response.status();
|
|
203
210
|
if (typeof status !== 'number')
|
|
204
211
|
return;
|
|
205
212
|
if (status <= 0)
|
|
206
213
|
return;
|
|
207
|
-
|
|
214
|
+
if (request.resourceType() === 'document') {
|
|
215
|
+
if (response.frame() !== page.mainFrame())
|
|
216
|
+
return;
|
|
217
|
+
this.mainDocumentStatus = status;
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
this.recordNetworkCall(request, status);
|
|
208
221
|
};
|
|
209
222
|
page.on('response', handler);
|
|
210
223
|
return () => page.off('response', handler);
|
|
211
224
|
}
|
|
225
|
+
recordNetworkCall(request, status) {
|
|
226
|
+
const resourceType = request.resourceType();
|
|
227
|
+
if (resourceType !== 'xhr' && resourceType !== 'fetch')
|
|
228
|
+
return;
|
|
229
|
+
const url = URL.parse(request.url());
|
|
230
|
+
if (!url)
|
|
231
|
+
return;
|
|
232
|
+
if (url.origin !== this.baseOrigin)
|
|
233
|
+
return;
|
|
234
|
+
const call = { method: request.method(), path: url.pathname, status };
|
|
235
|
+
if (this.networkRequests.some((r) => r.method === call.method && r.path === call.path && r.status === call.status))
|
|
236
|
+
return;
|
|
237
|
+
if (this.networkRequests.length >= MAX_NETWORK_CALLS) {
|
|
238
|
+
if (status < 400)
|
|
239
|
+
return;
|
|
240
|
+
const succeeded = this.networkRequests.findIndex((r) => r.status < 400);
|
|
241
|
+
if (succeeded === -1)
|
|
242
|
+
return;
|
|
243
|
+
this.networkRequests.splice(succeeded, 1);
|
|
244
|
+
}
|
|
245
|
+
this.networkRequests.push(call);
|
|
246
|
+
}
|
|
212
247
|
/**
|
|
213
248
|
* Capture HTML snapshots of all iframes on the page
|
|
214
249
|
*/
|
|
@@ -245,12 +280,7 @@ class Action {
|
|
|
245
280
|
async captureBrowserLogs() {
|
|
246
281
|
try {
|
|
247
282
|
const logs = await this.actor.grabBrowserLogs();
|
|
248
|
-
|
|
249
|
-
const importantLogs = logs.filter((log) => {
|
|
250
|
-
const level = log.type || log.level;
|
|
251
|
-
return ['info', 'error', 'warning', 'warn'].includes(level);
|
|
252
|
-
});
|
|
253
|
-
return importantLogs;
|
|
283
|
+
return logs.map(toBrowserLog).filter((log) => IMPORTANT_LOG_LEVELS.has(log.type));
|
|
254
284
|
}
|
|
255
285
|
catch (error) {
|
|
256
286
|
debugLog('Failed to capture browser logs:', error);
|
|
@@ -266,7 +296,7 @@ class Action {
|
|
|
266
296
|
const stepListener = attachStepLogger(executedSteps, assertionSteps);
|
|
267
297
|
const groupId = this.recorder ? await this.recorder.beginAction(codeString) : null;
|
|
268
298
|
this.playwrightGroupId = groupId;
|
|
269
|
-
const
|
|
299
|
+
const detachResponses = this.captureResponses();
|
|
270
300
|
const activeSpan = Observability.getSpan();
|
|
271
301
|
const tracer = trace.getTracer('ai');
|
|
272
302
|
const stepSpan = activeSpan ? tracer.startSpan('codeceptjs.step', undefined, trace.setSpan(context.active(), activeSpan)) : null;
|
|
@@ -310,7 +340,7 @@ class Action {
|
|
|
310
340
|
}
|
|
311
341
|
finally {
|
|
312
342
|
this.restorePageTimeout();
|
|
313
|
-
|
|
343
|
+
detachResponses();
|
|
314
344
|
if (groupId)
|
|
315
345
|
await this.recorder.endAction();
|
|
316
346
|
detachStepLogger(stepListener);
|
|
@@ -389,6 +419,11 @@ async function captureHtml(page, frame, actor) {
|
|
|
389
419
|
return actor.grabSource();
|
|
390
420
|
throw new Error('Playwright page is unavailable for HTML capture');
|
|
391
421
|
}
|
|
422
|
+
function toBrowserLog(log) {
|
|
423
|
+
const type = typeof log.type === 'function' ? log.type() : log.type || log.level || 'log';
|
|
424
|
+
const text = typeof log.text === 'function' ? log.text() : log.text || log.message || String(log);
|
|
425
|
+
return { type, text: text.replace(/\s+/g, ' ').trim() };
|
|
426
|
+
}
|
|
392
427
|
async function captureTitle(page, actor) {
|
|
393
428
|
if (page?.title)
|
|
394
429
|
return page.title();
|
|
@@ -15,7 +15,7 @@ export function WithWebMode(Base) {
|
|
|
15
15
|
researcher: ctx.explorBot.agentResearcher(),
|
|
16
16
|
navigator: ctx.explorBot.agentNavigator(),
|
|
17
17
|
});
|
|
18
|
-
const { see, context, visualClick
|
|
18
|
+
const { see, context, visualClick } = agentTools;
|
|
19
19
|
const tools = {
|
|
20
20
|
navigate: tool({
|
|
21
21
|
description: 'Navigate to a URL or page description using AI-powered navigation.',
|
|
@@ -115,7 +115,6 @@ export function WithWebMode(Base) {
|
|
|
115
115
|
}),
|
|
116
116
|
...codeceptTools,
|
|
117
117
|
context,
|
|
118
|
-
learnExperience,
|
|
119
118
|
};
|
|
120
119
|
if (see)
|
|
121
120
|
tools.see = see;
|
package/dist/src/ai/captain.d.ts
CHANGED
|
@@ -59,6 +59,16 @@ export declare class Captain extends CaptainBase implements Agent {
|
|
|
59
59
|
planSummary(): string;
|
|
60
60
|
reinjectContextIfNeeded(conversation: Conversation, currentState: WebPageState): Promise<void>;
|
|
61
61
|
coreTools(task: Task, onDone: (summary: string) => void): {
|
|
62
|
+
learnExperience: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
|
|
63
|
+
fileTag: any;
|
|
64
|
+
sectionIndex: any;
|
|
65
|
+
}, {
|
|
66
|
+
title: string;
|
|
67
|
+
url: string;
|
|
68
|
+
content: string;
|
|
69
|
+
} | {
|
|
70
|
+
error: string;
|
|
71
|
+
}, import("@ai-sdk/provider-utils").Context>>;
|
|
62
72
|
done: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
|
|
63
73
|
summary: string;
|
|
64
74
|
details?: string;
|
|
@@ -91,6 +101,16 @@ export declare class Captain extends CaptainBase implements Agent {
|
|
|
91
101
|
}, import("@ai-sdk/provider-utils").Context>>;
|
|
92
102
|
};
|
|
93
103
|
tools(task: Task, onDone: (summary: string) => void): Promise<{
|
|
104
|
+
learnExperience: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
|
|
105
|
+
fileTag: any;
|
|
106
|
+
sectionIndex: any;
|
|
107
|
+
}, {
|
|
108
|
+
title: string;
|
|
109
|
+
url: string;
|
|
110
|
+
content: string;
|
|
111
|
+
} | {
|
|
112
|
+
error: string;
|
|
113
|
+
}, import("@ai-sdk/provider-utils").Context>>;
|
|
94
114
|
done: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
|
|
95
115
|
summary: string;
|
|
96
116
|
details?: string;
|
package/dist/src/ai/captain.js
CHANGED
|
@@ -15,7 +15,7 @@ import { WithWebMode } from "./captain/web-mode.js";
|
|
|
15
15
|
import { toolExecutionLabel } from './conversation.js';
|
|
16
16
|
import { Researcher } from "./researcher.js";
|
|
17
17
|
import { TaskAgent } from "./task-agent.js";
|
|
18
|
-
import { withdrawVisionTools } from "./tools.js";
|
|
18
|
+
import { createLearnExperienceTool, withdrawVisionTools } from "./tools.js";
|
|
19
19
|
const MAX_STEPS = 15;
|
|
20
20
|
const CaptainBase = WithTestMode(WithWebMode(WithIdleMode(TaskAgent)));
|
|
21
21
|
export class Captain extends CaptainBase {
|
|
@@ -216,6 +216,15 @@ export class Captain extends CaptainBase {
|
|
|
216
216
|
}
|
|
217
217
|
coreTools(task, onDone) {
|
|
218
218
|
return {
|
|
219
|
+
learnExperience: createLearnExperienceTool({
|
|
220
|
+
getExperienceTracker: () => this.getExperienceTracker(),
|
|
221
|
+
getState: () => {
|
|
222
|
+
const state = this.explorBot.stateManager().getCurrentState();
|
|
223
|
+
if (!state)
|
|
224
|
+
return null;
|
|
225
|
+
return ActionResult.fromState(state);
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
219
228
|
done: tool({
|
|
220
229
|
description: 'Call when the user request is fulfilled.',
|
|
221
230
|
inputSchema: z.object({
|
package/dist/src/ai/driller.js
CHANGED
|
@@ -14,7 +14,7 @@ import { eidxInContainer } from "../utils/web-eidx.js";
|
|
|
14
14
|
import { WebElement } from "../utils/web-element.js";
|
|
15
15
|
import { drillLocatorRule } from "./rules.js";
|
|
16
16
|
import { TaskAgent, isInteractive } from "./task-agent.js";
|
|
17
|
-
import { createCodeceptJSTools } from "./tools.js";
|
|
17
|
+
import { createCodeceptJSTools, createLearnExperienceTool } from "./tools.js";
|
|
18
18
|
const debugLog = createDebug('explorbot:driller');
|
|
19
19
|
export class Driller extends TaskAgent {
|
|
20
20
|
ACTION_TOOLS = ['click', 'pressKey', 'form'];
|
|
@@ -234,7 +234,11 @@ export class Driller extends TaskAgent {
|
|
|
234
234
|
conversation.addUserText(await this.buildComponentPrompt(originalState, component));
|
|
235
235
|
let finished = false;
|
|
236
236
|
const actionTools = this.createVerifiedActionTools(createCodeceptJSTools(this.toolDeps, test), component);
|
|
237
|
-
const
|
|
237
|
+
const learnExperience = createLearnExperienceTool({
|
|
238
|
+
getExperienceTracker: () => this.getExperienceTracker(),
|
|
239
|
+
getState: () => ActionResult.fromState(this.stateManager.getCurrentState() || originalState),
|
|
240
|
+
});
|
|
241
|
+
const tools = { ...actionTools, learnExperience, ...this.createDrillFlowTools(originalState, test, interactive) };
|
|
238
242
|
await loop(async ({ stop, iteration }) => {
|
|
239
243
|
debugLog(`Drilling component ${component.name}, iteration ${iteration}`);
|
|
240
244
|
setActivity(`${this.emoji} Drilling ${component.name}...`, 'action');
|
|
@@ -9,18 +9,49 @@ export declare function createFishermanTools(apiClient: ApiClient, requestStore:
|
|
|
9
9
|
method: any;
|
|
10
10
|
path: any;
|
|
11
11
|
}, {
|
|
12
|
+
source: string;
|
|
13
|
+
method: any;
|
|
14
|
+
path: any;
|
|
15
|
+
definition: string;
|
|
16
|
+
rejectedCapture: {
|
|
17
|
+
status: number;
|
|
18
|
+
requestBody: any;
|
|
19
|
+
};
|
|
20
|
+
usable?: undefined;
|
|
21
|
+
rejectedRequestBody?: undefined;
|
|
22
|
+
status?: undefined;
|
|
23
|
+
requestBody?: undefined;
|
|
24
|
+
error?: undefined;
|
|
25
|
+
} | {
|
|
26
|
+
source: string;
|
|
27
|
+
method: any;
|
|
28
|
+
path: any;
|
|
29
|
+
usable: boolean;
|
|
30
|
+
rejectedRequestBody: any;
|
|
31
|
+
status: number;
|
|
32
|
+
definition?: undefined;
|
|
33
|
+
rejectedCapture?: undefined;
|
|
34
|
+
requestBody?: undefined;
|
|
35
|
+
error?: undefined;
|
|
36
|
+
} | {
|
|
12
37
|
source: string;
|
|
13
38
|
method: string;
|
|
14
39
|
path: string;
|
|
15
40
|
status: number;
|
|
16
41
|
requestBody: any;
|
|
17
42
|
definition?: undefined;
|
|
43
|
+
rejectedCapture?: undefined;
|
|
44
|
+
usable?: undefined;
|
|
45
|
+
rejectedRequestBody?: undefined;
|
|
18
46
|
error?: undefined;
|
|
19
47
|
} | {
|
|
20
48
|
source: string;
|
|
21
49
|
definition: string;
|
|
22
50
|
method?: undefined;
|
|
23
51
|
path?: undefined;
|
|
52
|
+
rejectedCapture?: undefined;
|
|
53
|
+
usable?: undefined;
|
|
54
|
+
rejectedRequestBody?: undefined;
|
|
24
55
|
status?: undefined;
|
|
25
56
|
requestBody?: undefined;
|
|
26
57
|
error?: undefined;
|
|
@@ -29,20 +60,25 @@ export declare function createFishermanTools(apiClient: ApiClient, requestStore:
|
|
|
29
60
|
error: any;
|
|
30
61
|
method?: undefined;
|
|
31
62
|
path?: undefined;
|
|
63
|
+
definition?: undefined;
|
|
64
|
+
rejectedCapture?: undefined;
|
|
65
|
+
usable?: undefined;
|
|
66
|
+
rejectedRequestBody?: undefined;
|
|
32
67
|
status?: undefined;
|
|
33
68
|
requestBody?: undefined;
|
|
34
|
-
definition?: undefined;
|
|
35
69
|
}, import("@ai-sdk/provider-utils").Context>>;
|
|
36
70
|
request: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<any, {
|
|
37
71
|
success: boolean;
|
|
38
72
|
error: string;
|
|
39
73
|
status?: undefined;
|
|
40
74
|
statusText?: undefined;
|
|
75
|
+
category?: undefined;
|
|
41
76
|
errorPreview?: undefined;
|
|
42
77
|
} | {
|
|
43
78
|
success: boolean;
|
|
44
79
|
status: number;
|
|
45
80
|
statusText: string;
|
|
81
|
+
category: ResponseCategory;
|
|
46
82
|
errorPreview: string;
|
|
47
83
|
error?: undefined;
|
|
48
84
|
} | {
|
|
@@ -50,6 +86,7 @@ export declare function createFishermanTools(apiClient: ApiClient, requestStore:
|
|
|
50
86
|
status: number;
|
|
51
87
|
error?: undefined;
|
|
52
88
|
statusText?: undefined;
|
|
89
|
+
category?: undefined;
|
|
53
90
|
errorPreview?: undefined;
|
|
54
91
|
}, import("@ai-sdk/provider-utils").Context>>;
|
|
55
92
|
finish: import("@ai-sdk/provider-utils").ExecutableTool<import("ai").Tool<{
|
|
@@ -88,3 +125,5 @@ export interface FishermanResult {
|
|
|
88
125
|
reason: string;
|
|
89
126
|
}>;
|
|
90
127
|
}
|
|
128
|
+
type ResponseCategory = 'validation' | 'authorization' | 'not_found' | 'conflict' | 'temporary' | 'server' | 'client';
|
|
129
|
+
export {};
|
|
@@ -23,6 +23,29 @@ export function createFishermanTools(apiClient, requestStore, opts) {
|
|
|
23
23
|
tag('step').log(`Fisherman: spec lookup ${method} ${path}`);
|
|
24
24
|
const captured = requestStore.findCapturedRequest(method, path);
|
|
25
25
|
if (captured) {
|
|
26
|
+
if (captured.status >= 400) {
|
|
27
|
+
const rejectedCapture = {
|
|
28
|
+
status: captured.status,
|
|
29
|
+
requestBody: captured.requestBody || 'no body',
|
|
30
|
+
};
|
|
31
|
+
if (opts.spec) {
|
|
32
|
+
try {
|
|
33
|
+
const definition = extractEndpointDefinition(opts.spec, path, opts.baseEndpoint);
|
|
34
|
+
return { source: 'spec', method, path, definition, rejectedCapture };
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { source: 'captured', method, path, usable: false, rejectedRequestBody: captured.requestBody || 'no body', status: captured.status };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
source: 'captured',
|
|
42
|
+
method: captured.method,
|
|
43
|
+
path: captured.path,
|
|
44
|
+
status: captured.status,
|
|
45
|
+
usable: false,
|
|
46
|
+
rejectedRequestBody: captured.requestBody || 'no body',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
26
49
|
return {
|
|
27
50
|
source: 'captured',
|
|
28
51
|
method: captured.method,
|
|
@@ -74,6 +97,7 @@ export function createFishermanTools(apiClient, requestStore, opts) {
|
|
|
74
97
|
success: false,
|
|
75
98
|
status: reqResult.status,
|
|
76
99
|
statusText: reqResult.statusText,
|
|
100
|
+
category: responseCategory(reqResult.status),
|
|
77
101
|
errorPreview: reqResult.rawResponseBody.substring(0, 300),
|
|
78
102
|
};
|
|
79
103
|
}
|
|
@@ -127,6 +151,21 @@ export function createFishermanTools(apiClient, requestStore, opts) {
|
|
|
127
151
|
};
|
|
128
152
|
return { tools, getResult, isFinished };
|
|
129
153
|
}
|
|
154
|
+
function responseCategory(status) {
|
|
155
|
+
if (status === 400 || status === 422)
|
|
156
|
+
return 'validation';
|
|
157
|
+
if (status === 401 || status === 403)
|
|
158
|
+
return 'authorization';
|
|
159
|
+
if (status === 404)
|
|
160
|
+
return 'not_found';
|
|
161
|
+
if (status === 409)
|
|
162
|
+
return 'conflict';
|
|
163
|
+
if (status === 408 || status === 425 || status === 429)
|
|
164
|
+
return 'temporary';
|
|
165
|
+
if (status >= 500)
|
|
166
|
+
return 'server';
|
|
167
|
+
return 'client';
|
|
168
|
+
}
|
|
130
169
|
function extractKeyFields(body, result = {}, depth = 0) {
|
|
131
170
|
if (!body || typeof body !== 'object' || depth > 5)
|
|
132
171
|
return result;
|
package/dist/src/ai/fisherman.js
CHANGED
|
@@ -170,7 +170,8 @@ export class Fisherman {
|
|
|
170
170
|
RULES:
|
|
171
171
|
- Always call getEndpointSpec before your first request to an unfamiliar endpoint
|
|
172
172
|
- Chain requests logically — create parent resources before children
|
|
173
|
-
-
|
|
173
|
+
- Use the response category and error text to decide what failed: validation requires corrected data, authorization requires valid access, not_found requires a valid path or parent, and conflict requires resolving the conflicting state
|
|
174
|
+
- Retry temporary or server failures once. Retry other failures only when the specification or error text gives a concrete correction
|
|
174
175
|
- Use realistic but unique data for each item (vary names, titles)
|
|
175
176
|
|
|
176
177
|
${dataProtectionRules}
|
|
@@ -15,6 +15,7 @@ declare class Navigator implements Agent {
|
|
|
15
15
|
experienceTracker: ExperienceTracker;
|
|
16
16
|
hooksRunner: HooksRunner;
|
|
17
17
|
MAX_ATTEMPTS: number;
|
|
18
|
+
lastFailureReason: string | null;
|
|
18
19
|
systemPrompt: string;
|
|
19
20
|
freeSailSystemPrompt: string;
|
|
20
21
|
explorer: Explorer;
|
|
@@ -25,18 +26,39 @@ declare class Navigator implements Agent {
|
|
|
25
26
|
get verifyTimeout(): number;
|
|
26
27
|
getBaseOrigin(): string | null;
|
|
27
28
|
getComparableCurrentUrl(stateManager: any, expectedUrl: string): string;
|
|
29
|
+
comparableUrl(state: {
|
|
30
|
+
url?: string;
|
|
31
|
+
fullUrl?: string;
|
|
32
|
+
}, expectedUrl: string): string;
|
|
28
33
|
isSameExpectedOrigin(expectedUrl: string, stateManager: any): boolean;
|
|
29
34
|
isOnExpectedPage(expectedUrl: string, stateManager: any): boolean;
|
|
30
35
|
visit(url: string): Promise<void>;
|
|
31
36
|
visitOnce(url: string): Promise<void>;
|
|
37
|
+
navigationError(url: string, fallback: string): Error;
|
|
38
|
+
failureReason(stopReason: string | null, knowledge: string, url: string): string | null;
|
|
32
39
|
resolveState(message: string, actionResult: ActionResult, opts?: {
|
|
33
40
|
action?: Action;
|
|
34
41
|
expectedUrl?: string;
|
|
42
|
+
experience?: string;
|
|
35
43
|
onAttempt?: (attempt: {
|
|
36
44
|
code: string;
|
|
37
45
|
error?: string;
|
|
38
46
|
}) => void;
|
|
39
47
|
}): Promise<boolean>;
|
|
48
|
+
buildResolutionPrompt(message: string, actionResult: ActionResult, injectedExperience?: string): Promise<string>;
|
|
49
|
+
buildRetryFeedback(failures: BatchFailure[], includeHtml: boolean, actionResult: ActionResult): Promise<string>;
|
|
50
|
+
executeAttempt(action: Action, codeBlock: string, message: string): Promise<{
|
|
51
|
+
ok: boolean;
|
|
52
|
+
error?: string;
|
|
53
|
+
}>;
|
|
54
|
+
verifyNavigation(action: Action, expectedUrl: string): Promise<{
|
|
55
|
+
freshState: ActionResult;
|
|
56
|
+
urlMatches: boolean;
|
|
57
|
+
}>;
|
|
58
|
+
ariaDiff(freshState: ActionResult, previous: ActionResult): Promise<string | null>;
|
|
59
|
+
saveFlow(message: string, expectedUrl: string | undefined, actionResult: ActionResult, progressBlocks: string[]): void;
|
|
60
|
+
rescueDelayedRedirect(action: Action, expectedUrl: string): Promise<boolean>;
|
|
61
|
+
askUserToResolve(action: Action, message: string, expectedUrl: string | undefined, stopReason: string | null): Promise<boolean>;
|
|
40
62
|
buildExperienceTools(): {
|
|
41
63
|
learnExperience: unknown;
|
|
42
64
|
} | undefined;
|
|
@@ -61,6 +83,12 @@ declare class Navigator implements Agent {
|
|
|
61
83
|
}>;
|
|
62
84
|
checkAlreadyVerified(aiResponse: string, actionResult: ActionResult): boolean;
|
|
63
85
|
}
|
|
86
|
+
type BatchFailure = {
|
|
87
|
+
code: string;
|
|
88
|
+
error: string;
|
|
89
|
+
ariaChanges?: string | null;
|
|
90
|
+
urlAfter?: string;
|
|
91
|
+
};
|
|
64
92
|
export type AssertionResult = {
|
|
65
93
|
code: string;
|
|
66
94
|
passed: boolean;
|