explorbot 0.1.32-beta.1 → 0.2.1
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 +35 -19
- package/bin/explorbot-cli.ts +33 -10
- package/boat/api-tester/src/config.ts +45 -3
- package/boat/doc-collector/src/ai/documentarian.ts +26 -28
- package/boat/doc-collector/src/ai/tools.ts +74 -16
- package/boat/doc-collector/src/cli.ts +1 -6
- package/boat/doc-collector/src/docbot.ts +30 -9
- package/boat/doc-collector/src/docs-renderer.ts +19 -24
- package/boat/doc-collector/src/screenshots.ts +34 -2
- package/boat/doc-collector/src/state-diagram.ts +281 -0
- package/dist/bin/explorbot-cli.js +29 -9
- package/dist/boat/api-tester/src/config.js +43 -4
- package/dist/boat/doc-collector/src/ai/documentarian.js +19 -7
- package/dist/boat/doc-collector/src/ai/tools.js +53 -16
- package/dist/boat/doc-collector/src/cli.js +1 -5
- package/dist/boat/doc-collector/src/docbot.js +27 -8
- package/dist/boat/doc-collector/src/docs-renderer.js +18 -3
- package/dist/boat/doc-collector/src/screenshots.js +30 -1
- package/dist/boat/doc-collector/src/state-diagram.js +200 -0
- package/dist/models.json +30 -0
- package/dist/package.json +23 -3
- package/dist/src/action-result.d.ts +8 -10
- package/dist/src/action-result.js +72 -164
- package/dist/src/action.d.ts +13 -12
- package/dist/src/action.js +37 -113
- package/dist/src/ai/agent.d.ts +17 -0
- package/dist/src/ai/captain/idle-mode.js +1 -1
- package/dist/src/ai/captain/test-mode.js +1 -1
- package/dist/src/ai/captain/web-mode.js +18 -29
- package/dist/src/ai/captain.d.ts +1 -3
- package/dist/src/ai/captain.js +17 -45
- package/dist/src/ai/conversation.d.ts +2 -5
- package/dist/src/ai/conversation.js +9 -28
- package/dist/src/ai/driller.d.ts +2 -17
- package/dist/src/ai/driller.js +26 -50
- package/dist/src/ai/experience-compactor.d.ts +2 -2
- package/dist/src/ai/experience-compactor.js +16 -33
- package/dist/src/ai/fisherman-tools.js +1 -1
- package/dist/src/ai/fisherman.d.ts +0 -1
- package/dist/src/ai/fisherman.js +0 -3
- package/dist/src/ai/historian/codeceptjs.d.ts +2 -0
- package/dist/src/ai/historian/codeceptjs.js +4 -3
- package/dist/src/ai/historian/playwright.d.ts +4 -1
- package/dist/src/ai/historian/playwright.js +4 -3
- package/dist/src/ai/historian/screencast.d.ts +2 -1
- package/dist/src/ai/historian/screencast.js +2 -2
- package/dist/src/ai/historian/utils.d.ts +0 -1
- package/dist/src/ai/historian/utils.js +0 -1
- package/dist/src/ai/historian.d.ts +10 -8
- package/dist/src/ai/historian.js +1 -5
- package/dist/src/ai/navigator.d.ts +8 -6
- package/dist/src/ai/navigator.js +34 -65
- package/dist/src/ai/pilot.d.ts +13 -8
- package/dist/src/ai/pilot.js +53 -59
- package/dist/src/ai/planner.d.ts +5 -4
- package/dist/src/ai/planner.js +66 -52
- package/dist/src/ai/provider.d.ts +4 -1
- package/dist/src/ai/provider.js +77 -137
- package/dist/src/ai/quartermaster.d.ts +22 -22
- package/dist/src/ai/quartermaster.js +8 -14
- package/dist/src/ai/rerunner.d.ts +3 -13
- package/dist/src/ai/rerunner.js +23 -44
- package/dist/src/ai/researcher/cache.js +11 -18
- package/dist/src/ai/researcher/coordinates.js +4 -3
- package/dist/src/ai/researcher/deep-analysis.d.ts +2 -0
- package/dist/src/ai/researcher/deep-analysis.js +8 -11
- package/dist/src/ai/researcher/locators.d.ts +9 -2
- package/dist/src/ai/researcher/locators.js +65 -8
- package/dist/src/ai/researcher/parser.js +1 -1
- package/dist/src/ai/researcher/research-result.d.ts +0 -1
- package/dist/src/ai/researcher/research-result.js +0 -19
- package/dist/src/ai/researcher/sections.d.ts +2 -0
- package/dist/src/ai/researcher/sections.js +7 -3
- package/dist/src/ai/researcher.js +40 -106
- package/dist/src/ai/rules.d.ts +1 -0
- package/dist/src/ai/rules.js +22 -15
- package/dist/src/ai/session-analyst.js +8 -5
- package/dist/src/ai/task-agent.d.ts +19 -6
- package/dist/src/ai/task-agent.js +37 -33
- package/dist/src/ai/tester.d.ts +6 -14
- package/dist/src/ai/tester.js +46 -111
- package/dist/src/ai/tools.d.ts +26 -14
- package/dist/src/ai/tools.js +138 -132
- package/dist/src/command-handler.js +1 -1
- package/dist/src/commands/compact-command.d.ts +1 -1
- package/dist/src/commands/compact-command.js +6 -6
- package/dist/src/commands/context-aria-command.js +1 -1
- package/dist/src/commands/context-command.js +8 -6
- package/dist/src/commands/context-data-command.js +2 -2
- package/dist/src/commands/context-experience-command.js +2 -2
- package/dist/src/commands/context-html-command.js +2 -2
- package/dist/src/commands/context-knowledge-command.js +2 -2
- package/dist/src/commands/drill-command.js +1 -1
- package/dist/src/commands/experience-command.js +1 -1
- package/dist/src/commands/explore-command.js +4 -3
- package/dist/src/commands/freesail-command.js +2 -2
- package/dist/src/commands/knows-command.js +1 -1
- package/dist/src/commands/learn-command.js +5 -3
- package/dist/src/commands/path-command.js +1 -1
- package/dist/src/commands/research-command.js +1 -1
- package/dist/src/commands/test-command.js +1 -1
- package/dist/src/components/AddKnowledge.d.ts +2 -0
- package/dist/src/components/AddKnowledge.js +3 -7
- package/dist/src/components/App.js +3 -3
- package/dist/src/config.d.ts +19 -0
- package/dist/src/config.js +156 -10
- package/dist/src/experience-tracker.d.ts +11 -6
- package/dist/src/experience-tracker.js +82 -155
- package/dist/src/explorbot.d.ts +21 -13
- package/dist/src/explorbot.js +84 -80
- package/dist/src/explorer.d.ts +66 -102
- package/dist/src/explorer.js +332 -623
- package/dist/src/knowledge-tracker.d.ts +2 -2
- package/dist/src/knowledge-tracker.js +38 -45
- package/dist/src/state-manager.d.ts +10 -30
- package/dist/src/state-manager.js +11 -129
- package/dist/src/utils/aria.d.ts +6 -1
- package/dist/src/utils/aria.js +4 -1
- package/dist/src/utils/cache.d.ts +15 -0
- package/dist/src/utils/cache.js +34 -0
- package/dist/src/utils/context-formatter.d.ts +6 -0
- package/dist/src/utils/context-formatter.js +15 -27
- package/dist/src/utils/hooks-runner.js +2 -4
- package/dist/src/utils/html.d.ts +5 -0
- package/dist/src/utils/html.js +71 -0
- package/dist/src/utils/markdown-files.d.ts +10 -0
- package/dist/src/utils/markdown-files.js +21 -0
- package/dist/src/utils/markdown-query.d.ts +6 -0
- package/dist/src/utils/markdown-query.js +14 -1
- package/dist/src/utils/page-readiness.d.ts +1 -0
- package/dist/src/utils/page-readiness.js +1 -1
- package/dist/src/utils/secrets.js +2 -2
- package/dist/src/utils/strings.d.ts +2 -0
- package/dist/src/utils/strings.js +10 -0
- package/dist/src/utils/test-files.js +1 -1
- package/dist/src/utils/web-annotate.d.ts +5 -0
- package/dist/src/utils/web-annotate.js +58 -0
- package/dist/src/utils/web-eidx.d.ts +2 -0
- package/dist/src/utils/web-eidx.js +20 -0
- package/dist/src/utils/web-element.d.ts +3 -1
- package/dist/src/utils/web-element.js +32 -0
- package/dist/src/utils/web-sandbox.d.ts +4 -0
- package/dist/src/utils/web-sandbox.js +37 -0
- package/models.json +30 -0
- package/package.json +23 -3
- package/src/action-result.ts +70 -173
- package/src/action.ts +38 -132
- package/src/ai/agent.ts +20 -0
- package/src/ai/captain/idle-mode.ts +1 -1
- package/src/ai/captain/test-mode.ts +1 -1
- package/src/ai/captain/web-mode.ts +18 -30
- package/src/ai/captain.ts +17 -42
- package/src/ai/conversation.ts +9 -32
- package/src/ai/driller.ts +53 -83
- package/src/ai/experience-compactor.ts +16 -35
- package/src/ai/fisherman-tools.ts +1 -1
- package/src/ai/fisherman.ts +0 -4
- package/src/ai/historian/codeceptjs.ts +5 -3
- package/src/ai/historian/playwright.ts +7 -4
- package/src/ai/historian/screencast.ts +4 -3
- package/src/ai/historian/utils.ts +0 -1
- package/src/ai/historian.ts +6 -8
- package/src/ai/navigator.ts +39 -70
- package/src/ai/pilot.ts +59 -61
- package/src/ai/planner.ts +76 -62
- package/src/ai/provider.ts +85 -146
- package/src/ai/quartermaster.ts +39 -45
- package/src/ai/rerunner.ts +27 -50
- package/src/ai/researcher/cache.ts +10 -16
- package/src/ai/researcher/coordinates.ts +4 -3
- package/src/ai/researcher/deep-analysis.ts +10 -11
- package/src/ai/researcher/locators.ts +66 -9
- package/src/ai/researcher/parser.ts +1 -1
- package/src/ai/researcher/research-result.ts +0 -18
- package/src/ai/researcher/sections.ts +9 -3
- package/src/ai/researcher.ts +42 -119
- package/src/ai/rules.ts +24 -13
- package/src/ai/session-analyst.ts +8 -5
- package/src/ai/task-agent.ts +45 -38
- package/src/ai/tester.ts +51 -129
- package/src/ai/tools.ts +145 -153
- package/src/command-handler.ts +1 -1
- package/src/commands/compact-command.ts +7 -8
- package/src/commands/context-aria-command.ts +1 -1
- package/src/commands/context-command.ts +8 -6
- package/src/commands/context-data-command.ts +2 -2
- package/src/commands/context-experience-command.ts +2 -2
- package/src/commands/context-html-command.ts +2 -2
- package/src/commands/context-knowledge-command.ts +2 -2
- package/src/commands/drill-command.ts +1 -1
- package/src/commands/experience-command.ts +1 -1
- package/src/commands/explore-command.ts +4 -3
- package/src/commands/freesail-command.ts +2 -2
- package/src/commands/knows-command.ts +1 -1
- package/src/commands/learn-command.ts +5 -3
- package/src/commands/path-command.ts +1 -1
- package/src/commands/research-command.ts +1 -1
- package/src/commands/test-command.ts +1 -1
- package/src/components/AddKnowledge.tsx +5 -7
- package/src/components/App.tsx +3 -3
- package/src/config.ts +185 -11
- package/src/experience-tracker.ts +89 -152
- package/src/explorbot.ts +88 -83
- package/src/explorer.ts +402 -693
- package/src/knowledge-tracker.ts +42 -49
- package/src/state-manager.ts +20 -155
- package/src/utils/aria.ts +11 -2
- package/src/utils/cache.ts +40 -0
- package/src/utils/context-formatter.ts +12 -15
- package/src/utils/hooks-runner.ts +2 -4
- package/src/utils/html.ts +79 -0
- package/src/utils/markdown-files.ts +30 -0
- package/src/utils/markdown-query.ts +15 -1
- package/src/utils/page-readiness.ts +1 -1
- package/src/utils/secrets.ts +2 -3
- package/src/utils/strings.ts +12 -0
- package/src/utils/test-files.ts +1 -1
- package/src/utils/web-annotate.ts +64 -0
- package/src/utils/web-eidx.ts +21 -0
- package/src/utils/web-element.ts +34 -0
- package/src/utils/web-sandbox.ts +43 -0
- package/dist/boat/api-tester/example/apibot.config.js +0 -30
package/src/ai/task-agent.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import dedent from 'dedent';
|
|
2
1
|
import type { ActionResult } from '../action-result.js';
|
|
3
|
-
import
|
|
2
|
+
import type { ExplorbotConfig } from '../config.ts';
|
|
3
|
+
import type { ExperienceTracker } from '../experience-tracker.js';
|
|
4
|
+
import type Explorer from '../explorer.ts';
|
|
4
5
|
import type { KnowledgeTracker } from '../knowledge-tracker.js';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import type { StateManager } from '../state-manager.ts';
|
|
7
|
+
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
8
|
+
import type { AgentDeps, ToolDeps } from './agent.ts';
|
|
8
9
|
import { Historian } from './historian.js';
|
|
9
10
|
import type { Navigator } from './navigator.js';
|
|
10
11
|
import type { Provider } from './provider.js';
|
|
@@ -21,6 +22,12 @@ function createNullProxy<T extends object>(): T {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export abstract class TaskAgent {
|
|
25
|
+
explorer!: Explorer;
|
|
26
|
+
provider!: Provider;
|
|
27
|
+
config!: ExplorbotConfig;
|
|
28
|
+
stateManager!: StateManager;
|
|
29
|
+
knowledgeTracker!: KnowledgeTracker;
|
|
30
|
+
protected hooksRunner!: HooksRunner;
|
|
24
31
|
protected consecutiveFailures = 0;
|
|
25
32
|
protected consecutiveEmptyResults = 0;
|
|
26
33
|
protected recentToolCalls: any[] = [];
|
|
@@ -29,44 +36,48 @@ export abstract class TaskAgent {
|
|
|
29
36
|
private _historian: Historian | null = null;
|
|
30
37
|
private _quartermaster: Quartermaster | null = null;
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
constructor(deps?: AgentDeps) {
|
|
40
|
+
if (!deps) return;
|
|
41
|
+
this.explorer = deps.explorer;
|
|
42
|
+
this.provider = deps.ai;
|
|
43
|
+
this.config = deps.config;
|
|
44
|
+
this.stateManager = deps.stateManager;
|
|
45
|
+
this.knowledgeTracker = deps.knowledgeTracker;
|
|
46
|
+
this.hooksRunner = new HooksRunner(deps.explorer, deps.config);
|
|
47
|
+
}
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
setHistorian(historian: Historian): void {
|
|
50
|
+
this._historian = historian;
|
|
51
|
+
}
|
|
39
52
|
|
|
40
|
-
|
|
53
|
+
setQuartermaster(quartermaster: Quartermaster): void {
|
|
54
|
+
this._quartermaster = quartermaster;
|
|
55
|
+
}
|
|
41
56
|
|
|
42
|
-
|
|
43
|
-
.map((k) => k.content)
|
|
44
|
-
.filter((k) => !!k)
|
|
45
|
-
.join('\n\n');
|
|
57
|
+
protected abstract getNavigator(): Navigator;
|
|
46
58
|
|
|
47
|
-
|
|
48
|
-
return
|
|
49
|
-
|
|
50
|
-
Here is relevant knowledge for this page:
|
|
59
|
+
protected get toolDeps(): ToolDeps {
|
|
60
|
+
return { explorer: this.explorer, stateManager: this.stateManager, ai: this.provider };
|
|
61
|
+
}
|
|
51
62
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`;
|
|
63
|
+
protected getExperienceTracker(): ExperienceTracker {
|
|
64
|
+
return this.stateManager.getExperienceTracker();
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
protected
|
|
58
|
-
|
|
59
|
-
const toc = tracker.getExperienceTableOfContents(actionResult);
|
|
60
|
-
if (toc.length === 0) return '';
|
|
61
|
-
|
|
62
|
-
const totalSections = toc.reduce((sum, entry) => sum + entry.sections.length, 0);
|
|
63
|
-
debugLog(`injecting experience TOC (${toc.length} files, ${totalSections} sections)`);
|
|
64
|
-
tag('operation').log(`Found ${toc.length} experience ${pluralize(toc.length, 'file')} (${totalSections} sections)`);
|
|
65
|
-
return renderExperienceToc(toc);
|
|
67
|
+
protected getKnowledgeTracker(): KnowledgeTracker {
|
|
68
|
+
return this.knowledgeTracker;
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
|
|
69
|
-
this.
|
|
71
|
+
protected getProvider(): Provider {
|
|
72
|
+
return this.provider;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
protected getKnowledge(actionResult: ActionResult): string {
|
|
76
|
+
return this.getKnowledgeTracker().renderRelevantKnowledge(actionResult);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected getExperience(actionResult: ActionResult): string {
|
|
80
|
+
return this.getExperienceTracker().renderExperienceTocFor(actionResult);
|
|
70
81
|
}
|
|
71
82
|
|
|
72
83
|
protected getHistorian(): Historian {
|
|
@@ -74,10 +85,6 @@ export abstract class TaskAgent {
|
|
|
74
85
|
return createNullProxy<Historian>();
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
setQuartermaster(quartermaster: Quartermaster): void {
|
|
78
|
-
this._quartermaster = quartermaster;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
88
|
protected getQuartermaster(): Quartermaster {
|
|
82
89
|
if (this._quartermaster) return this._quartermaster;
|
|
83
90
|
return createNullProxy<Quartermaster>();
|
package/src/ai/tester.ts
CHANGED
|
@@ -5,20 +5,17 @@ import dedent from 'dedent';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { ActionResult } from '../action-result.ts';
|
|
7
7
|
import { clearActivity, setActivity } from '../activity.ts';
|
|
8
|
-
import {
|
|
9
|
-
import type {
|
|
10
|
-
import type Explorer from '../explorer.ts';
|
|
8
|
+
import type { RequestStore } from '../api/request-store.ts';
|
|
9
|
+
import type { TestRun } from '../explorer.ts';
|
|
11
10
|
import { Observability } from '../observability.ts';
|
|
12
|
-
import type { StateTransition
|
|
11
|
+
import type { StateTransition } from '../state-manager.ts';
|
|
13
12
|
import { Stats } from '../stats.ts';
|
|
14
|
-
import { type
|
|
13
|
+
import { type Test, TestResult, type TestResultType } from '../test-plan.ts';
|
|
15
14
|
import { detectFocusArea, extractFocusedElement } from '../utils/aria.ts';
|
|
16
15
|
import { ErrorPageError, isErrorPage } from '../utils/error-page.ts';
|
|
17
|
-
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
18
|
-
import { codeToMarkdown } from '../utils/html.ts';
|
|
19
16
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
20
17
|
import { loop } from '../utils/loop.ts';
|
|
21
|
-
import type { Agent } from './agent.ts';
|
|
18
|
+
import type { Agent, AgentDeps } from './agent.ts';
|
|
22
19
|
import type { Captain } from './captain.ts';
|
|
23
20
|
import type { Conversation } from './conversation.ts';
|
|
24
21
|
import { Navigator } from './navigator.ts';
|
|
@@ -27,7 +24,7 @@ import { Provider } from './provider.ts';
|
|
|
27
24
|
import { Researcher } from './researcher.ts';
|
|
28
25
|
import { actionRule, capabilityGroundingRule, dataProtectionRules, focusedElementRule, formRequirementsRule, locatorRule, multipleTabsRule, sectionContextRule } from './rules.ts';
|
|
29
26
|
import { TaskAgent } from './task-agent.ts';
|
|
30
|
-
import { createCodeceptJSTools,
|
|
27
|
+
import { createCodeceptJSTools, createIframeTools } from './tools.ts';
|
|
31
28
|
|
|
32
29
|
const debugLog = createDebug('explorbot:tester');
|
|
33
30
|
|
|
@@ -46,8 +43,8 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
46
43
|
protected readonly ACTION_TOOLS = ['click', 'hover', 'pressKey', 'form'];
|
|
47
44
|
protected readonly SPECIAL_CONTEXT_ACTION_TOOLS = ['exitIframe'];
|
|
48
45
|
emoji = '🧪';
|
|
49
|
-
private
|
|
50
|
-
private
|
|
46
|
+
private requestStore: RequestStore;
|
|
47
|
+
private testRun: TestRun | null = null;
|
|
51
48
|
private currentConversation: Conversation | null = null;
|
|
52
49
|
private pilot: Pilot | null = null;
|
|
53
50
|
private captain: Captain | null = null;
|
|
@@ -58,43 +55,27 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
58
55
|
researcher: Researcher;
|
|
59
56
|
navigator: Navigator;
|
|
60
57
|
agentTools: any;
|
|
61
|
-
executionLogFile: string | null = null;
|
|
62
58
|
private previousUrl: string | null = null;
|
|
63
59
|
private previousStateHash: string | null = null;
|
|
64
60
|
private pageStateHash: string | null = null;
|
|
65
61
|
private pageActionResult: ActionResult | null = null;
|
|
66
|
-
private hooksRunner: HooksRunner;
|
|
67
62
|
private seenUiMapUrls = new Set<string>();
|
|
68
63
|
private lastAnalyzedStateHash: string | null = null;
|
|
69
64
|
private stalledIterations = 0;
|
|
70
65
|
private readonly MAX_STALLED_ITERATIONS = 3;
|
|
71
66
|
|
|
72
|
-
constructor(
|
|
73
|
-
super();
|
|
74
|
-
this.
|
|
75
|
-
this.provider = provider;
|
|
67
|
+
constructor(deps: AgentDeps, researcher: Researcher, navigator: Navigator, agentTools?: any) {
|
|
68
|
+
super(deps);
|
|
69
|
+
this.requestStore = deps.requestStore;
|
|
76
70
|
this.researcher = researcher;
|
|
77
71
|
this.navigator = navigator;
|
|
78
72
|
this.agentTools = agentTools;
|
|
79
|
-
this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
|
|
80
73
|
}
|
|
81
74
|
|
|
82
75
|
protected getNavigator(): Navigator {
|
|
83
76
|
return this.navigator;
|
|
84
77
|
}
|
|
85
78
|
|
|
86
|
-
protected getExperienceTracker(): ExperienceTracker {
|
|
87
|
-
return this.explorer.getStateManager().getExperienceTracker();
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
protected getKnowledgeTracker() {
|
|
91
|
-
return this.explorer.getKnowledgeTracker();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
protected getProvider(): Provider {
|
|
95
|
-
return this.provider;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
79
|
setPilot(pilot: Pilot): void {
|
|
99
80
|
this.pilot = pilot;
|
|
100
81
|
}
|
|
@@ -104,11 +85,11 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
104
85
|
}
|
|
105
86
|
|
|
106
87
|
private getCurrentState(): ActionResult {
|
|
107
|
-
return ActionResult.fromState(this.
|
|
88
|
+
return ActionResult.fromState(this.stateManager.getCurrentState()!);
|
|
108
89
|
}
|
|
109
90
|
|
|
110
91
|
private get progressCheckInterval(): number {
|
|
111
|
-
return (this.
|
|
92
|
+
return (this.config.ai?.agents?.tester as any)?.progressCheckInterval ?? 3;
|
|
112
93
|
}
|
|
113
94
|
|
|
114
95
|
getConversation(): Conversation | null {
|
|
@@ -117,7 +98,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
117
98
|
|
|
118
99
|
async test(task: Test): Promise<{ success: boolean }> {
|
|
119
100
|
Stats.tests++;
|
|
120
|
-
const state = this.
|
|
101
|
+
const state = this.stateManager.getCurrentState();
|
|
121
102
|
if (!state) throw new Error('No state found');
|
|
122
103
|
|
|
123
104
|
setActivity(`🧪 Testing: ${task.scenario}`, 'action');
|
|
@@ -129,20 +110,20 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
129
110
|
this.seenUiMapUrls.clear();
|
|
130
111
|
this.lastAnalyzedStateHash = null;
|
|
131
112
|
this.stalledIterations = 0;
|
|
132
|
-
this.
|
|
113
|
+
this.stateManager.clearHistory();
|
|
133
114
|
this.resetFailureCount();
|
|
134
115
|
this.pilot?.reset();
|
|
135
116
|
|
|
136
|
-
const requestStore = this.
|
|
137
|
-
requestStore
|
|
138
|
-
const offFailedRequest = requestStore
|
|
117
|
+
const requestStore = this.requestStore;
|
|
118
|
+
requestStore.clear();
|
|
119
|
+
const offFailedRequest = requestStore.onFailedRequest((r) => {
|
|
139
120
|
task.addNote(`Network error: ${r.method} ${r.path} → ${r.status}`, TestResult.FAILED);
|
|
140
121
|
});
|
|
141
122
|
|
|
142
123
|
const initialState = ActionResult.fromState(state);
|
|
143
124
|
if (isErrorPage(initialState)) {
|
|
144
125
|
task.start();
|
|
145
|
-
await this.explorer.
|
|
126
|
+
this.testRun = await this.explorer.beginTest(task);
|
|
146
127
|
offFailedRequest?.();
|
|
147
128
|
return await this.abortStartedTestOnErrorPage(task, initialState);
|
|
148
129
|
}
|
|
@@ -151,10 +132,6 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
151
132
|
conversation.markLastMessageCacheable();
|
|
152
133
|
this.currentConversation = conversation;
|
|
153
134
|
|
|
154
|
-
const outputDir = ConfigParser.getInstance().getOutputDir();
|
|
155
|
-
this.executionLogFile = join(outputDir, `tester_${task.sessionName}.md`);
|
|
156
|
-
// Note: Markdown saving functionality removed from Conversation class
|
|
157
|
-
|
|
158
135
|
const scenarioBlock = this.buildScenarioBlock(task, initialState);
|
|
159
136
|
conversation.addUserText(scenarioBlock);
|
|
160
137
|
conversation.markLastMessageCacheable();
|
|
@@ -203,7 +180,8 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
203
180
|
|
|
204
181
|
debugLog('Starting test execution with tools');
|
|
205
182
|
|
|
206
|
-
|
|
183
|
+
this.testRun = await this.explorer.beginTest(task);
|
|
184
|
+
if (!this.testRun.started) {
|
|
207
185
|
offFailedRequest?.();
|
|
208
186
|
await this.cleanupStartedTest(task);
|
|
209
187
|
return { success: task.isSuccessful };
|
|
@@ -221,7 +199,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
221
199
|
}
|
|
222
200
|
}
|
|
223
201
|
|
|
224
|
-
const startState = this.
|
|
202
|
+
const startState = this.stateManager.getCurrentState();
|
|
225
203
|
if (startState) {
|
|
226
204
|
task.addUrlNote(startState);
|
|
227
205
|
const startActionResult = ActionResult.fromState(startState);
|
|
@@ -233,14 +211,14 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
233
211
|
const currentUrl = startState?.url || task.startUrl || '';
|
|
234
212
|
await this.hooksRunner.runBeforeHook('tester', currentUrl);
|
|
235
213
|
|
|
236
|
-
const offStateChange = this.
|
|
214
|
+
const offStateChange = this.stateManager.onStateChange((event: StateTransition) => {
|
|
237
215
|
if (task.hasFinished) return;
|
|
238
216
|
if (event.toState?.url === event.fromState?.url) return;
|
|
239
217
|
if (event.toState) task.addUrlNote(event.toState, event.fromState || undefined);
|
|
240
218
|
task.states.push(event.toState);
|
|
241
219
|
});
|
|
242
220
|
|
|
243
|
-
const codeceptjsTools = createCodeceptJSTools(this.
|
|
221
|
+
const codeceptjsTools = createCodeceptJSTools(this.toolDeps, task);
|
|
244
222
|
let assertionPerformed = false;
|
|
245
223
|
let extensions = 0;
|
|
246
224
|
let shouldContinue = true;
|
|
@@ -251,7 +229,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
251
229
|
await loop(
|
|
252
230
|
async ({ stop, pause, iteration, userInput }) => {
|
|
253
231
|
debugLog('iteration', iteration);
|
|
254
|
-
if (!(await this.explorer.
|
|
232
|
+
if (!(await this.explorer.recover()).ok) {
|
|
255
233
|
task.addNote('Browser page is unavailable');
|
|
256
234
|
task.finish(TestResult.FAILED);
|
|
257
235
|
stop();
|
|
@@ -261,14 +239,16 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
261
239
|
|
|
262
240
|
const tools = {
|
|
263
241
|
...codeceptjsTools,
|
|
264
|
-
...(currentState.isInsideIframe ? createSpecialContextTools(this.explorer, 'iframe') : {}),
|
|
265
242
|
...this.createTestFlowTools(task, currentState, conversation),
|
|
266
243
|
...this.agentTools,
|
|
267
244
|
};
|
|
245
|
+
if (currentState.isInsideIframe) {
|
|
246
|
+
Object.assign(tools, createIframeTools(this.toolDeps));
|
|
247
|
+
}
|
|
268
248
|
|
|
269
249
|
debugLog(`Test ${task.scenario} iteration ${iteration}`);
|
|
270
250
|
|
|
271
|
-
if (this.
|
|
251
|
+
if (this.stateManager.isInDeadLoop()) {
|
|
272
252
|
task.addNote('Dead loop detected. Stopped');
|
|
273
253
|
stop();
|
|
274
254
|
return;
|
|
@@ -417,7 +397,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
417
397
|
|
|
418
398
|
if (task.hasFinished) break;
|
|
419
399
|
|
|
420
|
-
if (!(await this.explorer.
|
|
400
|
+
if (!(await this.explorer.recover()).ok) break;
|
|
421
401
|
|
|
422
402
|
const finalState = this.getCurrentState();
|
|
423
403
|
const wantsContinue = await this.pilot!.finalReview(task, finalState, conversation, this.navigator);
|
|
@@ -436,7 +416,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
436
416
|
shouldContinue = true;
|
|
437
417
|
}
|
|
438
418
|
|
|
439
|
-
const finalUrl = this.
|
|
419
|
+
const finalUrl = this.stateManager.getCurrentState()?.url || currentUrl;
|
|
440
420
|
await this.hooksRunner.runAfterHook('tester', finalUrl);
|
|
441
421
|
|
|
442
422
|
await this.getHistorian().saveSession(task, initialState, conversation);
|
|
@@ -448,7 +428,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
448
428
|
offStateChange();
|
|
449
429
|
offFailedRequest?.();
|
|
450
430
|
await this.finishTest(task);
|
|
451
|
-
await this.
|
|
431
|
+
await this.testRun?.stop(this.buildStopTestMeta(task));
|
|
452
432
|
|
|
453
433
|
return {
|
|
454
434
|
success: task.isSuccessful,
|
|
@@ -526,7 +506,6 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
526
506
|
const currentStateHash = currentState.hash;
|
|
527
507
|
|
|
528
508
|
const isNewUrl = this.previousUrl !== currentUrl;
|
|
529
|
-
const isStateChanged = !isNewUrl && this.previousStateHash !== currentStateHash;
|
|
530
509
|
|
|
531
510
|
this.previousUrl = currentUrl;
|
|
532
511
|
this.previousStateHash = currentStateHash;
|
|
@@ -565,7 +544,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
565
544
|
}
|
|
566
545
|
|
|
567
546
|
if (currentState.isInsideIframe) {
|
|
568
|
-
const iframeInfo = currentState.iframeURL ||
|
|
547
|
+
const iframeInfo = currentState.iframeURL || 'iframe context active';
|
|
569
548
|
context += dedent`
|
|
570
549
|
<iframe_context>
|
|
571
550
|
INSIDE IFRAME: ${iframeInfo}
|
|
@@ -574,10 +553,10 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
574
553
|
`;
|
|
575
554
|
}
|
|
576
555
|
|
|
577
|
-
|
|
578
|
-
|
|
556
|
+
const otherTabs = this.stateManager.otherTabs;
|
|
557
|
+
if (otherTabs.length > 0) {
|
|
579
558
|
context += multipleTabsRule(otherTabs);
|
|
580
|
-
this.
|
|
559
|
+
this.stateManager.otherTabs = [];
|
|
581
560
|
}
|
|
582
561
|
|
|
583
562
|
if (isNewUrl) {
|
|
@@ -641,27 +620,6 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
641
620
|
}
|
|
642
621
|
}
|
|
643
622
|
|
|
644
|
-
// if (isStateChanged) {
|
|
645
|
-
// const combinedHtml = await currentState.combinedHtml();
|
|
646
|
-
// context += dedent`
|
|
647
|
-
// Context (state changed):
|
|
648
|
-
|
|
649
|
-
// <page>
|
|
650
|
-
// CURRENT URL: ${currentState.url}
|
|
651
|
-
// CURRENT TITLE: ${currentState.title}
|
|
652
|
-
// </page>
|
|
653
|
-
|
|
654
|
-
// <page_html>
|
|
655
|
-
// ${combinedHtml}
|
|
656
|
-
// </page_html>
|
|
657
|
-
|
|
658
|
-
// <page_aria>
|
|
659
|
-
// ${currentState.ariaSnapshot}
|
|
660
|
-
// </page_aria>
|
|
661
|
-
// `;
|
|
662
|
-
// return context;
|
|
663
|
-
// }
|
|
664
|
-
|
|
665
623
|
if (context) return context;
|
|
666
624
|
|
|
667
625
|
if (iteration % 5) return '';
|
|
@@ -680,38 +638,6 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
680
638
|
`;
|
|
681
639
|
}
|
|
682
640
|
|
|
683
|
-
private async promptLogStep(task: Test): Promise<string> {
|
|
684
|
-
let logPrompt = dedent`
|
|
685
|
-
<task>
|
|
686
|
-
Add a note explaining what you achieved with previous action.
|
|
687
|
-
Use tools to interact with the page to achieve the scenario goal or expected outcomes.
|
|
688
|
-
Call record tool to explain the last action
|
|
689
|
-
Format: record([<action performed>, <what has changed>, <what you expect to do next>])
|
|
690
|
-
</task>
|
|
691
|
-
`;
|
|
692
|
-
|
|
693
|
-
if (task.getPrintableNotes()) {
|
|
694
|
-
logPrompt = dedent`
|
|
695
|
-
Your interaction log notes:
|
|
696
|
-
<notes>
|
|
697
|
-
${task.getPrintableNotes()}
|
|
698
|
-
</notes>
|
|
699
|
-
|
|
700
|
-
<rules>
|
|
701
|
-
Use your previous interaction notes to guide your next actions.
|
|
702
|
-
Do not perform the same checks.
|
|
703
|
-
</rules>
|
|
704
|
-
`;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
const remaining = task.getRemainingExpectations();
|
|
708
|
-
if (remaining.length > 0) {
|
|
709
|
-
logPrompt += `\nExpected steps to check: ${remaining.join(', ')}`;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
return logPrompt;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
641
|
private finishTest(task: Test): void {
|
|
716
642
|
if (!task.hasFinished) {
|
|
717
643
|
task.finish(TestResult.FAILED);
|
|
@@ -734,7 +660,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
734
660
|
task.addNote(error.message, TestResult.FAILED, actionResult.screenshotFile, actionResult.fullUrl || actionResult.url);
|
|
735
661
|
task.finish(TestResult.FAILED);
|
|
736
662
|
this.finishTest(task);
|
|
737
|
-
await this.
|
|
663
|
+
await this.testRun?.stop(this.buildStopTestMeta(task));
|
|
738
664
|
clearActivity(true);
|
|
739
665
|
return { success: false };
|
|
740
666
|
}
|
|
@@ -810,11 +736,13 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
810
736
|
- Before retrying your actions check maybe they already achived expected results. Use see() tool for that
|
|
811
737
|
- If the current URL is already a create/edit/new form and the scenario is about creating/editing that entity, fill and submit that form. Do not click the list-page "New" button again from inside the form.
|
|
812
738
|
- If the scenario is about search/filter/sort/tabs/list inspection and the current URL is a create/edit/new form, go back or reset to the stable list page before interacting with list controls.
|
|
813
|
-
- When selecting related entities from a list, do not choose rows/options/cards marked as "0 items", "0
|
|
814
|
-
- In selection pickers, counters such as "Selected 0", "Matched
|
|
739
|
+
- When selecting related entities from a list, do not choose rows/options/cards marked as "0 items", "0 results", or otherwise empty if the scenario requires selecting real content.
|
|
740
|
+
- In selection pickers, counters such as "Selected 0", "Matched 0", or disabled Save/Apply mean the selection did not register. Choose a non-empty item or change filters before submitting.
|
|
815
741
|
- A passed form/click command only means the command executed. If a required field remains empty, submit stays disabled, or the expected text is not visible, treat the action as not completed and correct the missing field/state.
|
|
816
|
-
- For filter/tab scenarios, success requires BOTH: the requested
|
|
817
|
-
-
|
|
742
|
+
- For filter/tab scenarios, success requires BOTH: the requested state is evidenced by a selected control, URL/query, or another explicit state indicator AND the list content matches that state. Do not finish from only one of these signals.
|
|
743
|
+
- Once the requested control state and matching content are both visible, finish the scenario instead of repeating the interaction. Do not require an aggregate count change unless a baseline was observed immediately before the action.
|
|
744
|
+
- Empty-state text such as "No matched items" only proves a filter when the requested filter state is explicit and the empty state belongs to the filtered list.
|
|
745
|
+
- Associate validation feedback with a field only through explicit evidence such as the field label in the message, an accessibility relationship, focus on the invalid control, or visual confirmation. Do not infer the affected field from DOM order or proximity alone.
|
|
818
746
|
- When filling complex form with lot of actions performed, use see() to look which fields were filled and which are not
|
|
819
747
|
- When verify() fails, use see() to visually confirm the result — visual confirmation is equally valid evidence
|
|
820
748
|
- For visual state verification (active tabs, selected items, counts, colors), prefer see() over DOM-based verify()
|
|
@@ -847,7 +775,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
847
775
|
|
|
848
776
|
${dataProtectionRules}
|
|
849
777
|
|
|
850
|
-
${this.provider.getSystemPromptForAgent('tester', this.
|
|
778
|
+
${this.provider.getSystemPromptForAgent('tester', this.stateManager.getCurrentState()?.url) || ''}
|
|
851
779
|
`;
|
|
852
780
|
}
|
|
853
781
|
|
|
@@ -875,10 +803,6 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
875
803
|
When creating or editing items via form() or type() you should include ${task.sessionName} in the value (if it is not restricted by the application logic)
|
|
876
804
|
Initial page URL: ${actionResult.url}
|
|
877
805
|
|
|
878
|
-
${capabilityGroundingRule}
|
|
879
|
-
|
|
880
|
-
${dataProtectionRules}
|
|
881
|
-
|
|
882
806
|
${this.buildDeletionScope(task)}
|
|
883
807
|
|
|
884
808
|
${this.buildAvailableFiles()}
|
|
@@ -898,7 +822,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
898
822
|
}
|
|
899
823
|
|
|
900
824
|
private buildAvailableFiles(): string {
|
|
901
|
-
const userFiles = this.
|
|
825
|
+
const userFiles = this.config.files || {};
|
|
902
826
|
const codeceptDir = (global as any).codecept_dir || process.cwd();
|
|
903
827
|
const lines: string[] = [];
|
|
904
828
|
|
|
@@ -956,11 +880,9 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
956
880
|
reason: z.string().optional().describe('Explanation why reset is the only option'),
|
|
957
881
|
}),
|
|
958
882
|
execute: async ({ reason }) => {
|
|
959
|
-
|
|
960
|
-
await this.explorer.switchToMainFrame();
|
|
961
|
-
}
|
|
883
|
+
await this.explorer.action().exitIframe();
|
|
962
884
|
|
|
963
|
-
const currentState = this.
|
|
885
|
+
const currentState = this.stateManager.getCurrentState();
|
|
964
886
|
const currentUrl = currentState?.fullUrl || currentState?.url;
|
|
965
887
|
if (currentUrl === resetUrl!) {
|
|
966
888
|
return {
|
|
@@ -988,7 +910,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
988
910
|
const explanation = reason ? `${reason} (RESET)` : 'Resetting to initial page';
|
|
989
911
|
const targetUrl = resetUrl!;
|
|
990
912
|
task.addNote(explanation);
|
|
991
|
-
const resetAction = this.explorer.
|
|
913
|
+
const resetAction = this.explorer.action();
|
|
992
914
|
const success = await resetAction.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, explanation);
|
|
993
915
|
|
|
994
916
|
if (success) {
|
|
@@ -1137,7 +1059,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
1137
1059
|
mappedStatus = TestResult.FAILED;
|
|
1138
1060
|
}
|
|
1139
1061
|
|
|
1140
|
-
const screenshotFile = this.
|
|
1062
|
+
const screenshotFile = this.stateManager.getCurrentState()?.screenshotFile;
|
|
1141
1063
|
|
|
1142
1064
|
for (const noteText of input.notes) {
|
|
1143
1065
|
task.addNote(noteText, mappedStatus, screenshotFile);
|
|
@@ -1178,7 +1100,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
1178
1100
|
const message = error instanceof Error ? error.message : String(error);
|
|
1179
1101
|
if (!task.hasFinished) task.addNote(`Execution error: ${message}`);
|
|
1180
1102
|
|
|
1181
|
-
const result = await this.explorer.
|
|
1103
|
+
const result = await this.explorer.recover(error);
|
|
1182
1104
|
tag('info').log(`Browser supervisor: ${result.action} - ${result.message}`);
|
|
1183
1105
|
task.addNote(result.message);
|
|
1184
1106
|
|
|
@@ -1213,7 +1135,7 @@ export class Tester extends TaskAgent implements Agent {
|
|
|
1213
1135
|
|
|
1214
1136
|
private async cleanupStartedTest(task: Test): Promise<void> {
|
|
1215
1137
|
await this.finishTest(task);
|
|
1216
|
-
await this.
|
|
1138
|
+
await this.testRun?.stop({
|
|
1217
1139
|
startUrl: task.startUrl,
|
|
1218
1140
|
style: task.style,
|
|
1219
1141
|
sessionName: task.sessionName,
|