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/conversation.ts
CHANGED
|
@@ -7,6 +7,12 @@ export interface ToolExecution {
|
|
|
7
7
|
wasSuccessful: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export function toToolExecution(toolName: string, input: any, rawOutput: any): ToolExecution {
|
|
11
|
+
let output = rawOutput;
|
|
12
|
+
if (rawOutput?.type === 'json' && rawOutput?.value) output = rawOutput.value;
|
|
13
|
+
return { toolName, input, output, wasSuccessful: output?.success !== false };
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
export function toolExecutionLabel(input: Record<string, any> | undefined): string {
|
|
11
17
|
return input?.explanation || input?.assertion || input?.reason || input?.request || '';
|
|
12
18
|
}
|
|
@@ -15,18 +21,14 @@ const AUTO_COMPACT_ARIA_CHANGES_CUTOFF = 500;
|
|
|
15
21
|
const AUTO_COMPACT_TARGETED_HTML_CUTOFF = 500;
|
|
16
22
|
|
|
17
23
|
export class Conversation {
|
|
18
|
-
id: string;
|
|
19
24
|
messages: ModelMessage[];
|
|
20
25
|
model: any;
|
|
21
|
-
telemetryFunctionId?: string;
|
|
22
26
|
protectedPrefixCount = 0;
|
|
23
27
|
private autoTrimRules: Map<string, number>;
|
|
24
28
|
|
|
25
|
-
constructor(messages: ModelMessage[] = [], model?: any
|
|
26
|
-
this.id = this.generateId();
|
|
29
|
+
constructor(messages: ModelMessage[] = [], model?: any) {
|
|
27
30
|
this.messages = messages;
|
|
28
31
|
this.model = model || '';
|
|
29
|
-
this.telemetryFunctionId = telemetryFunctionId;
|
|
30
32
|
this.autoTrimRules = new Map();
|
|
31
33
|
}
|
|
32
34
|
|
|
@@ -41,20 +43,6 @@ export class Conversation {
|
|
|
41
43
|
});
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
addUserImage(image: string): void {
|
|
45
|
-
if (!image || image.trim() === '') {
|
|
46
|
-
console.warn('Warning: Attempting to add empty image to conversation');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const imageData = image.startsWith('data:') ? image : `data:image/png;base64,${image}`;
|
|
51
|
-
|
|
52
|
-
this.messages.push({
|
|
53
|
-
role: 'user',
|
|
54
|
-
content: [{ type: 'file', mediaType: 'image/png', data: imageData }],
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
46
|
addAssistantText(text: string): void {
|
|
59
47
|
// Skip empty or whitespace-only messages
|
|
60
48
|
if (!text || text.trim() === '') {
|
|
@@ -83,7 +71,7 @@ export class Conversation {
|
|
|
83
71
|
}
|
|
84
72
|
|
|
85
73
|
clone(): Conversation {
|
|
86
|
-
return new Conversation([...this.messages], this.model
|
|
74
|
+
return new Conversation([...this.messages], this.model);
|
|
87
75
|
}
|
|
88
76
|
|
|
89
77
|
cleanupTag(tagName: string, replacement: string, keepLast = 0): void {
|
|
@@ -222,10 +210,6 @@ export class Conversation {
|
|
|
222
210
|
return result;
|
|
223
211
|
}
|
|
224
212
|
|
|
225
|
-
private generateId(): string {
|
|
226
|
-
return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
213
|
getToolExecutions(): ToolExecution[] {
|
|
230
214
|
const toolCalls = new Map<string, any>();
|
|
231
215
|
for (const message of this.messages) {
|
|
@@ -243,14 +227,7 @@ export class Conversation {
|
|
|
243
227
|
if (!Array.isArray(message.content)) continue;
|
|
244
228
|
for (const part of message.content) {
|
|
245
229
|
if (part.type !== 'tool-result') continue;
|
|
246
|
-
|
|
247
|
-
const output = rawOutput?.type === 'json' && rawOutput?.value ? rawOutput.value : rawOutput;
|
|
248
|
-
executions.push({
|
|
249
|
-
toolName: part.toolName,
|
|
250
|
-
input: toolCalls.get(part.toolCallId) || {},
|
|
251
|
-
output,
|
|
252
|
-
wasSuccessful: output?.success !== false,
|
|
253
|
-
});
|
|
230
|
+
executions.push(toToolExecution(part.toolName, toolCalls.get(part.toolCallId) || {}, part.output));
|
|
254
231
|
}
|
|
255
232
|
}
|
|
256
233
|
|
package/src/ai/driller.ts
CHANGED
|
@@ -3,13 +3,9 @@ import dedent from 'dedent';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { ActionResult } from '../action-result.ts';
|
|
5
5
|
import { setActivity } from '../activity.ts';
|
|
6
|
-
import type { ExperienceTracker } from '../experience-tracker.ts';
|
|
7
|
-
import type Explorer from '../explorer.ts';
|
|
8
|
-
import type { KnowledgeTracker } from '../knowledge-tracker.ts';
|
|
9
6
|
import { Observability } from '../observability.ts';
|
|
10
7
|
import { Plan, Test, TestResult } from '../test-plan.ts';
|
|
11
8
|
import { collectInteractiveNodes } from '../utils/aria.ts';
|
|
12
|
-
import { HooksRunner } from '../utils/hooks-runner.ts';
|
|
13
9
|
import {
|
|
14
10
|
EXPLORBOT_ATTRS,
|
|
15
11
|
HTML_COMPOSITE_AREA_HINTS,
|
|
@@ -26,12 +22,13 @@ import {
|
|
|
26
22
|
} from '../utils/html.ts';
|
|
27
23
|
import { createDebug, tag } from '../utils/logger.ts';
|
|
28
24
|
import { loop, pause } from '../utils/loop.ts';
|
|
25
|
+
import { annotatePageElements } from '../utils/web-annotate.ts';
|
|
26
|
+
import { eidxInContainer } from '../utils/web-eidx.ts';
|
|
29
27
|
import { WebElement } from '../utils/web-element.ts';
|
|
30
|
-
import type { Agent } from './agent.ts';
|
|
31
|
-
import type { Conversation } from './conversation.ts';
|
|
28
|
+
import type { Agent, AgentDeps } from './agent.ts';
|
|
32
29
|
import type { Navigator } from './navigator.ts';
|
|
33
30
|
import type { Provider } from './provider.ts';
|
|
34
|
-
import {
|
|
31
|
+
import { drillLocatorRule } from './rules.ts';
|
|
35
32
|
import { TaskAgent, isInteractive } from './task-agent.ts';
|
|
36
33
|
import { createCodeceptJSTools } from './tools.ts';
|
|
37
34
|
|
|
@@ -80,44 +77,25 @@ interface DrillOptions {
|
|
|
80
77
|
export class Driller extends TaskAgent implements Agent {
|
|
81
78
|
protected readonly ACTION_TOOLS = ['click', 'pressKey', 'form'];
|
|
82
79
|
emoji = 'D';
|
|
83
|
-
private explorer: Explorer;
|
|
84
|
-
private provider: Provider;
|
|
85
80
|
private navigator: Navigator;
|
|
86
|
-
private hooksRunner: HooksRunner;
|
|
87
81
|
private currentPlan?: Plan;
|
|
88
|
-
private currentConversation: Conversation | null = null;
|
|
89
82
|
private allResults: InteractionResult[] = [];
|
|
90
83
|
private verifiedAction: { componentId: string; toolName: string; code?: string; canonicalCode?: string } | null = null;
|
|
91
84
|
private pendingNestedContext: string | null = null;
|
|
92
85
|
|
|
93
86
|
MAX_COMPONENT_ITERATIONS = 12;
|
|
94
87
|
|
|
95
|
-
constructor(
|
|
96
|
-
super();
|
|
97
|
-
this.explorer = explorer;
|
|
98
|
-
this.provider = provider;
|
|
88
|
+
constructor(deps: AgentDeps, navigator: Navigator) {
|
|
89
|
+
super(deps);
|
|
99
90
|
this.navigator = navigator;
|
|
100
|
-
this.hooksRunner = new HooksRunner(explorer, explorer.getConfig());
|
|
101
91
|
}
|
|
102
92
|
|
|
103
93
|
protected getNavigator(): Navigator {
|
|
104
94
|
return this.navigator;
|
|
105
95
|
}
|
|
106
96
|
|
|
107
|
-
protected getExperienceTracker(): ExperienceTracker {
|
|
108
|
-
return this.explorer.getStateManager().getExperienceTracker();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
protected getKnowledgeTracker(): KnowledgeTracker {
|
|
112
|
-
return this.explorer.getKnowledgeTracker();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
protected getProvider(): Provider {
|
|
116
|
-
return this.provider;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
97
|
getSystemMessage(component?: ComponentInfo): string {
|
|
120
|
-
const currentUrl = this.
|
|
98
|
+
const currentUrl = this.stateManager.getCurrentState()?.url;
|
|
121
99
|
const customPrompt = this.provider.getSystemPromptForAgent('driller', currentUrl);
|
|
122
100
|
|
|
123
101
|
return dedent`
|
|
@@ -161,7 +139,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
161
139
|
|
|
162
140
|
async drill(opts: DrillOptions = {}): Promise<Plan> {
|
|
163
141
|
const { knowledgePath, maxComponents = 30, interactive = isInteractive() } = opts;
|
|
164
|
-
const currentState = this.
|
|
142
|
+
const currentState = this.stateManager.getCurrentState();
|
|
165
143
|
if (!currentState) throw new Error('No page state available');
|
|
166
144
|
|
|
167
145
|
const sessionName = `driller_${Date.now().toString(36)}`;
|
|
@@ -209,15 +187,15 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
209
187
|
|
|
210
188
|
private async captureAnnotatedState(): Promise<ActionResult> {
|
|
211
189
|
setActivity(`${this.emoji} Capturing annotated page state...`, 'action');
|
|
212
|
-
const action = this.explorer.
|
|
190
|
+
const action = this.explorer.action();
|
|
213
191
|
try {
|
|
214
192
|
const annotated = await Promise.race([
|
|
215
|
-
this.explorer.
|
|
193
|
+
this.explorer.withPage(annotatePageElements),
|
|
216
194
|
new Promise<never>((_, reject) => {
|
|
217
195
|
setTimeout(() => reject(new Error('annotateElements timeout')), 15000);
|
|
218
196
|
}),
|
|
219
197
|
]);
|
|
220
|
-
return action.capturePageState(
|
|
198
|
+
return action.capturePageState();
|
|
221
199
|
} catch (error) {
|
|
222
200
|
tag('warning').log(`Annotated capture failed, falling back to plain page state: ${error instanceof Error ? error.message : error}`);
|
|
223
201
|
return action.capturePageState();
|
|
@@ -228,9 +206,10 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
228
206
|
|
|
229
207
|
private async collectComponents(state: ActionResult, maxComponents: number): Promise<ComponentInfo[]> {
|
|
230
208
|
setActivity(`${this.emoji} Collecting components...`, 'action');
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
209
|
+
const webElements = await this.explorer.withPage(async (page) => {
|
|
210
|
+
const eidxList = await eidxInContainer(page, null);
|
|
211
|
+
return WebElement.fromEidxList(page, eidxList);
|
|
212
|
+
});
|
|
234
213
|
const ariaNodes = collectInteractiveNodes(state.ariaSnapshot);
|
|
235
214
|
const scored = webElements
|
|
236
215
|
.filter((element) => isDrillableElement(element))
|
|
@@ -323,11 +302,10 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
323
302
|
this.verifiedAction = null;
|
|
324
303
|
this.pendingNestedContext = null;
|
|
325
304
|
const conversation = this.provider.startConversation(this.getSystemMessage(component), 'driller');
|
|
326
|
-
this.currentConversation = conversation;
|
|
327
305
|
conversation.addUserText(await this.buildComponentPrompt(originalState, component));
|
|
328
306
|
|
|
329
307
|
let finished = false;
|
|
330
|
-
const actionTools = this.createVerifiedActionTools(createCodeceptJSTools(this.
|
|
308
|
+
const actionTools = this.createVerifiedActionTools(createCodeceptJSTools(this.toolDeps, test), component);
|
|
331
309
|
const tools = { ...actionTools, ...this.createDrillFlowTools(originalState, test, interactive) };
|
|
332
310
|
|
|
333
311
|
await loop(
|
|
@@ -336,7 +314,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
336
314
|
setActivity(`${this.emoji} Drilling ${component.name}...`, 'action');
|
|
337
315
|
|
|
338
316
|
if (iteration > 1) {
|
|
339
|
-
const currentState = ActionResult.fromState(this.
|
|
317
|
+
const currentState = ActionResult.fromState(this.stateManager.getCurrentState() || originalState);
|
|
340
318
|
conversation.addUserText(await this.buildContextUpdate(currentState, component));
|
|
341
319
|
if (this.pendingNestedContext) {
|
|
342
320
|
conversation.addUserText(this.pendingNestedContext);
|
|
@@ -580,7 +558,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
580
558
|
execute: async ({ reason }) => {
|
|
581
559
|
await this.restoreOriginalState(originalState, reason);
|
|
582
560
|
await this.captureAnnotatedState();
|
|
583
|
-
const currentState = this.
|
|
561
|
+
const currentState = this.stateManager.getCurrentState();
|
|
584
562
|
return { success: true, url: currentState?.url || originalState.url };
|
|
585
563
|
},
|
|
586
564
|
}),
|
|
@@ -601,16 +579,16 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
601
579
|
}
|
|
602
580
|
|
|
603
581
|
private async restoreOriginalState(originalState: ActionResult, reason: string): Promise<void> {
|
|
604
|
-
const currentState = this.
|
|
582
|
+
const currentState = this.stateManager.getCurrentState();
|
|
605
583
|
const targetUrl = originalState.fullUrl || originalState.url;
|
|
606
|
-
const action = this.explorer.
|
|
584
|
+
const action = this.explorer.action();
|
|
607
585
|
|
|
608
586
|
if (currentState?.url !== originalState.url) {
|
|
609
|
-
await action.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, `${reason} (restore URL)
|
|
587
|
+
await action.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, `${reason} (restore URL)`);
|
|
610
588
|
return;
|
|
611
589
|
}
|
|
612
590
|
|
|
613
|
-
await action.attempt('I.pressKey("Escape")', `${reason} (restore state)
|
|
591
|
+
await action.attempt('I.pressKey("Escape")', `${reason} (restore state)`);
|
|
614
592
|
}
|
|
615
593
|
|
|
616
594
|
private async saveToExperience(state: ActionResult, results: InteractionResult[]): Promise<void> {
|
|
@@ -668,7 +646,7 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
668
646
|
const overlayHtml = await this.getVisibleOverlayHtml();
|
|
669
647
|
if (!overlayHtml) return null;
|
|
670
648
|
|
|
671
|
-
const state = this.
|
|
649
|
+
const state = this.stateManager.getCurrentState();
|
|
672
650
|
if (!state) return null;
|
|
673
651
|
const currentState = ActionResult.fromState(state);
|
|
674
652
|
return dedent`
|
|
@@ -689,40 +667,42 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
689
667
|
}
|
|
690
668
|
|
|
691
669
|
private async getVisibleOverlayHtml(): Promise<string> {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
},
|
|
698
|
-
{
|
|
699
|
-
extractorSource: getVisibleOverlayHtmlExtractorSource(),
|
|
700
|
-
config: {
|
|
701
|
-
interactiveContentSelector: HTML_SELECTORS.interactiveContent,
|
|
702
|
-
limits: HTML_EXTRACTION_LIMITS,
|
|
703
|
-
overlaySelectors: HTML_SELECTORS.semanticOverlays,
|
|
704
|
-
visibilityLimits: HTML_VISIBILITY_LIMITS,
|
|
670
|
+
return this.explorer.withPage((page) =>
|
|
671
|
+
page.evaluate(
|
|
672
|
+
({ extractorSource, config }) => {
|
|
673
|
+
const extract = new Function(`return ${extractorSource}`)() as (config: any) => string;
|
|
674
|
+
return extract(config);
|
|
705
675
|
},
|
|
706
|
-
|
|
676
|
+
{
|
|
677
|
+
extractorSource: getVisibleOverlayHtmlExtractorSource(),
|
|
678
|
+
config: {
|
|
679
|
+
interactiveContentSelector: HTML_SELECTORS.interactiveContent,
|
|
680
|
+
limits: HTML_EXTRACTION_LIMITS,
|
|
681
|
+
overlaySelectors: HTML_SELECTORS.semanticOverlays,
|
|
682
|
+
visibilityLimits: HTML_VISIBILITY_LIMITS,
|
|
683
|
+
},
|
|
684
|
+
}
|
|
685
|
+
)
|
|
707
686
|
);
|
|
708
687
|
}
|
|
709
688
|
|
|
710
689
|
private async getComponentScopeHtml(component: ComponentInfo, originalState: ActionResult): Promise<string> {
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
eidx: component.eidx,
|
|
719
|
-
extractorSource: getComponentScopeHtmlExtractorSource(),
|
|
720
|
-
config: {
|
|
721
|
-
eidxAttr: EXPLORBOT_ATTRS.eidx,
|
|
722
|
-
interactiveControlSelector: HTML_SELECTORS.interactiveControl,
|
|
723
|
-
limits: HTML_EXTRACTION_LIMITS,
|
|
690
|
+
const scopedHtml = await this.explorer.withPage((page) =>
|
|
691
|
+
page.evaluate(
|
|
692
|
+
({ eidx, extractorSource, config }) => {
|
|
693
|
+
const extract = new Function(`return ${extractorSource}`)() as (eidx: string, config: any) => string;
|
|
694
|
+
return extract(eidx, config);
|
|
724
695
|
},
|
|
725
|
-
|
|
696
|
+
{
|
|
697
|
+
eidx: component.eidx,
|
|
698
|
+
extractorSource: getComponentScopeHtmlExtractorSource(),
|
|
699
|
+
config: {
|
|
700
|
+
eidxAttr: EXPLORBOT_ATTRS.eidx,
|
|
701
|
+
interactiveControlSelector: HTML_SELECTORS.interactiveControl,
|
|
702
|
+
limits: HTML_EXTRACTION_LIMITS,
|
|
703
|
+
},
|
|
704
|
+
}
|
|
705
|
+
)
|
|
726
706
|
);
|
|
727
707
|
|
|
728
708
|
if (scopedHtml) return scopedHtml;
|
|
@@ -789,14 +769,6 @@ export class Driller extends TaskAgent implements Agent {
|
|
|
789
769
|
tag('step').log(` ${status} ${componentTest.component?.name || test.scenario}`);
|
|
790
770
|
}
|
|
791
771
|
}
|
|
792
|
-
|
|
793
|
-
getCurrentPlan(): Plan | undefined {
|
|
794
|
-
return this.currentPlan;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
getConversation(): Conversation | null {
|
|
798
|
-
return this.currentConversation;
|
|
799
|
-
}
|
|
800
772
|
}
|
|
801
773
|
|
|
802
774
|
function formatAriaNode(node: Record<string, unknown>): string {
|
|
@@ -1190,5 +1162,3 @@ function isInteractiveElement(element: WebElement): boolean {
|
|
|
1190
1162
|
if (element.attrs['aria-haspopup'] || element.attrs['aria-expanded'] || element.attrs['aria-controls']) return true;
|
|
1191
1163
|
return false;
|
|
1192
1164
|
}
|
|
1193
|
-
|
|
1194
|
-
const drillLocatorRule = locatorRule.replace(/<context_simplification>[\s\S]*?<\/context_simplification>/, '').trim();
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { unlinkSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
2
3
|
import dedent from 'dedent';
|
|
3
|
-
import { type Tokens, marked } from 'marked';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { type ExperienceFile, type ExperienceTracker, RECENT_WINDOW_DAYS } from '../experience-tracker.js';
|
|
6
6
|
import { Observability } from '../observability.js';
|
|
7
7
|
import { createDebug, log, tag } from '../utils/logger.js';
|
|
8
8
|
import { mdq } from '../utils/markdown-query.js';
|
|
9
|
+
import { isNonReusableCode } from '../utils/step-analyzer.js';
|
|
9
10
|
import { generalizeUrl, hasDynamicUrlSegment } from '../utils/url-matcher.js';
|
|
10
11
|
import type { Agent } from './agent.js';
|
|
11
12
|
import type { Provider } from './provider.js';
|
|
12
13
|
|
|
13
14
|
const debugLog = createDebug('explorbot:experience-compactor');
|
|
14
15
|
|
|
15
|
-
export
|
|
16
|
+
export const COMPACT_MAX_LENGTH = 5000;
|
|
16
17
|
|
|
17
18
|
interface MergeGroup {
|
|
18
19
|
pattern: string;
|
|
@@ -23,7 +24,6 @@ export class ExperienceCompactor implements Agent {
|
|
|
23
24
|
emoji = '🗜️';
|
|
24
25
|
private provider: Provider;
|
|
25
26
|
private experienceTracker: ExperienceTracker;
|
|
26
|
-
private MAX_LENGTH = 5000;
|
|
27
27
|
|
|
28
28
|
constructor(provider: Provider, experienceTracker: ExperienceTracker) {
|
|
29
29
|
this.provider = provider;
|
|
@@ -32,7 +32,7 @@ export class ExperienceCompactor implements Agent {
|
|
|
32
32
|
|
|
33
33
|
async compactExperience(experience: string): Promise<string> {
|
|
34
34
|
const stripped = this.stripNonUsefulEntries(experience);
|
|
35
|
-
if (stripped.length <
|
|
35
|
+
if (stripped.length < COMPACT_MAX_LENGTH) {
|
|
36
36
|
return stripped;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -129,7 +129,7 @@ export class ExperienceCompactor implements Agent {
|
|
|
129
129
|
if (!url || url.startsWith('~')) continue;
|
|
130
130
|
const generalized = generalizeUrl(url);
|
|
131
131
|
if (generalized === url) continue;
|
|
132
|
-
const stateHash = file.filePath
|
|
132
|
+
const stateHash = basename(file.filePath, '.md');
|
|
133
133
|
const newData = { ...file.data, url: `~${generalized}~`, mergedFrom: [url] };
|
|
134
134
|
this.experienceTracker.writeExperienceFile(stateHash, file.content, newData);
|
|
135
135
|
tag('substep').log(`Generalized URL: ${url} → ~${generalized}~`);
|
|
@@ -139,7 +139,7 @@ export class ExperienceCompactor implements Agent {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
async compactFiles(files: ExperienceFile[], options?: { skipSmall?: boolean }): Promise<number> {
|
|
142
|
-
const workingSet = options?.skipSmall ? files.filter((f) => f.content.length >=
|
|
142
|
+
const workingSet = options?.skipSmall ? files.filter((f) => f.content.length >= COMPACT_MAX_LENGTH) : files;
|
|
143
143
|
|
|
144
144
|
let compactedCount = 0;
|
|
145
145
|
const total = workingSet.length;
|
|
@@ -151,7 +151,7 @@ export class ExperienceCompactor implements Agent {
|
|
|
151
151
|
|
|
152
152
|
for (let i = 0; i < workingSet.length; i++) {
|
|
153
153
|
const experience = workingSet[i];
|
|
154
|
-
const shortName = experience.filePath
|
|
154
|
+
const shortName = basename(experience.filePath);
|
|
155
155
|
const willReview = this.isRecent(experience);
|
|
156
156
|
|
|
157
157
|
if (total > 1 && willReview) {
|
|
@@ -164,8 +164,8 @@ export class ExperienceCompactor implements Agent {
|
|
|
164
164
|
content = await this.reviewExperienceQuality(content, experience.data);
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
if (content.length >=
|
|
168
|
-
if (total > 1) tag('substep').log(`[${i + 1}/${total}] compacting ${shortName} (over ${
|
|
167
|
+
if (content.length >= COMPACT_MAX_LENGTH) {
|
|
168
|
+
if (total > 1) tag('substep').log(`[${i + 1}/${total}] compacting ${shortName} (over ${COMPACT_MAX_LENGTH} chars)`);
|
|
169
169
|
const prompt = this.buildCompactionPrompt(content);
|
|
170
170
|
const model = this.provider.getModelForAgent('experience-compactor');
|
|
171
171
|
const response = await this.provider.chat(
|
|
@@ -181,7 +181,7 @@ export class ExperienceCompactor implements Agent {
|
|
|
181
181
|
|
|
182
182
|
if (content === experience.content) continue;
|
|
183
183
|
|
|
184
|
-
const stateHash = experience.filePath
|
|
184
|
+
const stateHash = basename(experience.filePath, '.md');
|
|
185
185
|
this.experienceTracker.writeExperienceFile(stateHash, content, experience.data);
|
|
186
186
|
debugLog('Experience file compacted:', experience.filePath);
|
|
187
187
|
compactedCount++;
|
|
@@ -314,7 +314,7 @@ export class ExperienceCompactor implements Agent {
|
|
|
314
314
|
const [targetFile, ...sourceFiles] = group.files;
|
|
315
315
|
const combinedContent = group.files.map((f) => f.content).join('\n\n---\n\n');
|
|
316
316
|
|
|
317
|
-
const targetStateHash = targetFile.filePath
|
|
317
|
+
const targetStateHash = basename(targetFile.filePath, '.md');
|
|
318
318
|
const newFrontmatter = {
|
|
319
319
|
...targetFile.data,
|
|
320
320
|
url: group.pattern,
|
|
@@ -350,7 +350,7 @@ export class ExperienceCompactor implements Agent {
|
|
|
350
350
|
<rules>
|
|
351
351
|
- Use markdown h2 headers only (##) - NO XML tags or wrappers in output
|
|
352
352
|
- Merge similar flows to remove duplicates
|
|
353
|
-
- Keep output under ${
|
|
353
|
+
- Keep output under ${COMPACT_MAX_LENGTH} characters
|
|
354
354
|
- Be explicit and short - no proposals or explanations
|
|
355
355
|
|
|
356
356
|
KEEP only:
|
|
@@ -438,28 +438,9 @@ export class ExperienceCompactor implements Agent {
|
|
|
438
438
|
}
|
|
439
439
|
|
|
440
440
|
function listSections(content: string): { title: string; raw: string }[] {
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
let currentHeading: string | null = null;
|
|
445
|
-
let currentRaw = '';
|
|
446
|
-
|
|
447
|
-
const flush = () => {
|
|
448
|
-
if (currentHeading !== null) sections.push({ title: currentHeading, raw: currentRaw });
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
for (const token of tokens) {
|
|
452
|
-
const raw = (token as any).raw || '';
|
|
453
|
-
if (token.type === 'heading' && (token as Tokens.Heading).depth === 2) {
|
|
454
|
-
flush();
|
|
455
|
-
currentHeading = (token as Tokens.Heading).text.trim();
|
|
456
|
-
currentRaw = raw;
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
if (currentHeading !== null) currentRaw += raw;
|
|
460
|
-
}
|
|
461
|
-
flush();
|
|
462
|
-
return sections;
|
|
441
|
+
const sections = mdq(content).query('section2').each();
|
|
442
|
+
const metas = mdq(content).query('section2').meta();
|
|
443
|
+
return sections.map((q, i) => ({ title: metas[i].text.trim(), raw: q.text() }));
|
|
463
444
|
}
|
|
464
445
|
|
|
465
446
|
function dropEmptySections(content: string): string {
|
|
@@ -483,7 +464,7 @@ function dropNonReusableSections(content: string): string {
|
|
|
483
464
|
|
|
484
465
|
for (const section of sections) {
|
|
485
466
|
const raw = section.text();
|
|
486
|
-
if (
|
|
467
|
+
if (!isNonReusableCode(raw)) continue;
|
|
487
468
|
result = result.replace(raw, '');
|
|
488
469
|
}
|
|
489
470
|
|
|
@@ -158,7 +158,7 @@ function extractKeyFields(body: any, result: Record<string, any> = {}, depth = 0
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
for (const [key, value] of Object.entries(body)) {
|
|
161
|
-
if (value
|
|
161
|
+
if (value == null) continue;
|
|
162
162
|
if (typeof value === 'object') {
|
|
163
163
|
extractKeyFields(value, result, depth + 1);
|
|
164
164
|
continue;
|
package/src/ai/fisherman.ts
CHANGED
|
@@ -44,10 +44,6 @@ export class Fisherman implements Agent {
|
|
|
44
44
|
return this.mode !== 'disabled';
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
getMode(): string {
|
|
48
|
-
return this.mode;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
47
|
async ensureReady(scopeUrl?: string): Promise<void> {
|
|
52
48
|
await this.detectMode(scopeUrl);
|
|
53
49
|
this.spec ??= await this.specLoader();
|
|
@@ -2,12 +2,12 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ActionResult } from '../../action-result.ts';
|
|
4
4
|
import { ConfigParser } from '../../config.ts';
|
|
5
|
-
import {
|
|
5
|
+
import type { StateManager } from '../../state-manager.ts';
|
|
6
6
|
import type { Plan } from '../../test-plan.ts';
|
|
7
7
|
import { tag } from '../../utils/logger.ts';
|
|
8
8
|
import { relativeToCwd } from '../../utils/next-steps.ts';
|
|
9
|
-
import { safeFilename } from '../../utils/strings.ts';
|
|
10
9
|
import { CODECEPT_TOOLS, isNonReusableCode, stripComments } from '../../utils/step-analyzer.ts';
|
|
10
|
+
import { safeFilename } from '../../utils/strings.ts';
|
|
11
11
|
import type { Conversation } from '../conversation.ts';
|
|
12
12
|
import { ASSERTION_TOOLS } from '../tools.ts';
|
|
13
13
|
import type { Constructor } from './mixin.ts';
|
|
@@ -21,6 +21,7 @@ export interface CodeceptJSMethods {
|
|
|
21
21
|
export function WithCodeceptJS<T extends Constructor>(Base: T) {
|
|
22
22
|
return class extends Base {
|
|
23
23
|
declare savedFiles: Set<string>;
|
|
24
|
+
declare stateManager?: StateManager;
|
|
24
25
|
|
|
25
26
|
toCode(conversation: Conversation, scenario: string): string {
|
|
26
27
|
const toolExecutions = conversation.getToolExecutions();
|
|
@@ -108,7 +109,8 @@ export function WithCodeceptJS<T extends Constructor>(Base: T) {
|
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
private getKnowledgeLines(url: string, indent = ' '): string[] {
|
|
111
|
-
const knowledgeTracker =
|
|
112
|
+
const knowledgeTracker = this.stateManager?.getKnowledgeTracker();
|
|
113
|
+
if (!knowledgeTracker) return [];
|
|
112
114
|
const state = new ActionResult({ url });
|
|
113
115
|
const { wait, waitForElement, code } = knowledgeTracker.getStateParameters(state, ['wait', 'waitForElement', 'code']);
|
|
114
116
|
|
|
@@ -2,13 +2,14 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ActionResult } from '../../action-result.ts';
|
|
4
4
|
import { ConfigParser } from '../../config.ts';
|
|
5
|
-
import
|
|
5
|
+
import type Explorer from '../../explorer.ts';
|
|
6
6
|
import { type PlaywrightRecorder, type TraceCall, renderAssertion, renderCall } from '../../playwright-recorder.ts';
|
|
7
|
+
import type { StateManager } from '../../state-manager.ts';
|
|
7
8
|
import type { Plan } from '../../test-plan.ts';
|
|
8
9
|
import { tag } from '../../utils/logger.ts';
|
|
9
10
|
import { relativeToCwd } from '../../utils/next-steps.ts';
|
|
10
|
-
import { safeFilename } from '../../utils/strings.ts';
|
|
11
11
|
import { CODECEPT_TOOLS } from '../../utils/step-analyzer.ts';
|
|
12
|
+
import { safeFilename } from '../../utils/strings.ts';
|
|
12
13
|
import type { Conversation } from '../conversation.ts';
|
|
13
14
|
import { ASSERTION_TOOLS } from '../tools.ts';
|
|
14
15
|
import type { Constructor } from './mixin.ts';
|
|
@@ -23,8 +24,9 @@ export interface PlaywrightMethods {
|
|
|
23
24
|
|
|
24
25
|
export function WithPlaywright<T extends Constructor>(Base: T) {
|
|
25
26
|
return class extends Base {
|
|
26
|
-
declare playwright: { recorder: PlaywrightRecorder;
|
|
27
|
+
declare playwright: { recorder: PlaywrightRecorder; explorer: Explorer } | undefined;
|
|
27
28
|
declare savedFiles: Set<string>;
|
|
29
|
+
declare stateManager?: StateManager;
|
|
28
30
|
|
|
29
31
|
async toPlaywrightCode(conversation: Conversation, scenario: string): Promise<string> {
|
|
30
32
|
const toolExecutions = conversation.getToolExecutions();
|
|
@@ -146,7 +148,8 @@ export function WithPlaywright<T extends Constructor>(Base: T) {
|
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
private getPlaywrightKnowledgeLines(url: string, indent = ' '): string[] {
|
|
149
|
-
const knowledgeTracker =
|
|
151
|
+
const knowledgeTracker = this.stateManager?.getKnowledgeTracker();
|
|
152
|
+
if (!knowledgeTracker) return [];
|
|
150
153
|
const state = new ActionResult({ url });
|
|
151
154
|
const { wait, waitForElement } = knowledgeTracker.getStateParameters(state, ['wait', 'waitForElement']);
|
|
152
155
|
|
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import * as codeceptjs from 'codeceptjs';
|
|
5
5
|
import { outputPath } from '../../config.ts';
|
|
6
6
|
import type { ExplorbotConfig } from '../../config.ts';
|
|
7
|
+
import type Explorer from '../../explorer.ts';
|
|
7
8
|
import type { PlaywrightRecorder } from '../../playwright-recorder.ts';
|
|
8
9
|
import { tag } from '../../utils/logger.ts';
|
|
9
10
|
import { relativeToCwd } from '../../utils/next-steps.ts';
|
|
@@ -22,7 +23,7 @@ export function WithScreencast<T extends Constructor>(Base: T) {
|
|
|
22
23
|
return class extends Base {
|
|
23
24
|
declare config: ExplorbotConfig | undefined;
|
|
24
25
|
declare savedFiles: Set<string>;
|
|
25
|
-
declare playwright: { recorder: PlaywrightRecorder;
|
|
26
|
+
declare playwright: { recorder: PlaywrightRecorder; explorer: Explorer } | undefined;
|
|
26
27
|
|
|
27
28
|
private screencastPage: any = null;
|
|
28
29
|
private screencastActive = false;
|
|
@@ -41,7 +42,7 @@ export function WithScreencast<T extends Constructor>(Base: T) {
|
|
|
41
42
|
attachScreencast(): void {
|
|
42
43
|
if (this.screencastListenersInstalled) return;
|
|
43
44
|
if (!this.config?.ai?.agents?.historian?.screencast) return;
|
|
44
|
-
if (!this.playwright?.
|
|
45
|
+
if (!this.playwright?.explorer) return;
|
|
45
46
|
|
|
46
47
|
this.onTestBefore = (test: any) => {
|
|
47
48
|
void this.startScreencast(test);
|
|
@@ -62,7 +63,7 @@ export function WithScreencast<T extends Constructor>(Base: T) {
|
|
|
62
63
|
|
|
63
64
|
private async startScreencast(test: any): Promise<void> {
|
|
64
65
|
if (this.screencastActive) return;
|
|
65
|
-
const page = this.playwright?.
|
|
66
|
+
const page = this.playwright?.explorer?.page;
|
|
66
67
|
if (!page?.screencast?.start) return;
|
|
67
68
|
|
|
68
69
|
const task = test?._explorbotTest;
|