@browserflow-ai/exploration 0.0.6
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/dist/adapters/claude-cli.d.ts +57 -0
- package/dist/adapters/claude-cli.d.ts.map +1 -0
- package/dist/adapters/claude-cli.js +195 -0
- package/dist/adapters/claude-cli.js.map +1 -0
- package/dist/adapters/claude.d.ts +54 -0
- package/dist/adapters/claude.d.ts.map +1 -0
- package/dist/adapters/claude.js +160 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/index.d.ts +6 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/types.d.ts +196 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +3 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/agent-browser-session.d.ts +62 -0
- package/dist/agent-browser-session.d.ts.map +1 -0
- package/dist/agent-browser-session.js +272 -0
- package/dist/agent-browser-session.js.map +1 -0
- package/dist/evidence.d.ts +111 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +144 -0
- package/dist/evidence.js.map +1 -0
- package/dist/explorer.d.ts +180 -0
- package/dist/explorer.d.ts.map +1 -0
- package/dist/explorer.js +393 -0
- package/dist/explorer.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/locator-candidates.d.ts +127 -0
- package/dist/locator-candidates.d.ts.map +1 -0
- package/dist/locator-candidates.js +358 -0
- package/dist/locator-candidates.js.map +1 -0
- package/dist/step-executor.d.ts +99 -0
- package/dist/step-executor.d.ts.map +1 -0
- package/dist/step-executor.js +646 -0
- package/dist/step-executor.js.map +1 -0
- package/package.json +34 -0
- package/src/adapters/claude-cli.test.ts +134 -0
- package/src/adapters/claude-cli.ts +240 -0
- package/src/adapters/claude.test.ts +195 -0
- package/src/adapters/claude.ts +190 -0
- package/src/adapters/index.ts +21 -0
- package/src/adapters/types.ts +207 -0
- package/src/agent-browser-session.test.ts +369 -0
- package/src/agent-browser-session.ts +349 -0
- package/src/evidence.test.ts +239 -0
- package/src/evidence.ts +203 -0
- package/src/explorer.test.ts +321 -0
- package/src/explorer.ts +565 -0
- package/src/index.ts +51 -0
- package/src/locator-candidates.test.ts +602 -0
- package/src/locator-candidates.ts +441 -0
- package/src/step-executor.test.ts +696 -0
- package/src/step-executor.ts +783 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// @browserflow-ai/exploration - Claude AI Adapter
|
|
2
|
+
|
|
3
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
4
|
+
import type {
|
|
5
|
+
AIAdapter,
|
|
6
|
+
ExploreParams,
|
|
7
|
+
ExplorationOutput,
|
|
8
|
+
RetryParams,
|
|
9
|
+
EnhancedSnapshot,
|
|
10
|
+
FindElementResult,
|
|
11
|
+
} from './types';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Configuration options for the Claude adapter
|
|
15
|
+
*/
|
|
16
|
+
export interface ClaudeAdapterConfig {
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
model?: string;
|
|
19
|
+
maxTokens?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* System prompt for element finding
|
|
24
|
+
*/
|
|
25
|
+
const ELEMENT_FINDER_SYSTEM_PROMPT = `You are helping find UI elements based on natural language descriptions.
|
|
26
|
+
Given an accessibility snapshot of a web page, identify the element that best matches the user's description.
|
|
27
|
+
|
|
28
|
+
Rules:
|
|
29
|
+
1. Return the ref (like "e1", "e2") of the matching element using the select_element tool
|
|
30
|
+
2. If multiple elements could match, pick the most likely based on context
|
|
31
|
+
3. If no element matches, use ref "NOT_FOUND" with confidence 0
|
|
32
|
+
4. Consider the element's role, name, text content, and position in the hierarchy
|
|
33
|
+
5. Be precise - prefer exact matches over partial matches`;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Tool definition for structured element selection
|
|
37
|
+
*/
|
|
38
|
+
const SELECT_ELEMENT_TOOL: Anthropic.Tool = {
|
|
39
|
+
name: 'select_element',
|
|
40
|
+
description: 'Select an element by its ref from the page snapshot',
|
|
41
|
+
input_schema: {
|
|
42
|
+
type: 'object' as const,
|
|
43
|
+
properties: {
|
|
44
|
+
ref: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'Element ref like e1, e2, or NOT_FOUND if no match',
|
|
47
|
+
},
|
|
48
|
+
confidence: {
|
|
49
|
+
type: 'number',
|
|
50
|
+
description: 'Confidence score 0-1 for the selection',
|
|
51
|
+
},
|
|
52
|
+
reasoning: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'Explanation for why this element was selected',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
required: ['ref', 'confidence', 'reasoning'],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Claude adapter for AI-powered browser exploration
|
|
63
|
+
*
|
|
64
|
+
* Uses Claude's vision and reasoning capabilities to:
|
|
65
|
+
* - Interpret spec steps
|
|
66
|
+
* - Identify elements in browser snapshots
|
|
67
|
+
* - Execute exploration workflows
|
|
68
|
+
*/
|
|
69
|
+
export class ClaudeAdapter implements AIAdapter {
|
|
70
|
+
readonly name = 'claude';
|
|
71
|
+
|
|
72
|
+
private client: Anthropic;
|
|
73
|
+
private model: string;
|
|
74
|
+
private maxTokens: number;
|
|
75
|
+
|
|
76
|
+
constructor(config: ClaudeAdapterConfig = {}) {
|
|
77
|
+
this.client = new Anthropic({
|
|
78
|
+
apiKey: config.apiKey,
|
|
79
|
+
});
|
|
80
|
+
this.model = config.model ?? 'claude-haiku-4-5';
|
|
81
|
+
this.maxTokens = config.maxTokens ?? 8192;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Find element from natural language query using Claude
|
|
86
|
+
*
|
|
87
|
+
* @param query - Natural language description of the element
|
|
88
|
+
* @param snapshot - Browser snapshot with element tree and refs
|
|
89
|
+
* @returns Promise resolving to element ref with reasoning
|
|
90
|
+
*/
|
|
91
|
+
async findElement(query: string, snapshot: EnhancedSnapshot): Promise<FindElementResult> {
|
|
92
|
+
const userMessage = `Find the element matching this description: "${query}"
|
|
93
|
+
|
|
94
|
+
Current page snapshot:
|
|
95
|
+
${snapshot.tree}
|
|
96
|
+
|
|
97
|
+
Available refs: ${Object.keys(snapshot.refs).join(', ')}
|
|
98
|
+
|
|
99
|
+
Use the select_element tool to return the ref of the best matching element.`;
|
|
100
|
+
|
|
101
|
+
const response = await this.client.messages.create({
|
|
102
|
+
model: this.model,
|
|
103
|
+
max_tokens: 1024,
|
|
104
|
+
system: ELEMENT_FINDER_SYSTEM_PROMPT,
|
|
105
|
+
tools: [SELECT_ELEMENT_TOOL],
|
|
106
|
+
tool_choice: { type: 'tool', name: 'select_element' },
|
|
107
|
+
messages: [
|
|
108
|
+
{
|
|
109
|
+
role: 'user',
|
|
110
|
+
content: userMessage,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Handle tool_use response (preferred)
|
|
116
|
+
for (const block of response.content) {
|
|
117
|
+
if (block.type === 'tool_use' && block.name === 'select_element') {
|
|
118
|
+
const input = block.input as { ref: string; confidence: number; reasoning: string };
|
|
119
|
+
return {
|
|
120
|
+
ref: input.ref,
|
|
121
|
+
confidence: input.confidence,
|
|
122
|
+
reasoning: input.reasoning,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback: parse text response if no tool_use
|
|
128
|
+
for (const block of response.content) {
|
|
129
|
+
if (block.type === 'text') {
|
|
130
|
+
const refMatch = block.text.match(/\be(\d+)\b/);
|
|
131
|
+
if (refMatch) {
|
|
132
|
+
return {
|
|
133
|
+
ref: refMatch[0],
|
|
134
|
+
confidence: 0.7, // Lower confidence for text extraction
|
|
135
|
+
reasoning: block.text,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// No element found
|
|
142
|
+
return {
|
|
143
|
+
ref: 'NOT_FOUND',
|
|
144
|
+
confidence: 0,
|
|
145
|
+
reasoning: 'Could not extract element ref from response',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Run exploration on a spec using Claude
|
|
151
|
+
*
|
|
152
|
+
* Note: The primary AI method is findElement(). This explore() method is a stub
|
|
153
|
+
* that returns a minimal valid structure. Full exploration is orchestrated by
|
|
154
|
+
* the Explorer class which uses findElement() for AI-powered element discovery.
|
|
155
|
+
*
|
|
156
|
+
* @param params - Exploration parameters including spec and browser config
|
|
157
|
+
* @returns Promise resolving to exploration output
|
|
158
|
+
*/
|
|
159
|
+
async explore(params: ExploreParams): Promise<ExplorationOutput> {
|
|
160
|
+
const explorationId = `exp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
spec: params.spec.name,
|
|
164
|
+
specPath: params.specPath,
|
|
165
|
+
explorationId,
|
|
166
|
+
timestamp: new Date().toISOString(),
|
|
167
|
+
durationMs: 0,
|
|
168
|
+
browser: params.browser ?? 'chromium',
|
|
169
|
+
viewport: params.viewport ?? { width: 1280, height: 720 },
|
|
170
|
+
baseUrl: params.baseUrl,
|
|
171
|
+
steps: [],
|
|
172
|
+
outcomeChecks: [],
|
|
173
|
+
overallStatus: 'completed',
|
|
174
|
+
errors: [],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Retry exploration with review feedback
|
|
180
|
+
*
|
|
181
|
+
* Note: Currently delegates to explore(). Future enhancement could use
|
|
182
|
+
* review feedback to improve element selection accuracy.
|
|
183
|
+
*
|
|
184
|
+
* @param params - Retry parameters including previous exploration and feedback
|
|
185
|
+
* @returns Promise resolving to new exploration output
|
|
186
|
+
*/
|
|
187
|
+
async retryWithFeedback(params: RetryParams): Promise<ExplorationOutput> {
|
|
188
|
+
return this.explore(params);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// @browserflow-ai/exploration - Adapter exports
|
|
2
|
+
|
|
3
|
+
export { ClaudeAdapter } from './claude';
|
|
4
|
+
export type { ClaudeAdapterConfig } from './claude';
|
|
5
|
+
|
|
6
|
+
export { ClaudeCliAdapter } from './claude-cli';
|
|
7
|
+
export type { ClaudeCliAdapterConfig } from './claude-cli';
|
|
8
|
+
|
|
9
|
+
export type {
|
|
10
|
+
AIAdapter,
|
|
11
|
+
ExploreParams,
|
|
12
|
+
ExplorationOutput,
|
|
13
|
+
RetryParams,
|
|
14
|
+
ReviewFeedback,
|
|
15
|
+
Spec,
|
|
16
|
+
SpecStep,
|
|
17
|
+
StepResult,
|
|
18
|
+
StepExecution,
|
|
19
|
+
StepScreenshots,
|
|
20
|
+
OutcomeCheck,
|
|
21
|
+
} from './types';
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// @browserflow-ai/exploration - Adapter types
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a step execution result from AI exploration
|
|
5
|
+
*/
|
|
6
|
+
export interface StepExecution {
|
|
7
|
+
status: 'completed' | 'failed' | 'skipped';
|
|
8
|
+
method: string;
|
|
9
|
+
elementRef?: string;
|
|
10
|
+
selectorUsed?: string;
|
|
11
|
+
durationMs: number;
|
|
12
|
+
error?: string | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Screenshots captured during a step
|
|
17
|
+
*/
|
|
18
|
+
export interface StepScreenshots {
|
|
19
|
+
before: string;
|
|
20
|
+
after: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Result of executing a single step
|
|
25
|
+
*/
|
|
26
|
+
export interface StepResult {
|
|
27
|
+
stepIndex: number;
|
|
28
|
+
specAction: Record<string, unknown>;
|
|
29
|
+
execution: StepExecution;
|
|
30
|
+
screenshots: StepScreenshots;
|
|
31
|
+
snapshotBefore?: Record<string, unknown>;
|
|
32
|
+
snapshotAfter?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Outcome check result
|
|
37
|
+
*/
|
|
38
|
+
export interface OutcomeCheck {
|
|
39
|
+
check: string;
|
|
40
|
+
expected: unknown;
|
|
41
|
+
actual: unknown;
|
|
42
|
+
passed: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Complete output from an exploration run
|
|
47
|
+
*/
|
|
48
|
+
export interface ExplorationOutput {
|
|
49
|
+
spec: string;
|
|
50
|
+
specPath: string;
|
|
51
|
+
specDescription?: string; // Spec-level description for UI display
|
|
52
|
+
explorationId: string;
|
|
53
|
+
timestamp: string;
|
|
54
|
+
durationMs: number;
|
|
55
|
+
browser: string;
|
|
56
|
+
viewport: { width: number; height: number };
|
|
57
|
+
baseUrl: string;
|
|
58
|
+
steps: StepResult[];
|
|
59
|
+
outcomeChecks: OutcomeCheck[];
|
|
60
|
+
overallStatus: 'completed' | 'failed' | 'timeout';
|
|
61
|
+
errors: string[];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Verify check conditions
|
|
66
|
+
*/
|
|
67
|
+
export interface VerifyCheck {
|
|
68
|
+
element_visible?: string;
|
|
69
|
+
element_not_visible?: string;
|
|
70
|
+
text_contains?: string;
|
|
71
|
+
text_not_contains?: string;
|
|
72
|
+
url_contains?: string;
|
|
73
|
+
element_count?: {
|
|
74
|
+
selector: string;
|
|
75
|
+
expected: number;
|
|
76
|
+
};
|
|
77
|
+
attribute?: {
|
|
78
|
+
selector: string;
|
|
79
|
+
attribute: string;
|
|
80
|
+
equals: string;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Step definition from a spec file
|
|
86
|
+
*/
|
|
87
|
+
export interface SpecStep {
|
|
88
|
+
action: string;
|
|
89
|
+
query?: string;
|
|
90
|
+
selector?: string;
|
|
91
|
+
ref?: string;
|
|
92
|
+
url?: string; // Canonical field for navigate action
|
|
93
|
+
to?: string; // Legacy field for navigate action (backward compat)
|
|
94
|
+
value?: string;
|
|
95
|
+
for?: string;
|
|
96
|
+
text?: string;
|
|
97
|
+
contains?: string;
|
|
98
|
+
timeout?: string | number;
|
|
99
|
+
duration?: string | number;
|
|
100
|
+
checks?: VerifyCheck[];
|
|
101
|
+
name?: string; // 1-4 word display name for UI
|
|
102
|
+
description?: string;
|
|
103
|
+
why?: string; // Rationale for this step
|
|
104
|
+
option?: string;
|
|
105
|
+
checked?: boolean;
|
|
106
|
+
scrollX?: number;
|
|
107
|
+
scrollY?: number;
|
|
108
|
+
[key: string]: unknown;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Spec file structure
|
|
113
|
+
*/
|
|
114
|
+
export interface Spec {
|
|
115
|
+
name: string;
|
|
116
|
+
description?: string;
|
|
117
|
+
preconditions?: Record<string, unknown>;
|
|
118
|
+
steps: SpecStep[];
|
|
119
|
+
expectedOutcomes?: Record<string, unknown>[];
|
|
120
|
+
tags?: string[];
|
|
121
|
+
timeout?: string;
|
|
122
|
+
priority?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Parameters for running an exploration
|
|
127
|
+
*/
|
|
128
|
+
export interface ExploreParams {
|
|
129
|
+
spec: Spec;
|
|
130
|
+
specPath: string;
|
|
131
|
+
baseUrl: string;
|
|
132
|
+
browser?: 'chromium' | 'firefox' | 'webkit';
|
|
133
|
+
viewport?: { width: number; height: number };
|
|
134
|
+
timeout?: number;
|
|
135
|
+
outputDir: string;
|
|
136
|
+
sessionId?: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Parameters for retrying with feedback
|
|
141
|
+
*/
|
|
142
|
+
export interface RetryParams extends ExploreParams {
|
|
143
|
+
previousExploration: ExplorationOutput;
|
|
144
|
+
reviewFeedback: ReviewFeedback;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Review feedback structure
|
|
149
|
+
*/
|
|
150
|
+
export interface ReviewFeedback {
|
|
151
|
+
explorationId: string;
|
|
152
|
+
reviewer: string;
|
|
153
|
+
steps: {
|
|
154
|
+
stepIndex: number;
|
|
155
|
+
status: 'approved' | 'rejected' | 'pending';
|
|
156
|
+
comment?: string;
|
|
157
|
+
tags?: string[];
|
|
158
|
+
}[];
|
|
159
|
+
overallNotes?: string;
|
|
160
|
+
verdict: 'approved' | 'rejected';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Enhanced snapshot from browser - includes tree and element refs
|
|
165
|
+
*/
|
|
166
|
+
export interface EnhancedSnapshot {
|
|
167
|
+
tree: string;
|
|
168
|
+
refs: Record<string, unknown>;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Result from findElement operation
|
|
173
|
+
*/
|
|
174
|
+
export interface FindElementResult {
|
|
175
|
+
/** Element reference (e.g., "e5") or "NOT_FOUND" */
|
|
176
|
+
ref: string;
|
|
177
|
+
/** AI reasoning for why this element was selected */
|
|
178
|
+
reasoning: string;
|
|
179
|
+
/** Confidence score 0-1 */
|
|
180
|
+
confidence: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* AI Adapter interface - defines the contract for LLM integrations
|
|
185
|
+
*/
|
|
186
|
+
export interface AIAdapter {
|
|
187
|
+
/**
|
|
188
|
+
* Name of the adapter (e.g., 'claude', 'openai')
|
|
189
|
+
*/
|
|
190
|
+
readonly name: string;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Run exploration on a spec
|
|
194
|
+
*/
|
|
195
|
+
explore(params: ExploreParams): Promise<ExplorationOutput>;
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Find element from natural language query
|
|
199
|
+
* Uses AI to interpret the query and match against snapshot elements
|
|
200
|
+
*/
|
|
201
|
+
findElement(query: string, snapshot: EnhancedSnapshot): Promise<FindElementResult>;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Retry exploration with review feedback
|
|
205
|
+
*/
|
|
206
|
+
retryWithFeedback?(params: RetryParams): Promise<ExplorationOutput>;
|
|
207
|
+
}
|