@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.
Files changed (58) hide show
  1. package/dist/adapters/claude-cli.d.ts +57 -0
  2. package/dist/adapters/claude-cli.d.ts.map +1 -0
  3. package/dist/adapters/claude-cli.js +195 -0
  4. package/dist/adapters/claude-cli.js.map +1 -0
  5. package/dist/adapters/claude.d.ts +54 -0
  6. package/dist/adapters/claude.d.ts.map +1 -0
  7. package/dist/adapters/claude.js +160 -0
  8. package/dist/adapters/claude.js.map +1 -0
  9. package/dist/adapters/index.d.ts +6 -0
  10. package/dist/adapters/index.d.ts.map +1 -0
  11. package/dist/adapters/index.js +4 -0
  12. package/dist/adapters/index.js.map +1 -0
  13. package/dist/adapters/types.d.ts +196 -0
  14. package/dist/adapters/types.d.ts.map +1 -0
  15. package/dist/adapters/types.js +3 -0
  16. package/dist/adapters/types.js.map +1 -0
  17. package/dist/agent-browser-session.d.ts +62 -0
  18. package/dist/agent-browser-session.d.ts.map +1 -0
  19. package/dist/agent-browser-session.js +272 -0
  20. package/dist/agent-browser-session.js.map +1 -0
  21. package/dist/evidence.d.ts +111 -0
  22. package/dist/evidence.d.ts.map +1 -0
  23. package/dist/evidence.js +144 -0
  24. package/dist/evidence.js.map +1 -0
  25. package/dist/explorer.d.ts +180 -0
  26. package/dist/explorer.d.ts.map +1 -0
  27. package/dist/explorer.js +393 -0
  28. package/dist/explorer.js.map +1 -0
  29. package/dist/index.d.ts +15 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +15 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/locator-candidates.d.ts +127 -0
  34. package/dist/locator-candidates.d.ts.map +1 -0
  35. package/dist/locator-candidates.js +358 -0
  36. package/dist/locator-candidates.js.map +1 -0
  37. package/dist/step-executor.d.ts +99 -0
  38. package/dist/step-executor.d.ts.map +1 -0
  39. package/dist/step-executor.js +646 -0
  40. package/dist/step-executor.js.map +1 -0
  41. package/package.json +34 -0
  42. package/src/adapters/claude-cli.test.ts +134 -0
  43. package/src/adapters/claude-cli.ts +240 -0
  44. package/src/adapters/claude.test.ts +195 -0
  45. package/src/adapters/claude.ts +190 -0
  46. package/src/adapters/index.ts +21 -0
  47. package/src/adapters/types.ts +207 -0
  48. package/src/agent-browser-session.test.ts +369 -0
  49. package/src/agent-browser-session.ts +349 -0
  50. package/src/evidence.test.ts +239 -0
  51. package/src/evidence.ts +203 -0
  52. package/src/explorer.test.ts +321 -0
  53. package/src/explorer.ts +565 -0
  54. package/src/index.ts +51 -0
  55. package/src/locator-candidates.test.ts +602 -0
  56. package/src/locator-candidates.ts +441 -0
  57. package/src/step-executor.test.ts +696 -0
  58. package/src/step-executor.ts +783 -0
@@ -0,0 +1,565 @@
1
+ // @browserflow-ai/exploration - Main exploration orchestrator
2
+
3
+ // Debug flag - set via BF_DEBUG=1 environment variable
4
+ const DEBUG = process.env.BF_DEBUG === '1';
5
+
6
+ function debug(...args: unknown[]): void {
7
+ if (DEBUG) {
8
+ console.error('[explorer]', ...args);
9
+ }
10
+ }
11
+
12
+ import type {
13
+ AIAdapter,
14
+ ExploreParams,
15
+ ExplorationOutput,
16
+ Spec,
17
+ SpecStep,
18
+ StepResult,
19
+ StepExecution,
20
+ StepScreenshots,
21
+ EnhancedSnapshot,
22
+ } from './adapters/types';
23
+ import { StepExecutor } from './step-executor';
24
+ import { EvidenceCollector } from './evidence';
25
+ import { LocatorCandidateGenerator } from './locator-candidates';
26
+ import * as path from 'path';
27
+
28
+ /**
29
+ * Browser launch options
30
+ */
31
+ export interface BrowserLaunchOptions {
32
+ headless?: boolean;
33
+ viewport?: { width: number; height: number };
34
+ }
35
+
36
+ /**
37
+ * Browser session interface - abstracts agent-browser integration
38
+ * Actual implementation will use agent-browser's BrowserManager
39
+ */
40
+ export interface BrowserSession {
41
+ isLaunched(): boolean;
42
+ launch(options?: BrowserLaunchOptions): Promise<void>;
43
+ navigate(url: string): Promise<void>;
44
+ screenshot(options?: {
45
+ fullPage?: boolean;
46
+ clip?: { x: number; y: number; width: number; height: number };
47
+ mask?: string[];
48
+ quality?: number;
49
+ }): Promise<Buffer>;
50
+ getSnapshot(options?: {
51
+ interactive?: boolean;
52
+ maxDepth?: number;
53
+ compact?: boolean;
54
+ selector?: string;
55
+ }): Promise<EnhancedSnapshot>;
56
+ close(): Promise<void>;
57
+
58
+ // Interaction methods
59
+ click?(ref: string): Promise<void>;
60
+ fill?(ref: string, value: string): Promise<void>;
61
+ type?(ref: string, text: string): Promise<void>;
62
+ select?(ref: string, option: string): Promise<void>;
63
+ check?(ref: string, checked: boolean): Promise<void>;
64
+ press?(key: string): Promise<void>;
65
+
66
+ // Navigation methods
67
+ back?(): Promise<void>;
68
+ forward?(): Promise<void>;
69
+ refresh?(): Promise<void>;
70
+
71
+ // Wait methods
72
+ waitForSelector?(selector: string, timeout: number): Promise<void>;
73
+ waitForURL?(urlPattern: string, timeout: number): Promise<void>;
74
+ waitForText?(text: string, timeout: number): Promise<void>;
75
+ waitForLoadState?(state: 'load' | 'domcontentloaded' | 'networkidle'): Promise<void>;
76
+ waitForTimeout?(ms: number): Promise<void>;
77
+
78
+ // Scroll methods
79
+ scrollIntoView?(ref: string): Promise<void>;
80
+ scroll?(x: number, y: number): Promise<void>;
81
+
82
+ // State methods
83
+ getCurrentURL?(): string;
84
+ }
85
+
86
+ /**
87
+ * Configuration for the Explorer
88
+ */
89
+ export interface ExplorerConfig {
90
+ adapter: AIAdapter;
91
+ browser?: BrowserSession;
92
+ outputDir?: string;
93
+ headless?: boolean;
94
+ viewport?: { width: number; height: number };
95
+ }
96
+
97
+ /**
98
+ * Explorer - Main orchestrator for AI-powered browser exploration
99
+ *
100
+ * Coordinates between:
101
+ * - AI adapter (Claude, OpenAI, etc.)
102
+ * - Step executor (runs individual steps)
103
+ * - Evidence collector (screenshots, traces)
104
+ * - Locator candidate generator (element selection strategies)
105
+ */
106
+ export class Explorer {
107
+ private adapter: AIAdapter;
108
+ private browser?: BrowserSession;
109
+ private outputDir: string;
110
+ private headless: boolean;
111
+ private defaultViewport: { width: number; height: number };
112
+ private stepExecutor: StepExecutor;
113
+ private evidenceCollector: EvidenceCollector;
114
+ private locatorGenerator: LocatorCandidateGenerator;
115
+
116
+ constructor(config: ExplorerConfig) {
117
+ this.adapter = config.adapter;
118
+ this.browser = config.browser;
119
+ this.outputDir = config.outputDir ?? './explorations';
120
+ this.headless = config.headless ?? true;
121
+ this.defaultViewport = config.viewport ?? { width: 1280, height: 720 };
122
+ this.stepExecutor = new StepExecutor();
123
+ this.evidenceCollector = new EvidenceCollector();
124
+ this.locatorGenerator = new LocatorCandidateGenerator();
125
+ }
126
+
127
+ /**
128
+ * Generate a unique exploration ID
129
+ */
130
+ private generateExplorationId(): string {
131
+ const timestamp = Date.now();
132
+ const random = Math.random().toString(36).slice(2, 8);
133
+ return `exp-${timestamp}-${random}`;
134
+ }
135
+
136
+ /**
137
+ * Run full exploration on a spec
138
+ *
139
+ * This is the main orchestration method that:
140
+ * 1. Launches browser
141
+ * 2. Navigates to starting page
142
+ * 3. Executes each step with evidence collection
143
+ * 4. Handles failures gracefully (continues on error)
144
+ * 5. Produces ExplorationOutput
145
+ *
146
+ * @param spec - The spec to explore
147
+ * @param baseUrl - Base URL for navigation
148
+ * @param options - Additional options
149
+ * @returns Promise resolving to exploration output
150
+ */
151
+ async runExploration(
152
+ spec: Spec,
153
+ baseUrl: string,
154
+ options: Partial<{
155
+ specPath: string;
156
+ viewport: { width: number; height: number };
157
+ headless: boolean;
158
+ }> = {}
159
+ ): Promise<ExplorationOutput> {
160
+ const startTime = Date.now();
161
+ const explorationId = this.generateExplorationId();
162
+ const steps: StepResult[] = [];
163
+ const errors: string[] = [];
164
+
165
+ // Configure evidence collector with exploration-specific output directory
166
+ const explorationOutputDir = path.join(this.outputDir, explorationId);
167
+ this.evidenceCollector.setOutputDir(explorationOutputDir);
168
+
169
+ // Determine viewport - spec preconditions override defaults
170
+ const viewport =
171
+ (spec.preconditions?.viewport as { width: number; height: number } | undefined) ??
172
+ options.viewport ??
173
+ this.defaultViewport;
174
+
175
+ // Ensure browser is available
176
+ if (!this.browser) {
177
+ throw new Error('Browser session not configured');
178
+ }
179
+
180
+ try {
181
+ // 1. Launch browser
182
+ await this.browser.launch({
183
+ headless: options.headless ?? this.headless,
184
+ viewport,
185
+ });
186
+
187
+ // Register browser session with evidence collector
188
+ this.evidenceCollector.registerSession(explorationId, this.browser);
189
+
190
+ // 2. Navigate to starting page
191
+ const pageConfig = spec.preconditions?.page as { url?: string } | undefined;
192
+ const startPage = pageConfig?.url ?? '/';
193
+ const fullUrl = startPage.startsWith('http') ? startPage : `${baseUrl}${startPage}`;
194
+ await this.browser.navigate(fullUrl);
195
+
196
+ // Wait briefly for JS to initialize (configurable via spec timeout)
197
+ // Note: For JS-heavy apps, consider adding explicit wait steps in the spec
198
+ if (this.browser.waitForTimeout) {
199
+ debug('Waiting 1s for page to stabilize...');
200
+ await this.browser.waitForTimeout(1000);
201
+ }
202
+
203
+ // 3. Execute each step
204
+ for (let i = 0; i < spec.steps.length; i++) {
205
+ const specStep = spec.steps[i];
206
+ const stepResult = await this.executeStepWithEvidence(specStep, i, baseUrl, explorationId);
207
+ steps.push(stepResult);
208
+
209
+ if (stepResult.execution.status === 'failed' && stepResult.execution.error) {
210
+ errors.push(`Step ${i}: ${stepResult.execution.error}`);
211
+ }
212
+ }
213
+
214
+ // 4. Build output
215
+ const overallStatus = steps.every((s) => s.execution.status === 'completed')
216
+ ? 'completed'
217
+ : 'failed';
218
+
219
+ return {
220
+ spec: spec.name,
221
+ specPath: options.specPath ?? `specs/${spec.name}.yaml`,
222
+ specDescription: spec.description,
223
+ explorationId,
224
+ timestamp: new Date().toISOString(),
225
+ durationMs: Date.now() - startTime,
226
+ browser: 'chromium',
227
+ viewport,
228
+ baseUrl,
229
+ steps,
230
+ outcomeChecks: [],
231
+ overallStatus,
232
+ errors,
233
+ };
234
+ } finally {
235
+ // Unregister browser session from evidence collector
236
+ this.evidenceCollector.unregisterSession(explorationId);
237
+
238
+ // Always close browser
239
+ await this.browser.close();
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Execute a single step with evidence collection (screenshots)
245
+ */
246
+ private async executeStepWithEvidence(
247
+ step: SpecStep,
248
+ stepIndex: number,
249
+ baseUrl: string,
250
+ explorationId: string
251
+ ): Promise<StepResult> {
252
+ const startTime = Date.now();
253
+ let snapshotBefore: EnhancedSnapshot | undefined;
254
+ let snapshotAfter: EnhancedSnapshot | undefined;
255
+
256
+ // Capture before screenshot
257
+ const screenshotBeforePath = `screenshots/step-${String(stepIndex).padStart(2, '0')}-before.png`;
258
+ const screenshotAfterPath = `screenshots/step-${String(stepIndex).padStart(2, '0')}-after.png`;
259
+
260
+ try {
261
+ debug(`Step ${stepIndex}: ${step.action} - starting`);
262
+
263
+ // Take before screenshot and save to disk
264
+ debug(`Step ${stepIndex}: capturing before screenshot`);
265
+ await this.evidenceCollector.captureScreenshot(
266
+ explorationId,
267
+ `step-${String(stepIndex).padStart(2, '0')}-before`
268
+ );
269
+
270
+ // Get snapshot for element finding
271
+ debug(`Step ${stepIndex}: getting before snapshot`);
272
+ snapshotBefore = await this.browser!.getSnapshot({ interactive: true });
273
+ debug(`Step ${stepIndex}: snapshot has ${Object.keys(snapshotBefore.refs).length} refs, tree: ${snapshotBefore.tree.slice(0, 100)}...`);
274
+
275
+ // Execute the step
276
+ debug(`Step ${stepIndex}: executing action ${step.action}`);
277
+ const startAction = Date.now();
278
+ const execution = await this.executeAction(step, snapshotBefore, baseUrl);
279
+ debug(`Step ${stepIndex}: action completed in ${Date.now() - startAction}ms, status: ${execution.status}`);
280
+
281
+ // Take after screenshot and save to disk
282
+ debug(`Step ${stepIndex}: capturing after screenshot`);
283
+ await this.evidenceCollector.captureScreenshot(
284
+ explorationId,
285
+ `step-${String(stepIndex).padStart(2, '0')}-after`
286
+ );
287
+
288
+ // Get after snapshot
289
+ debug(`Step ${stepIndex}: getting after snapshot`);
290
+ snapshotAfter = await this.browser!.getSnapshot({ interactive: true });
291
+ debug(`Step ${stepIndex}: after snapshot has ${Object.keys(snapshotAfter.refs).length} refs`);
292
+
293
+ return {
294
+ stepIndex,
295
+ specAction: step as Record<string, unknown>,
296
+ execution: {
297
+ ...execution,
298
+ durationMs: Date.now() - startTime,
299
+ },
300
+ screenshots: {
301
+ before: screenshotBeforePath,
302
+ after: screenshotAfterPath,
303
+ },
304
+ snapshotBefore: snapshotBefore as unknown as Record<string, unknown>,
305
+ snapshotAfter: snapshotAfter as unknown as Record<string, unknown>,
306
+ };
307
+ } catch (error) {
308
+ const errorMessage = error instanceof Error ? error.message : String(error);
309
+
310
+ return {
311
+ stepIndex,
312
+ specAction: step as Record<string, unknown>,
313
+ execution: {
314
+ status: 'failed',
315
+ method: step.action,
316
+ durationMs: Date.now() - startTime,
317
+ error: errorMessage,
318
+ },
319
+ screenshots: {
320
+ before: screenshotBeforePath,
321
+ after: screenshotAfterPath,
322
+ },
323
+ snapshotBefore: snapshotBefore as unknown as Record<string, unknown>,
324
+ };
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Execute a single action based on step type
330
+ */
331
+ private async executeAction(
332
+ step: SpecStep,
333
+ snapshot: EnhancedSnapshot,
334
+ baseUrl: string
335
+ ): Promise<StepExecution> {
336
+ switch (step.action) {
337
+ case 'navigate': {
338
+ // Support both url (canonical) and to (legacy)
339
+ const targetUrl = step.url ?? step.to;
340
+ if (!targetUrl) {
341
+ throw new Error('Navigate action requires "url" field');
342
+ }
343
+ const url = targetUrl.startsWith('http') ? targetUrl : `${baseUrl}${targetUrl}`;
344
+ await this.browser!.navigate(url);
345
+ return {
346
+ status: 'completed',
347
+ method: 'navigate',
348
+ durationMs: 0,
349
+ };
350
+ }
351
+
352
+ case 'click': {
353
+ const elementRef = await this.findElementRef(step, snapshot);
354
+ if (this.browser!.click) {
355
+ await this.browser!.click(elementRef);
356
+ }
357
+ return {
358
+ status: 'completed',
359
+ method: 'click',
360
+ elementRef,
361
+ durationMs: 0,
362
+ };
363
+ }
364
+
365
+ case 'fill': {
366
+ const elementRef = await this.findElementRef(step, snapshot);
367
+ const value = step.value ?? '';
368
+ if (this.browser!.fill) {
369
+ await this.browser!.fill(elementRef, value);
370
+ }
371
+ return {
372
+ status: 'completed',
373
+ method: 'fill',
374
+ elementRef,
375
+ durationMs: 0,
376
+ };
377
+ }
378
+
379
+ case 'wait': {
380
+ // Wait actions are handled by timing, for now just mark completed
381
+ return {
382
+ status: 'completed',
383
+ method: 'wait',
384
+ durationMs: 0,
385
+ };
386
+ }
387
+
388
+ case 'verify_state': {
389
+ // Verify state by checking conditions
390
+ return {
391
+ status: 'completed',
392
+ method: 'verify_state',
393
+ durationMs: 0,
394
+ };
395
+ }
396
+
397
+ default: {
398
+ return {
399
+ status: 'completed',
400
+ method: step.action,
401
+ durationMs: 0,
402
+ };
403
+ }
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Find element ref using AI adapter or direct selector/ref
409
+ */
410
+ private async findElementRef(step: SpecStep, snapshot: EnhancedSnapshot): Promise<string> {
411
+ // If step has a direct ref, use it
412
+ if (step.ref) {
413
+ return step.ref;
414
+ }
415
+
416
+ // If step has a selector, it's already specified
417
+ if (step.selector) {
418
+ return step.selector;
419
+ }
420
+
421
+ // Check for target object (v2 spec format)
422
+ const target = step.target as {
423
+ query?: string;
424
+ testid?: string;
425
+ role?: string;
426
+ css?: string;
427
+ text?: string;
428
+ label?: string;
429
+ placeholder?: string;
430
+ } | undefined;
431
+
432
+ if (target) {
433
+ // If target has CSS selector, use it directly
434
+ if (target.css) {
435
+ return target.css;
436
+ }
437
+
438
+ // If target has testid, convert to Playwright testId selector
439
+ if (target.testid) {
440
+ return `[data-testid="${target.testid}"]`;
441
+ }
442
+
443
+ // Use AI adapter to find element from query or other target properties
444
+ const query = target.query
445
+ || (target.role && `a ${target.role} element`)
446
+ || (target.text && `element with text "${target.text}"`)
447
+ || (target.label && `element with label "${target.label}"`)
448
+ || (target.placeholder && `input with placeholder "${target.placeholder}"`);
449
+
450
+ if (query) {
451
+ const result = await this.adapter.findElement(query, snapshot);
452
+ if (result.ref === 'NOT_FOUND') {
453
+ throw new Error(`Element not found for query: ${query}`);
454
+ }
455
+ return result.ref;
456
+ }
457
+ }
458
+
459
+ // Legacy: Use AI adapter to find element from step.query (legacy format)
460
+ if ((step as { query?: string }).query) {
461
+ const result = await this.adapter.findElement((step as { query?: string }).query!, snapshot);
462
+ if (result.ref === 'NOT_FOUND') {
463
+ throw new Error(`Element not found for query: ${(step as { query?: string }).query}`);
464
+ }
465
+ return result.ref;
466
+ }
467
+
468
+ throw new Error('Step must have ref, selector, target, or query');
469
+ }
470
+
471
+ /**
472
+ * Run exploration on a spec
473
+ *
474
+ * @param spec - The spec to explore
475
+ * @param baseUrl - Base URL for the browser session
476
+ * @param options - Additional options
477
+ * @returns Promise resolving to exploration output
478
+ */
479
+ async explore(
480
+ spec: Spec,
481
+ baseUrl: string,
482
+ options: Partial<ExploreParams> = {}
483
+ ): Promise<ExplorationOutput> {
484
+ const params: ExploreParams = {
485
+ spec,
486
+ specPath: options.specPath ?? `specs/${spec.name}.yaml`,
487
+ baseUrl,
488
+ browser: options.browser ?? 'chromium',
489
+ viewport: options.viewport ?? { width: 1280, height: 720 },
490
+ timeout: options.timeout ?? 30000,
491
+ outputDir: options.outputDir ?? `${this.outputDir}/${spec.name}`,
492
+ sessionId: options.sessionId,
493
+ };
494
+
495
+ // Delegate to adapter for AI-powered exploration
496
+ return this.adapter.explore(params);
497
+ }
498
+
499
+ /**
500
+ * Execute a single step manually (for testing/debugging)
501
+ *
502
+ * @param step - The step to execute
503
+ * @param stepIndex - Index of the step (default: 0)
504
+ * @returns Promise resolving to step result
505
+ */
506
+ async executeStep(
507
+ step: Spec['steps'][number],
508
+ stepIndex: number = 0
509
+ ): Promise<StepResult> {
510
+ return this.stepExecutor.execute(step, stepIndex);
511
+ }
512
+
513
+ /**
514
+ * Capture evidence (screenshot) at current state
515
+ *
516
+ * @param sessionId - Browser session ID
517
+ * @param name - Evidence name/identifier
518
+ * @returns Promise resolving to evidence file path
519
+ */
520
+ async captureEvidence(sessionId: string, name: string): Promise<string> {
521
+ return this.evidenceCollector.captureScreenshot(sessionId, name);
522
+ }
523
+
524
+ /**
525
+ * Generate locator candidates for an element
526
+ *
527
+ * @param query - Natural language description of element
528
+ * @param snapshot - Browser snapshot with element refs
529
+ * @returns Promise resolving to ranked list of locator options
530
+ */
531
+ async generateLocators(
532
+ query: string,
533
+ snapshot: Record<string, unknown>
534
+ ): Promise<string[]> {
535
+ return this.locatorGenerator.generateCandidates(query, snapshot);
536
+ }
537
+
538
+ /**
539
+ * Get the configured AI adapter
540
+ */
541
+ getAdapter(): AIAdapter {
542
+ return this.adapter;
543
+ }
544
+
545
+ /**
546
+ * Get the step executor instance
547
+ */
548
+ getStepExecutor(): StepExecutor {
549
+ return this.stepExecutor;
550
+ }
551
+
552
+ /**
553
+ * Get the evidence collector instance
554
+ */
555
+ getEvidenceCollector(): EvidenceCollector {
556
+ return this.evidenceCollector;
557
+ }
558
+
559
+ /**
560
+ * Get the locator generator instance
561
+ */
562
+ getLocatorGenerator(): LocatorCandidateGenerator {
563
+ return this.locatorGenerator;
564
+ }
565
+ }
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ // @browserflow-ai/exploration - AI exploration engine
2
+
3
+ // Main orchestrator
4
+ export { Explorer } from './explorer';
5
+ export type { ExplorerConfig, BrowserSession, BrowserLaunchOptions } from './explorer';
6
+
7
+ // Adapters
8
+ export { ClaudeAdapter } from './adapters/claude';
9
+ export type { ClaudeAdapterConfig } from './adapters/claude';
10
+ export { ClaudeCliAdapter } from './adapters/claude-cli';
11
+ export type { ClaudeCliAdapterConfig } from './adapters/claude-cli';
12
+
13
+ // Browser Session Adapters
14
+ export { AgentBrowserSession, createBrowserSession } from './agent-browser-session';
15
+
16
+ // Core types
17
+ export type {
18
+ AIAdapter,
19
+ ExploreParams,
20
+ ExplorationOutput,
21
+ RetryParams,
22
+ ReviewFeedback,
23
+ Spec,
24
+ SpecStep,
25
+ StepResult,
26
+ StepExecution,
27
+ StepScreenshots,
28
+ OutcomeCheck,
29
+ EnhancedSnapshot,
30
+ FindElementResult,
31
+ } from './adapters/types';
32
+
33
+ // Step execution
34
+ export { StepExecutor } from './step-executor';
35
+ export type { StepExecutorConfig } from './step-executor';
36
+
37
+ // Evidence collection
38
+ export { EvidenceCollector } from './evidence';
39
+ export type {
40
+ EvidenceCollectorConfig,
41
+ EvidenceMetadata,
42
+ ScreenshotOptions,
43
+ } from './evidence';
44
+
45
+ // Locator generation
46
+ export { LocatorCandidateGenerator } from './locator-candidates';
47
+ export type {
48
+ LocatorCandidateGeneratorConfig,
49
+ LocatorCandidate,
50
+ ElementInfo,
51
+ } from './locator-candidates';