explorbot 0.4.0 → 0.4.2
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 +13 -6
- package/boat/api-tester/src/ai/chief.ts +7 -1
- package/boat/api-tester/src/ai/curler-tools.ts +1 -1
- package/boat/api-tester/src/ai/curler.ts +7 -1
- package/boat/api-tester/src/apibot.ts +10 -4
- package/boat/api-tester/src/cli.ts +15 -2
- package/boat/api-tester/src/config.ts +28 -8
- package/boat/doc-collector/bin/doc-collector-cli.ts +3 -2
- package/boat/doc-collector/src/ai/documentarian.ts +57 -5
- package/boat/doc-collector/src/ai/tools.ts +1 -1
- package/boat/doc-collector/src/cli.ts +21 -3
- package/boat/doc-collector/src/config.ts +3 -0
- package/boat/doc-collector/src/docbot.ts +57 -12
- package/boat/doc-collector/src/docs-renderer.ts +79 -55
- package/boat/doc-collector/src/interaction-screenshots.ts +19 -10
- package/boat/doc-collector/src/screenshots.ts +106 -3
- package/boat/doc-collector/src/template-dedup.ts +31 -0
- package/boat/prima/bin/prima-cli.ts +2 -0
- package/boat/prima/src/cli.ts +5 -0
- package/boat/prima/src/prima.ts +3 -4
- package/dist/bin/explorbot-cli.js +14 -6
- package/dist/boat/api-tester/bin/apibot-cli.js +3 -2
- package/dist/boat/api-tester/src/ai/chief.js +6 -1
- package/dist/boat/api-tester/src/ai/curler-tools.js +1 -1
- package/dist/boat/api-tester/src/ai/curler.js +6 -1
- package/dist/boat/api-tester/src/apibot.js +7 -3
- package/dist/boat/api-tester/src/cli.js +14 -2
- package/dist/boat/api-tester/src/config.js +31 -8
- package/dist/boat/doc-collector/bin/doc-collector-cli.js +3 -2
- package/dist/boat/doc-collector/src/ai/documentarian.js +62 -4
- package/dist/boat/doc-collector/src/ai/tools.js +1 -1
- package/dist/boat/doc-collector/src/cli.js +18 -3
- package/dist/boat/doc-collector/src/config.js +1 -0
- package/dist/boat/doc-collector/src/docbot.js +53 -11
- package/dist/boat/doc-collector/src/docs-renderer.js +69 -46
- package/dist/boat/doc-collector/src/interaction-screenshots.js +18 -11
- package/dist/boat/doc-collector/src/screenshots.js +104 -1
- package/dist/boat/doc-collector/src/template-dedup.js +27 -0
- package/dist/boat/prima/bin/prima-cli.js +2 -0
- package/dist/boat/prima/src/cli.js +5 -0
- package/dist/boat/prima/src/prima.js +3 -4
- package/dist/package.json +1 -1
- package/dist/rules/planner/styles/normal.md +1 -1
- package/dist/src/action-result.d.ts +2 -2
- package/dist/src/action-result.js +15 -17
- package/dist/src/action.d.ts +1 -1
- package/dist/src/action.js +15 -15
- package/dist/src/ai/captain.js +1 -1
- package/dist/src/ai/fisherman/request-haul.d.ts +11 -0
- package/dist/src/ai/fisherman/request-haul.js +28 -0
- package/dist/src/ai/fisherman-tools.d.ts +10 -1
- package/dist/src/ai/fisherman-tools.js +35 -31
- package/dist/src/ai/fisherman.d.ts +3 -2
- package/dist/src/ai/fisherman.js +8 -6
- package/dist/src/ai/navigator.js +1 -1
- package/dist/src/ai/pilot.js +21 -16
- package/dist/src/ai/planner.js +9 -7
- package/dist/src/ai/provider.js +8 -14
- package/dist/src/ai/researcher/coordinates.d.ts +2 -0
- package/dist/src/ai/researcher/coordinates.js +13 -1
- package/dist/src/ai/researcher/deep-analysis.js +9 -9
- package/dist/src/ai/researcher/fingerprint-worker.js +1 -11
- package/dist/src/ai/researcher.js +3 -1
- package/dist/src/ai/rules.js +11 -3
- package/dist/src/ai/tester.js +20 -20
- package/dist/src/ai/tools.d.ts +1 -1
- package/dist/src/ai/tools.js +18 -74
- package/dist/src/api/request-store.d.ts +1 -5
- package/dist/src/api/request-store.js +12 -22
- package/dist/src/api/spec-reader.js +1 -1
- package/dist/src/commands/config-command.js +3 -10
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/explore-command.js +12 -1
- package/dist/src/commands/index.js +2 -0
- package/dist/src/commands/init-command.js +3 -7
- package/dist/src/commands/options/base-option.d.ts +8 -0
- package/dist/src/commands/options/base-option.js +12 -0
- package/dist/src/commands/options/index.d.ts +5 -0
- package/dist/src/commands/options/index.js +5 -0
- package/dist/src/commands/options/knowledge-option.d.ts +7 -0
- package/dist/src/commands/options/knowledge-option.js +12 -0
- package/dist/src/commands/options/ws-option.d.ts +7 -0
- package/dist/src/commands/options/ws-option.js +21 -0
- package/dist/src/commands/plans-command.d.ts +4 -1
- package/dist/src/commands/plans-command.js +4 -4
- package/dist/src/commands/recommended-models-command.d.ts +9 -0
- package/dist/src/commands/recommended-models-command.js +85 -0
- package/dist/src/config.d.ts +3 -0
- package/dist/src/config.js +18 -2
- package/dist/src/experience-tracker.js +1 -1
- package/dist/src/explorbot.js +4 -4
- package/dist/src/global-config.d.ts +2 -0
- package/dist/src/global-config.js +6 -0
- package/dist/src/knowledge-tracker.d.ts +20 -7
- package/dist/src/knowledge-tracker.js +69 -31
- package/dist/src/remote.d.ts +0 -3
- package/dist/src/remote.js +0 -18
- package/dist/src/state-manager.d.ts +3 -3
- package/dist/src/state-manager.js +8 -8
- package/dist/src/test-plan.d.ts +1 -0
- package/dist/src/test-plan.js +13 -9
- package/dist/src/utils/aria.d.ts +1 -1
- package/dist/src/utils/aria.js +23 -2
- package/dist/src/utils/cli-section.d.ts +1 -0
- package/dist/src/utils/cli-section.js +12 -0
- package/dist/src/utils/html-diff.d.ts +1 -0
- package/dist/src/utils/html-diff.js +5 -5
- package/dist/src/utils/html.d.ts +2 -0
- package/dist/src/utils/html.js +5 -4
- package/dist/src/utils/overlay.d.ts +19 -55
- package/dist/src/utils/overlay.js +94 -148
- package/dist/src/utils/region.d.ts +41 -0
- package/dist/src/utils/region.js +99 -0
- package/dist/src/utils/similarity.d.ts +1 -0
- package/dist/src/utils/similarity.js +11 -0
- package/dist/src/utils/url-matcher.d.ts +1 -1
- package/dist/src/utils/url-matcher.js +4 -4
- package/docs/api-testing/basics.md +15 -0
- package/docs/api-testing/planning.md +10 -1
- package/docs/doc-collection/basics.md +11 -5
- package/docs/doc-collection/crawling.md +21 -1
- package/docs/reference/commands.md +27 -4
- package/docs/superpowers/plans/2026-09-03-model-resolves-click-ambiguity.md +509 -0
- package/docs/workflow/agentic-usage.md +11 -2
- package/docs/workflow/application-spec.md +4 -0
- package/docs/workflow/knowledge.md +46 -2
- package/package.json +1 -1
- package/rules/planner/styles/normal.md +1 -1
- package/src/action-result.ts +15 -16
- package/src/action.ts +16 -15
- package/src/ai/captain.ts +1 -1
- package/src/ai/fisherman/request-haul.ts +32 -0
- package/src/ai/fisherman-tools.ts +38 -32
- package/src/ai/fisherman.ts +8 -7
- package/src/ai/navigator.ts +1 -1
- package/src/ai/pilot.ts +19 -14
- package/src/ai/planner.ts +9 -8
- package/src/ai/provider.ts +8 -12
- package/src/ai/researcher/coordinates.ts +13 -1
- package/src/ai/researcher/deep-analysis.ts +9 -9
- package/src/ai/researcher/fingerprint-worker.ts +1 -10
- package/src/ai/researcher.ts +2 -1
- package/src/ai/rules.ts +11 -3
- package/src/ai/tester.ts +18 -18
- package/src/ai/tools.ts +18 -86
- package/src/api/request-store.ts +13 -27
- package/src/api/spec-reader.ts +1 -1
- package/src/commands/config-command.ts +3 -8
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/explore-command.ts +12 -1
- package/src/commands/index.ts +2 -0
- package/src/commands/init-command.ts +4 -11
- package/src/commands/options/base-option.ts +18 -0
- package/src/commands/options/index.ts +7 -0
- package/src/commands/options/knowledge-option.ts +14 -0
- package/src/commands/options/ws-option.ts +24 -0
- package/src/commands/plans-command.ts +5 -5
- package/src/commands/recommended-models-command.ts +92 -0
- package/src/config.ts +20 -2
- package/src/experience-tracker.ts +1 -1
- package/src/explorbot.ts +4 -4
- package/src/global-config.ts +8 -0
- package/src/knowledge-tracker.ts +94 -36
- package/src/remote.ts +0 -20
- package/src/state-manager.ts +10 -9
- package/src/test-plan.ts +12 -10
- package/src/utils/aria.ts +26 -3
- package/src/utils/cli-section.ts +12 -0
- package/src/utils/html-diff.ts +6 -6
- package/src/utils/html.ts +6 -4
- package/src/utils/overlay.ts +105 -192
- package/src/utils/region.ts +132 -0
- package/src/utils/similarity.ts +9 -0
- package/src/utils/url-matcher.ts +3 -3
- package/dist/src/utils/request-map.d.ts +0 -7
- package/dist/src/utils/request-map.js +0 -16
- package/src/utils/request-map.ts +0 -19
|
@@ -6,6 +6,7 @@ import { htmlDiff, liveRegionMessages } from "./utils/html-diff.js";
|
|
|
6
6
|
import { extractHeadings, extractLinks, extractTargetedHtml, htmlCombinedSnapshot, htmlMinimalUISnapshot, htmlTextSnapshot, minifyHtml } from "./utils/html.js";
|
|
7
7
|
import { createDebug } from "./utils/logger.js";
|
|
8
8
|
import { Overlay } from "./utils/overlay.js";
|
|
9
|
+
import { Region } from "./utils/region.js";
|
|
9
10
|
import { slugify } from "./utils/strings.js";
|
|
10
11
|
import { extractStatePath, matchesUrl } from "./utils/url-matcher.js";
|
|
11
12
|
const debugLog = createDebug('explorbot:state');
|
|
@@ -38,7 +39,7 @@ export class ActionResult {
|
|
|
38
39
|
notes = [];
|
|
39
40
|
links = [];
|
|
40
41
|
verifications;
|
|
41
|
-
overlay = new
|
|
42
|
+
overlay = new Region();
|
|
42
43
|
_diffCache = null;
|
|
43
44
|
constructor(data) {
|
|
44
45
|
this.id = data.id;
|
|
@@ -198,7 +199,7 @@ export class ActionResult {
|
|
|
198
199
|
if (record.region && this.overlay.name !== record.region)
|
|
199
200
|
return false;
|
|
200
201
|
if (record.root) {
|
|
201
|
-
if (!this.overlay.
|
|
202
|
+
if (!this.overlay.isOpen)
|
|
202
203
|
return false;
|
|
203
204
|
if (this.overlay.root && this.overlay.root !== record.root)
|
|
204
205
|
return false;
|
|
@@ -446,15 +447,12 @@ export class ActionResult {
|
|
|
446
447
|
pageDiff.ariaChanges = diff.ariaChanged;
|
|
447
448
|
pageDiff.ariaChangeCount = diff.ariaChangeCount;
|
|
448
449
|
}
|
|
449
|
-
if (this.overlay.
|
|
450
|
+
if (this.overlay.isOpen && (!previousState.overlay.isOpen || previousState.overlay.name !== this.overlay.name)) {
|
|
450
451
|
pageDiff.areaOfInterest = this.overlay.describe();
|
|
451
452
|
}
|
|
452
453
|
if (pageDiff.areaOfInterest && this.overlay.html && this.overlay.root) {
|
|
453
454
|
const htmlConfig = ConfigParser.getInstance().getConfig().html;
|
|
454
|
-
|
|
455
|
-
if (subtree.length > HTML_PART_SUBTREE_BUDGET) {
|
|
456
|
-
subtree = `${subtree.slice(0, HTML_PART_SUBTREE_BUDGET)}...<!-- truncated -->`;
|
|
457
|
-
}
|
|
455
|
+
const subtree = trimSubtree(await minifyHtml(htmlCombinedSnapshot(this.overlay.html, htmlConfig?.combined)));
|
|
458
456
|
pageDiff.htmlParts = [{ container: this.overlay.root, subtree, rawSize: subtree.length, added: [], removed: [] }];
|
|
459
457
|
}
|
|
460
458
|
else if (diff.isSameUrl() && diff.htmlParts.length > 0) {
|
|
@@ -478,7 +476,7 @@ export class ActionResult {
|
|
|
478
476
|
parts.push(`h1_${this.h1}`);
|
|
479
477
|
if (this.h2)
|
|
480
478
|
parts.push(`h2_${this.h2}`);
|
|
481
|
-
if (includeRegion && this.overlay.
|
|
479
|
+
if (includeRegion && this.overlay.isOpen && this.overlay.name)
|
|
482
480
|
parts.push(`region_${this.overlay.name}`);
|
|
483
481
|
let stateString = slugify(parts.map((part) => part.substring(0, 100)).join('_'));
|
|
484
482
|
if (stateString.length > 200) {
|
|
@@ -511,6 +509,7 @@ const CONSOLE_ERROR_LIMIT = 3;
|
|
|
511
509
|
const HTML_PARTS_TOTAL_BUDGET = 8000;
|
|
512
510
|
const HTML_PARTS_COUNT_LIMIT = 8;
|
|
513
511
|
const HTML_PART_SUBTREE_BUDGET = 2000;
|
|
512
|
+
const SUBTREE_TAIL_BUDGET = 600;
|
|
514
513
|
function collapseHtmlParts(parts) {
|
|
515
514
|
const total = parts.reduce((sum, p) => sum + p.subtree.length, 0);
|
|
516
515
|
const fullPageReRender = total > HTML_PARTS_TOTAL_BUDGET || parts.length > HTML_PARTS_COUNT_LIMIT;
|
|
@@ -522,15 +521,14 @@ function collapseHtmlParts(parts) {
|
|
|
522
521
|
subtree: `<html><head></head><body>...collapsed (${part.subtree.length} chars, ${part.added.length} added, ${part.removed.length} removed)...</body></html>`,
|
|
523
522
|
}));
|
|
524
523
|
}
|
|
525
|
-
return parts.map((part) => {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
});
|
|
524
|
+
return parts.map((part) => ({ ...part, subtree: trimSubtree(part.subtree) }));
|
|
525
|
+
}
|
|
526
|
+
function trimSubtree(subtree) {
|
|
527
|
+
if (subtree.length <= HTML_PART_SUBTREE_BUDGET)
|
|
528
|
+
return subtree;
|
|
529
|
+
const head = subtree.slice(0, HTML_PART_SUBTREE_BUDGET - SUBTREE_TAIL_BUDGET);
|
|
530
|
+
const tail = subtree.slice(-SUBTREE_TAIL_BUDGET);
|
|
531
|
+
return `${head}...<!-- truncated ${subtree.length - HTML_PART_SUBTREE_BUDGET} chars -->${tail}`;
|
|
534
532
|
}
|
|
535
533
|
export class Diff {
|
|
536
534
|
current;
|
package/dist/src/action.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ declare class Action {
|
|
|
33
33
|
includeScreenshot?: boolean;
|
|
34
34
|
codeBlock?: string;
|
|
35
35
|
}): Promise<ActionResult>;
|
|
36
|
-
|
|
36
|
+
detectRegion(result: ActionResult): Promise<void>;
|
|
37
37
|
captureMainDocumentStatus(): Promise<number | undefined>;
|
|
38
38
|
captureResponses(): () => void;
|
|
39
39
|
recordNetworkCall(request: any, status: number): void;
|
package/dist/src/action.js
CHANGED
|
@@ -166,7 +166,7 @@ class Action {
|
|
|
166
166
|
iframeURL: frame ? frame.url?.() || 'iframe' : undefined,
|
|
167
167
|
});
|
|
168
168
|
if (!frame)
|
|
169
|
-
await this.
|
|
169
|
+
await this.detectRegion(result).catch((err) => debugLog('Region detection failed:', err.message));
|
|
170
170
|
this.stateManager.updateState(result, codeBlock);
|
|
171
171
|
return result;
|
|
172
172
|
}
|
|
@@ -179,36 +179,36 @@ class Action {
|
|
|
179
179
|
return new ActionResult({ url, error: msg });
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
async
|
|
182
|
+
async detectRegion(result) {
|
|
183
183
|
const previousState = this.stateManager.getCurrentState();
|
|
184
184
|
if (!previousState)
|
|
185
185
|
return;
|
|
186
186
|
const previous = ActionResult.fromState(previousState);
|
|
187
|
-
const
|
|
187
|
+
const previousRegion = previous.overlay;
|
|
188
188
|
const sameUrl = !!previous.url && result.isSameUrl({ url: previous.url });
|
|
189
189
|
const overlayPage = new OverlayPage(this.playwrightHelper.page);
|
|
190
|
-
if (result.overlay.
|
|
191
|
-
result.overlay =
|
|
190
|
+
if (result.overlay.isModal && previousRegion.isModal && previousRegion.root && previousRegion.type === result.overlay.type && previousRegion.name === result.overlay.name) {
|
|
191
|
+
result.overlay = previousRegion;
|
|
192
192
|
return;
|
|
193
193
|
}
|
|
194
194
|
if (!previous.html)
|
|
195
195
|
return;
|
|
196
196
|
if (previous.html === result.html) {
|
|
197
|
-
if (sameUrl &&
|
|
198
|
-
result.overlay =
|
|
197
|
+
if (sameUrl && previousRegion.isOpen && previousRegion.xpath && !result.overlay.isModal)
|
|
198
|
+
result.overlay = previousRegion;
|
|
199
199
|
return;
|
|
200
200
|
}
|
|
201
201
|
let carried = null;
|
|
202
|
-
if (sameUrl &&
|
|
203
|
-
if (await overlayPage.isStillOpen(
|
|
204
|
-
carried =
|
|
202
|
+
if (sameUrl && previousRegion.isOpen && previousRegion.xpath) {
|
|
203
|
+
if (await overlayPage.isStillOpen(previousRegion)) {
|
|
204
|
+
carried = previousRegion;
|
|
205
205
|
}
|
|
206
206
|
else {
|
|
207
|
-
debugLog(`Region closed: ${
|
|
208
|
-
if (!result.overlay.
|
|
209
|
-
const parent =
|
|
207
|
+
debugLog(`Region closed: ${previousRegion.name || previousRegion.type}`);
|
|
208
|
+
if (!result.overlay.isModal) {
|
|
209
|
+
const parent = previousRegion.parent;
|
|
210
210
|
if (parent?.xpath) {
|
|
211
|
-
const restored =
|
|
211
|
+
const restored = Overlay.resolve({ overlay: parent });
|
|
212
212
|
if (await overlayPage.isStillOpen(restored))
|
|
213
213
|
result.overlay = restored;
|
|
214
214
|
}
|
|
@@ -224,7 +224,7 @@ class Action {
|
|
|
224
224
|
sameUrl,
|
|
225
225
|
previousHtml: previous.html,
|
|
226
226
|
});
|
|
227
|
-
if (result.overlay
|
|
227
|
+
if (result.overlay instanceof Overlay) {
|
|
228
228
|
if (detected)
|
|
229
229
|
result.overlay = result.overlay.withGeometry(detected);
|
|
230
230
|
return;
|
package/dist/src/ai/captain.js
CHANGED
|
@@ -137,7 +137,7 @@ export class Captain extends CaptainBase {
|
|
|
137
137
|
const headingLines = formatHeadings(state);
|
|
138
138
|
const headingsBlock = headingLines.join('\n');
|
|
139
139
|
let pageSummary = '';
|
|
140
|
-
const cachedResearch = Researcher.getCachedResearch(
|
|
140
|
+
const cachedResearch = Researcher.getCachedResearch(actionResult);
|
|
141
141
|
if (cachedResearch) {
|
|
142
142
|
pageSummary = `<page_summary>\n${this.explorBot.agentResearcher().extractBrief(cachedResearch)}\n</page_summary>`;
|
|
143
143
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RequestResult } from '../../api/request-result.js';
|
|
2
|
+
import { type RequestStore } from '../../api/request-store.js';
|
|
3
|
+
export declare class RequestHaul {
|
|
4
|
+
store: RequestStore;
|
|
5
|
+
start: number;
|
|
6
|
+
constructor(store: RequestStore);
|
|
7
|
+
requests(): RequestResult[];
|
|
8
|
+
failed(): RequestResult[];
|
|
9
|
+
successfulWrites(): RequestResult[];
|
|
10
|
+
byId(): Map<string, RequestResult>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isFailedRequest } from "../../api/request-store.js";
|
|
2
|
+
export class RequestHaul {
|
|
3
|
+
store;
|
|
4
|
+
start;
|
|
5
|
+
constructor(store) {
|
|
6
|
+
this.store = store;
|
|
7
|
+
this.start = store.getMadeRequests().length;
|
|
8
|
+
}
|
|
9
|
+
requests() {
|
|
10
|
+
return this.store.getMadeRequests().slice(this.start);
|
|
11
|
+
}
|
|
12
|
+
failed() {
|
|
13
|
+
return this.requests().filter(isFailedRequest);
|
|
14
|
+
}
|
|
15
|
+
successfulWrites() {
|
|
16
|
+
return this.requests().filter((r) => r.isWrite && !r.error && r.status >= 200 && r.status < 400);
|
|
17
|
+
}
|
|
18
|
+
byId() {
|
|
19
|
+
const map = new Map();
|
|
20
|
+
for (const request of this.successfulWrites()) {
|
|
21
|
+
const { id } = request.extractIdAndTitle();
|
|
22
|
+
if (id === undefined)
|
|
23
|
+
continue;
|
|
24
|
+
map.set(String(id), request);
|
|
25
|
+
}
|
|
26
|
+
return map;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ApiClient } from '../api/api-client.js';
|
|
2
2
|
import type { RequestStore } from '../api/request-store.js';
|
|
3
|
-
|
|
3
|
+
import type { RequestHaul } from './fisherman/request-haul.js';
|
|
4
|
+
export declare function createFishermanTools(apiClient: ApiClient, requestStore: RequestStore, haul: RequestHaul, opts: {
|
|
4
5
|
spec?: any;
|
|
5
6
|
baseEndpoint?: string;
|
|
6
7
|
}): {
|
|
@@ -120,6 +121,14 @@ export declare function createFishermanTools(apiClient: ApiClient, requestStore:
|
|
|
120
121
|
isFinished: () => boolean;
|
|
121
122
|
finishFromText: (text?: string) => void;
|
|
122
123
|
};
|
|
124
|
+
export declare function verifyFinish(haul: RequestHaul, input: {
|
|
125
|
+
summary: string;
|
|
126
|
+
created: FishermanResult['created'];
|
|
127
|
+
failed?: FishermanResult['failed'];
|
|
128
|
+
}): {
|
|
129
|
+
result: FishermanResult | null;
|
|
130
|
+
error?: string;
|
|
131
|
+
};
|
|
123
132
|
export interface FishermanResult {
|
|
124
133
|
success: boolean;
|
|
125
134
|
summary: string;
|
|
@@ -3,19 +3,15 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { extractEndpointDefinition } from "../api/spec-reader.js";
|
|
5
5
|
import { tag } from "../utils/logger.js";
|
|
6
|
-
import { RequestMap } from "../utils/request-map.js";
|
|
7
6
|
import { isDynamicSegment } from "../utils/url-matcher.js";
|
|
8
|
-
export function createFishermanTools(apiClient, requestStore, opts) {
|
|
7
|
+
export function createFishermanTools(apiClient, requestStore, haul, opts) {
|
|
9
8
|
let finished = false;
|
|
10
9
|
let result = null;
|
|
11
|
-
const
|
|
12
|
-
const runRequests = () => requestStore.getMadeRequests().slice(ledgerStart);
|
|
13
|
-
const successfulWrites = () => runRequests().filter((r) => r.isWrite && !r.error && r.status >= 200 && r.status < 400);
|
|
14
|
-
const getResult = () => result ?? synthesizeResult(runRequests(), successfulWrites(), false);
|
|
10
|
+
const getResult = () => result ?? synthesizeResult(haul, false);
|
|
15
11
|
const isFinished = () => finished;
|
|
16
12
|
const finishFromText = (text) => {
|
|
17
13
|
finished = true;
|
|
18
|
-
const synthesized = synthesizeResult(
|
|
14
|
+
const synthesized = synthesizeResult(haul, true);
|
|
19
15
|
if (text && synthesized.success)
|
|
20
16
|
synthesized.summary = text;
|
|
21
17
|
result = synthesized;
|
|
@@ -142,30 +138,12 @@ export function createFishermanTools(apiClient, requestStore, opts) {
|
|
|
142
138
|
.describe('List of items that could not be created'),
|
|
143
139
|
}),
|
|
144
140
|
execute: async ({ summary, created, failed }) => {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
return { finished: false, error: 'No successful write request was made in this run, so nothing was created. Keep working, or call stop if the data cannot be prepared.' };
|
|
149
|
-
}
|
|
150
|
-
const createdRequests = new RequestMap(writes);
|
|
151
|
-
const verified = [];
|
|
152
|
-
for (const item of created) {
|
|
153
|
-
if (item.id === undefined) {
|
|
154
|
-
verified.push(item);
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
const request = createdRequests.get(item.id);
|
|
158
|
-
if (!request) {
|
|
159
|
-
tag('warning').log(`Fisherman: dropped unverified created item ${item.type} (id: ${item.id})`);
|
|
160
|
-
continue;
|
|
161
|
-
}
|
|
162
|
-
verified.push({ ...item, request: request.toEndpoint() });
|
|
163
|
-
}
|
|
164
|
-
if (verified.length === 0)
|
|
165
|
-
verified.push(...writes.map(toCreatedItem));
|
|
141
|
+
const { result: verified, error } = verifyFinish(haul, { summary, created, failed });
|
|
142
|
+
if (!verified)
|
|
143
|
+
return { finished: false, error };
|
|
166
144
|
tag('success').log(`Fisherman done: ${summary}`);
|
|
167
145
|
finished = true;
|
|
168
|
-
result =
|
|
146
|
+
result = verified;
|
|
169
147
|
return { finished: true };
|
|
170
148
|
},
|
|
171
149
|
}),
|
|
@@ -184,8 +162,34 @@ export function createFishermanTools(apiClient, requestStore, opts) {
|
|
|
184
162
|
};
|
|
185
163
|
return { tools, getResult, isFinished, finishFromText };
|
|
186
164
|
}
|
|
187
|
-
function
|
|
188
|
-
const
|
|
165
|
+
export function verifyFinish(haul, input) {
|
|
166
|
+
const writes = haul.successfulWrites();
|
|
167
|
+
if (writes.length === 0) {
|
|
168
|
+
tag('warning').log('Fisherman: finish rejected — no successful write request in this run');
|
|
169
|
+
return { result: null, error: 'No successful write request was made in this run, so nothing was created. Keep working, or call stop if the data cannot be prepared.' };
|
|
170
|
+
}
|
|
171
|
+
const createdRequests = haul.byId();
|
|
172
|
+
const verified = [];
|
|
173
|
+
for (const item of input.created) {
|
|
174
|
+
if (item.id === undefined) {
|
|
175
|
+
verified.push(item);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const request = createdRequests.get(String(item.id));
|
|
179
|
+
if (!request) {
|
|
180
|
+
tag('warning').log(`Fisherman: dropped unverified created item ${item.type} (id: ${item.id})`);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
verified.push({ ...item, request: request.toEndpoint() });
|
|
184
|
+
}
|
|
185
|
+
if (verified.length === 0)
|
|
186
|
+
verified.push(...writes.map(toCreatedItem));
|
|
187
|
+
return { result: { success: true, summary: input.summary, created: verified, failed: input.failed || [] } };
|
|
188
|
+
}
|
|
189
|
+
function synthesizeResult(haul, declaredDone) {
|
|
190
|
+
const made = haul.requests();
|
|
191
|
+
const writes = haul.successfulWrites();
|
|
192
|
+
const failures = haul.failed();
|
|
189
193
|
let summary = `Stopped before finishing: ${made.length} requests, ${writes.length} successful writes, ${failures.length} failed`;
|
|
190
194
|
const lastFailure = failures[failures.length - 1];
|
|
191
195
|
if (lastFailure)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ApiClient } from '../api/api-client.js';
|
|
2
|
-
import type
|
|
2
|
+
import { type RequestStore } from '../api/request-store.js';
|
|
3
3
|
import type { Agent } from './agent.js';
|
|
4
4
|
import { type FishermanResult } from './fisherman-tools.js';
|
|
5
|
+
import { RequestHaul } from './fisherman/request-haul.js';
|
|
5
6
|
import type { Provider } from './provider.js';
|
|
6
7
|
export declare class Fisherman implements Agent {
|
|
7
8
|
emoji: string;
|
|
@@ -26,6 +27,6 @@ export declare class Fisherman implements Agent {
|
|
|
26
27
|
refreshAuth(): Promise<void>;
|
|
27
28
|
buildEndpointList(scopeUrl?: string): string;
|
|
28
29
|
buildSystemPrompt(endpointList: string, toolNames: string[], scopeUrl?: string): string;
|
|
29
|
-
isStuckOnEndpoint(
|
|
30
|
+
isStuckOnEndpoint(haul: RequestHaul): boolean;
|
|
30
31
|
buildTaskPrompt(instructions: string): string;
|
|
31
32
|
}
|
package/dist/src/ai/fisherman.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import dedent from 'dedent';
|
|
2
|
+
import { isFailedRequest } from "../api/request-store.js";
|
|
2
3
|
import { listAllEndpoints } from "../api/spec-reader.js";
|
|
3
4
|
import { createDebug, tag } from "../utils/logger.js";
|
|
4
5
|
const debugLog = createDebug('explorbot:fisherman');
|
|
5
6
|
import { loop } from "../utils/loop.js";
|
|
6
7
|
import { createFishermanTools } from "./fisherman-tools.js";
|
|
8
|
+
import { RequestHaul } from "./fisherman/request-haul.js";
|
|
7
9
|
import { dataProtectionRules } from "./rules.js";
|
|
8
10
|
const MAX_ITERATIONS = 15;
|
|
9
11
|
const MAX_TOOL_ROUNDTRIPS = 5;
|
|
@@ -61,11 +63,11 @@ export class Fisherman {
|
|
|
61
63
|
}
|
|
62
64
|
await this.refreshAuth();
|
|
63
65
|
debugLog(`auth headers: ${Object.keys(this.apiClient.getHeaders()).join(', ')}`);
|
|
64
|
-
const
|
|
66
|
+
const haul = new RequestHaul(this.requestStore);
|
|
67
|
+
const { tools, getResult, isFinished, finishFromText } = createFishermanTools(this.apiClient, this.requestStore, haul, {
|
|
65
68
|
spec: this.spec,
|
|
66
69
|
baseEndpoint: this.baseEndpoint,
|
|
67
70
|
});
|
|
68
|
-
const ledgerStart = this.requestStore.getMadeRequests().length;
|
|
69
71
|
const conversation = this.provider.startConversation(this.buildSystemPrompt(endpointList, Object.keys(tools), scopeUrl), 'fisherman');
|
|
70
72
|
conversation.addUserText(this.buildTaskPrompt(instructions));
|
|
71
73
|
await loop(async ({ stop, iteration }) => {
|
|
@@ -85,7 +87,7 @@ export class Fisherman {
|
|
|
85
87
|
stop();
|
|
86
88
|
return;
|
|
87
89
|
}
|
|
88
|
-
if (this.isStuckOnEndpoint(
|
|
90
|
+
if (this.isStuckOnEndpoint(haul)) {
|
|
89
91
|
tag('warning').log('Fisherman: repeated failures on the same endpoint — stopping');
|
|
90
92
|
stop();
|
|
91
93
|
return;
|
|
@@ -190,13 +192,13 @@ export class Fisherman {
|
|
|
190
192
|
${dataProtectionRules}
|
|
191
193
|
`;
|
|
192
194
|
}
|
|
193
|
-
isStuckOnEndpoint(
|
|
194
|
-
const made =
|
|
195
|
+
isStuckOnEndpoint(haul) {
|
|
196
|
+
const made = haul.requests();
|
|
195
197
|
if (made.length < REPEATED_FAILURE_LIMIT)
|
|
196
198
|
return false;
|
|
197
199
|
const recent = made.slice(-REPEATED_FAILURE_LIMIT);
|
|
198
200
|
const first = recent[0];
|
|
199
|
-
return recent.every((r) => (r
|
|
201
|
+
return recent.every((r) => isFailedRequest(r) && r.method === first.method && r.path === first.path);
|
|
200
202
|
}
|
|
201
203
|
buildTaskPrompt(instructions) {
|
|
202
204
|
return dedent `
|
package/dist/src/ai/navigator.js
CHANGED
|
@@ -522,7 +522,7 @@ class Navigator {
|
|
|
522
522
|
return null;
|
|
523
523
|
}
|
|
524
524
|
const currentActionResult = actionResult || ActionResult.fromState(state);
|
|
525
|
-
const research = Researcher.getCachedResearch(
|
|
525
|
+
const research = Researcher.getCachedResearch(currentActionResult) || '';
|
|
526
526
|
const combinedHtml = await currentActionResult.combinedHtml();
|
|
527
527
|
const history = stateManager.getStateHistory();
|
|
528
528
|
const visitCounts = new Map();
|
package/dist/src/ai/pilot.js
CHANGED
|
@@ -19,6 +19,7 @@ const META_TOOLS = ['record', 'reset', 'stop', 'finish'];
|
|
|
19
19
|
const PILOT_REASONING_LIMIT = 500;
|
|
20
20
|
const PILOT_MESSAGE_LIMIT = 2;
|
|
21
21
|
const PILOT_MESSAGE_MAX_LENGTH = 160;
|
|
22
|
+
const PILOT_REQUEST_LIMIT = 5;
|
|
22
23
|
export class Pilot {
|
|
23
24
|
emoji = '🧭';
|
|
24
25
|
provider;
|
|
@@ -741,21 +742,21 @@ export class Pilot {
|
|
|
741
742
|
lines.push(`h2: ${state.h2 || ''}`);
|
|
742
743
|
lines.push(`h3: ${state.h3 || ''}`);
|
|
743
744
|
lines.push(`h4: ${state.h4 || ''}`);
|
|
744
|
-
const
|
|
745
|
-
if (
|
|
746
|
-
let line = `
|
|
747
|
-
if (
|
|
748
|
-
line += ` (root: ${
|
|
745
|
+
const region = state.overlay;
|
|
746
|
+
if (region.isModal) {
|
|
747
|
+
let line = `overlay: ${region.name || region.type}`;
|
|
748
|
+
if (region.root)
|
|
749
|
+
line += ` (root: ${region.root})`;
|
|
749
750
|
lines.push(line);
|
|
750
751
|
}
|
|
751
|
-
else if (
|
|
752
|
-
let line = `region: ${
|
|
753
|
-
if (
|
|
754
|
-
line += `, root: ${
|
|
752
|
+
else if (region.isOpen) {
|
|
753
|
+
let line = `region: ${region.name || 'unnamed'} (inline`;
|
|
754
|
+
if (region.root)
|
|
755
|
+
line += `, root: ${region.root}`;
|
|
755
756
|
lines.push(`${line})`);
|
|
756
757
|
}
|
|
757
758
|
else {
|
|
758
|
-
lines.push('
|
|
759
|
+
lines.push('overlay: none');
|
|
759
760
|
}
|
|
760
761
|
const tabs = this.stateManager.otherTabs;
|
|
761
762
|
if (tabs.length > 0) {
|
|
@@ -971,9 +972,13 @@ export class Pilot {
|
|
|
971
972
|
line += `\n ${ariaDiff}`;
|
|
972
973
|
if (t.output?.pageDiff?.urlChanged)
|
|
973
974
|
line += `\n moved: ${t.output.pageDiff.previousUrl} → ${t.output.pageDiff.currentUrl}`;
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
975
|
+
const pageRequests = t.output?.pageDiff?.requests ?? [];
|
|
976
|
+
const requests = pageRequests
|
|
977
|
+
.filter((r) => r.status >= 400)
|
|
978
|
+
.concat(pageRequests.filter((r) => r.status < 400))
|
|
979
|
+
.slice(0, PILOT_REQUEST_LIMIT);
|
|
980
|
+
if (requests.length > 0) {
|
|
981
|
+
line += `\n requests: ${requests.map((r) => `${r.method} ${r.path} → ${r.status}`).join(', ')}`;
|
|
977
982
|
}
|
|
978
983
|
const messages = (t.output?.pageDiff?.messages ?? []).slice(0, PILOT_MESSAGE_LIMIT);
|
|
979
984
|
if (messages.length > 0) {
|
|
@@ -1034,15 +1039,15 @@ export class Pilot {
|
|
|
1034
1039
|
|
|
1035
1040
|
Diagnostic patterns (use <state>, executed/element/skipped fields, ariaDiff):
|
|
1036
1041
|
- Click failed + button in "disabled buttons" → required field missing. Instruct fill first.
|
|
1037
|
-
- "
|
|
1042
|
+
- "overlay: none" but Tester targets an overlay → overlay closed; re-trigger.
|
|
1038
1043
|
- "region:" in <state> → a large area appeared in place without navigation (subview, wizard step, panel). Direct Tester to act inside it; the rest of the page is still usable.
|
|
1039
1044
|
- Action SUCCESS but ariaDiff empty → may have worked without visible DOM change; check result message.
|
|
1040
|
-
- MultipleElementsFound →
|
|
1045
|
+
- MultipleElementsFound → nothing was clicked. Tell Tester to reuse the same locator with step.opts({ elementIndex: N }) from the numbered elements list.
|
|
1041
1046
|
- Wrong page (settings vs feature) → getVisitedStates() then back() or reset(). Don't try breadcrumbs (SPA back-nav is unreliable).
|
|
1042
1047
|
- Click SUCCESS but executed locator ≠ explanation intent, or "skipped" attempts present → wrong element clicked.
|
|
1043
1048
|
- form(I.type()) SUCCESS but "element" shows a button/link → keys went to wrong element; click the input first.
|
|
1044
1049
|
- ariaDiff shows 5+ added/removed → page entered new mode (editor/modal); call context() before guessing selectors.
|
|
1045
|
-
- Empty dropdown/list when items expected →
|
|
1050
|
+
- Empty dropdown/list when items expected → wait explicitly, then check the state changed: ariaDiff and any GET that loaded data. If still nothing loaded, confirm the empty state with verify().
|
|
1046
1051
|
- Search-and-select needs SEQUENCE: focus trigger → type to filter → click option. Tell Tester to split into separate tool calls.
|
|
1047
1052
|
- Multi-action explanation in one tool call → instruct Tester to split.
|
|
1048
1053
|
|
package/dist/src/ai/planner.js
CHANGED
|
@@ -96,11 +96,15 @@ export class Planner extends PlannerBase {
|
|
|
96
96
|
Tests must be relevant to the page
|
|
97
97
|
Tests must be achievable from UI
|
|
98
98
|
Tests must be verifiable from UI
|
|
99
|
-
|
|
99
|
+
One test verifies ONE business operation: the steps that reach it, the action itself, and its verification.
|
|
100
|
+
Steps that only reach the action — opening a form, expanding a panel, creating or locating the item to act on — belong to that test. A second operation with its own verification does not.
|
|
100
101
|
Bad: "Open delete dropdown" + "Confirm deletion" — these are ONE test, not two.
|
|
101
102
|
Bad: "Search for X" + "Verify search results" — searching and verifying is ONE test.
|
|
102
103
|
Bad: "Leave field empty" + "Click submit" — that's one negative test, not two.
|
|
103
|
-
|
|
104
|
+
Bad: "Create a record, rename it, delete it" — three verified operations, so THREE tests, not one.
|
|
105
|
+
Good: "Rename existing record and verify the new title" — ONE test; creating is skipped, we assume record already exists, only the rename is verified.
|
|
106
|
+
You may rely on another test having run first in case we deal with empty state and no relevant data was created yet and we expect another our test creates it
|
|
107
|
+
When the page reports a record is missing or unavailable, it is not a testable surface — plan list-level or recovery behavior instead of operations on that record.${featureDirective}${focusExistingDataDirective}
|
|
104
108
|
</task>
|
|
105
109
|
|
|
106
110
|
${customPrompt || ''}
|
|
@@ -166,9 +170,6 @@ export class Planner extends PlannerBase {
|
|
|
166
170
|
if (!aiResult?.object?.scenarios) {
|
|
167
171
|
throw new Error('No tasks were created successfully');
|
|
168
172
|
}
|
|
169
|
-
if (aiResult.object.scenarios.length === 0 && !this.currentPlan) {
|
|
170
|
-
throw new Error('No tasks were created successfully');
|
|
171
|
-
}
|
|
172
173
|
const defaultStartUrl = this.getDefaultStartUrl(state);
|
|
173
174
|
const fromPlanning = aiResult.object.scenarios.map((s) => new Test(s.scenario, s.priority, s.expectedOutcomes, s.startUrl || defaultStartUrl, s.steps || []));
|
|
174
175
|
return { tests: fromPlanning, planName: aiResult.object.planName };
|
|
@@ -295,6 +296,7 @@ export class Planner extends PlannerBase {
|
|
|
295
296
|
<task>
|
|
296
297
|
Based on the page research, create ${this.MIN_TASKS}-${this.MAX_TASKS} exploratory testing scenarios.
|
|
297
298
|
For each scenario provide specific steps and expected outcomes.
|
|
299
|
+
Exception: if the page reports the requested resource is missing, shows a failure state, or holds no content and no controls, return an empty scenarios list. Never invent tests for a page with nothing to exercise.
|
|
298
300
|
</task>
|
|
299
301
|
|
|
300
302
|
<rules>
|
|
@@ -308,13 +310,13 @@ export class Planner extends PlannerBase {
|
|
|
308
310
|
Focus on error or success messages as outcome.
|
|
309
311
|
Focus on URL page change or data persistency after page reload.
|
|
310
312
|
If there are subpages (pages with same URL path) plan testing of those subpages as well
|
|
311
|
-
|
|
313
|
+
Plan CRUD operations in order: create, read, update, delete.
|
|
312
314
|
Do not invent specific route names, success messages, validation texts, badge counts, or welcome messages unless they are visible in research, visited pages, or prior observed flows.
|
|
313
315
|
When validation placement or wording was not observed, require feedback associated with the invalid input without inventing a specific location or message.
|
|
314
316
|
If exact wording is unknown, describe the expected result generically, for example "an authentication error is shown" or "the user stays on the login page" instead of guessing the literal text.
|
|
315
317
|
If exact redirect destination is unknown, describe the destination by visible page identity, for example "the dashboard page opens" or "the current workspace home page opens" instead of inventing a URL slug.
|
|
316
318
|
Only propose scenarios whose prerequisites are evident from page research, visited pages, or API data preparation context.
|
|
317
|
-
If a scenario needs existing records, recipients, results, notifications, or other target data, propose it only when that data is visible
|
|
319
|
+
If a scenario needs existing records, recipients, results, notifications, or other target data, propose it only when that data is visible, API preconditions can create it, or the scenario itself creates the record as its setup.
|
|
318
320
|
If the page appears read-only, degraded, demo-limited, maintenance-like, or lacks write controls, prefer read-only scenarios such as opening panels, inspecting visible lists, filtering, searching, or verifying current state.
|
|
319
321
|
Do not assume hidden data exists just because a control is present.
|
|
320
322
|
For scenarios that act on existing items or search/filter by existing values, use only item names or values visible in research, visited pages, or prior observed flows.
|
package/dist/src/ai/provider.js
CHANGED
|
@@ -10,6 +10,7 @@ import { configuredModels, modelName as getModelName } from '../config.js';
|
|
|
10
10
|
import { executionController } from "../execution-controller.js";
|
|
11
11
|
import { Observability } from "../observability.js";
|
|
12
12
|
import { Stats } from "../stats.js";
|
|
13
|
+
import { getCliName } from '../utils/cli-name.js';
|
|
13
14
|
import { createDebug, tag } from '../utils/logger.js';
|
|
14
15
|
import { withRetry } from '../utils/retry.js';
|
|
15
16
|
import { RulesLoader } from "../utils/rules-loader.js";
|
|
@@ -43,14 +44,7 @@ export async function flushTelemetry() {
|
|
|
43
44
|
}
|
|
44
45
|
const CONTEXT_LENGTH_PATTERNS = ['reduce the length', 'context length', 'maximum context', 'token limit', 'too many tokens', 'max_tokens', 'context_length_exceeded', 'output truncated at maxtokens'];
|
|
45
46
|
function extractCachedTokens(usage) {
|
|
46
|
-
|
|
47
|
-
return 0;
|
|
48
|
-
const direct = usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens;
|
|
49
|
-
if (typeof direct === 'number')
|
|
50
|
-
return direct;
|
|
51
|
-
const raw = usage.raw;
|
|
52
|
-
const fromRaw = raw?.prompt_tokens_details?.cached_tokens ?? raw?.promptTokensDetails?.cachedTokens;
|
|
53
|
-
return typeof fromRaw === 'number' ? fromRaw : 0;
|
|
47
|
+
return usage?.inputTokenDetails?.cacheReadTokens ?? 0;
|
|
54
48
|
}
|
|
55
49
|
function abortAfterIdle(ms, cancel, controller) {
|
|
56
50
|
return new Promise((_, reject) => {
|
|
@@ -99,7 +93,7 @@ export class Provider {
|
|
|
99
93
|
modelCallWaiters = [];
|
|
100
94
|
constructor(config) {
|
|
101
95
|
if (!config?.model) {
|
|
102
|
-
throw new AiError(
|
|
96
|
+
throw new AiError(`AI model is not configured. Set ai.model in your config file, or pick one with ${getCliName()} recommended-models`);
|
|
103
97
|
}
|
|
104
98
|
this.config = config;
|
|
105
99
|
this.initLangfuse();
|
|
@@ -226,8 +220,8 @@ export class Provider {
|
|
|
226
220
|
if (!usage)
|
|
227
221
|
return;
|
|
228
222
|
Stats.recordTokens(agentName, modelName, {
|
|
229
|
-
input: usage.inputTokens ??
|
|
230
|
-
output: usage.outputTokens ??
|
|
223
|
+
input: usage.inputTokens ?? 0,
|
|
224
|
+
output: usage.outputTokens ?? 0,
|
|
231
225
|
total: usage.totalTokens ?? 0,
|
|
232
226
|
cached: extractCachedTokens(usage),
|
|
233
227
|
});
|
|
@@ -387,7 +381,7 @@ export class Provider {
|
|
|
387
381
|
let invalidRequestFeedbackAdded = false;
|
|
388
382
|
const executedStepMessages = [];
|
|
389
383
|
try {
|
|
390
|
-
|
|
384
|
+
let response = await this.withModelRequestSlot(() => withRetry(async () => {
|
|
391
385
|
const stepMessages = [];
|
|
392
386
|
const onStepEnd = (step) => {
|
|
393
387
|
stepMessages.push(...(step.response?.messages || []));
|
|
@@ -413,7 +407,7 @@ export class Provider {
|
|
|
413
407
|
return result;
|
|
414
408
|
}, this.getRetryOptions(options)));
|
|
415
409
|
clearActivity();
|
|
416
|
-
withExecutedSteps(response, executedStepMessages);
|
|
410
|
+
response = withExecutedSteps(response, executedStepMessages);
|
|
417
411
|
// Log tool usage summary
|
|
418
412
|
if (response.toolCalls && response.toolCalls.length > 0) {
|
|
419
413
|
responseLog(response.toolCalls);
|
|
@@ -585,7 +579,7 @@ export class Provider {
|
|
|
585
579
|
}
|
|
586
580
|
async processImage(prompt, image) {
|
|
587
581
|
if (!this.config.visionModel) {
|
|
588
|
-
throw new Error(
|
|
582
|
+
throw new Error(`Vision model not configured. Set ai.visionModel in your config; ${getCliName()} recommended-models lists the providers that serve one`);
|
|
589
583
|
}
|
|
590
584
|
setActivity(`🤖 Processing image with ${this.config.visionModel}`, 'ai');
|
|
591
585
|
const messages = [
|
|
@@ -8,6 +8,7 @@ export declare function visuallyAnnotateContainers(page: Page, containers: Array
|
|
|
8
8
|
css: string;
|
|
9
9
|
label: string;
|
|
10
10
|
}>): Promise<number>;
|
|
11
|
+
export declare function removeVisualAnnotations(page: Page): Promise<void>;
|
|
11
12
|
export declare function WithCoordinates<T extends Constructor>(Base: T): {
|
|
12
13
|
new (...args: any[]): {
|
|
13
14
|
explorer: Explorer;
|
|
@@ -20,6 +21,7 @@ export declare function WithCoordinates<T extends Constructor>(Base: T): {
|
|
|
20
21
|
label: string;
|
|
21
22
|
}>;
|
|
22
23
|
}): Promise<number>;
|
|
24
|
+
removeVisualAnnotations(): Promise<void>;
|
|
23
25
|
_analyzeScreenshotForVisualProps(): Promise<VisualAnalysisResult>;
|
|
24
26
|
mergeVisualData(result: ResearchResult, visualData: Map<string, {
|
|
25
27
|
coordinates: string | null;
|