refactor(types): comprehensive TypeScript type system improvements
Major type system refactoring and error fixes across the codebase: **Type System Improvements:** - Extended OpenFangStreamEvent with 'connected' and 'agents_updated' event types - Added GatewayPong interface for WebSocket pong responses - Added index signature to MemorySearchOptions for Record compatibility - Fixed RawApproval interface with hand_name, run_id properties **Gateway & Protocol Fixes:** - Fixed performHandshake nonce handling in gateway-client.ts - Fixed onAgentStream callback type definitions - Fixed HandRun runId mapping to handle undefined values - Fixed Approval mapping with proper default values **Memory System Fixes:** - Fixed MemoryEntry creation with required properties (lastAccessedAt, accessCount) - Replaced getByAgent with getAll method in vector-memory.ts - Fixed MemorySearchOptions type compatibility **Component Fixes:** - Fixed ReflectionLog property names (filePath→file, proposedContent→suggestedContent) - Fixed SkillMarket suggestSkills async call arguments - Fixed message-virtualization useRef generic type - Fixed session-persistence messageCount type conversion **Code Cleanup:** - Removed unused imports and variables across multiple files - Consolidated StoredError interface (removed duplicate) - Deleted obsolete test files (feedbackStore.test.ts, memory-index.test.ts) **New Features:** - Added browser automation module (Tauri backend) - Added Active Learning Panel component - Added Agent Onboarding Wizard - Added Memory Graph visualization - Added Personality Selector - Added Skill Market store and components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
460
desktop/src/lib/browser-client.ts
Normal file
460
desktop/src/lib/browser-client.ts
Normal file
@@ -0,0 +1,460 @@
|
||||
/**
|
||||
* Browser Automation Client for ZCLAW
|
||||
* Provides TypeScript API for Fantoccini-based browser automation
|
||||
*/
|
||||
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export interface BrowserSessionResult {
|
||||
session_id: string;
|
||||
}
|
||||
|
||||
export interface BrowserSessionInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
current_url: string | null;
|
||||
title: string | null;
|
||||
status: string;
|
||||
created_at: string;
|
||||
last_activity: string;
|
||||
}
|
||||
|
||||
export interface BrowserNavigationResult {
|
||||
url: string | null;
|
||||
title: string | null;
|
||||
}
|
||||
|
||||
export interface BrowserElementInfo {
|
||||
selector: string;
|
||||
tag_name: string | null;
|
||||
text: string | null;
|
||||
is_displayed: boolean;
|
||||
is_enabled: boolean;
|
||||
is_selected: boolean;
|
||||
location: BrowserElementLocation | null;
|
||||
size: BrowserElementSize | null;
|
||||
}
|
||||
|
||||
export interface BrowserElementLocation {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface BrowserElementSize {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface BrowserScreenshotResult {
|
||||
base64: string;
|
||||
format: string;
|
||||
}
|
||||
|
||||
export interface FormFieldData {
|
||||
selector: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Session Management
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a new browser session
|
||||
*/
|
||||
export async function createSession(options?: {
|
||||
webdriverUrl?: string;
|
||||
headless?: boolean;
|
||||
browserType?: 'chrome' | 'firefox' | 'edge' | 'safari';
|
||||
windowWidth?: number;
|
||||
windowHeight?: number;
|
||||
}): Promise<BrowserSessionResult> {
|
||||
return invoke('browser_create_session', {
|
||||
webdriverUrl: options?.webdriverUrl,
|
||||
headless: options?.headless,
|
||||
browserType: options?.browserType,
|
||||
windowWidth: options?.windowWidth,
|
||||
windowHeight: options?.windowHeight,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Close a browser session
|
||||
*/
|
||||
export async function closeSession(sessionId: string): Promise<void> {
|
||||
return invoke('browser_close_session', { sessionId });
|
||||
}
|
||||
|
||||
/**
|
||||
* List all browser sessions
|
||||
*/
|
||||
export async function listSessions(): Promise<BrowserSessionInfo[]> {
|
||||
return invoke('browser_list_sessions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session info
|
||||
*/
|
||||
export async function getSession(sessionId: string): Promise<BrowserSessionInfo> {
|
||||
return invoke('browser_get_session', { sessionId });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Navigation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Navigate to URL
|
||||
*/
|
||||
export async function navigate(
|
||||
sessionId: string,
|
||||
url: string
|
||||
): Promise<BrowserNavigationResult> {
|
||||
return invoke('browser_navigate', { sessionId, url });
|
||||
}
|
||||
|
||||
/**
|
||||
* Go back
|
||||
*/
|
||||
export async function back(sessionId: string): Promise<void> {
|
||||
return invoke('browser_back', { sessionId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Go forward
|
||||
*/
|
||||
export async function forward(sessionId: string): Promise<void> {
|
||||
return invoke('browser_forward', { sessionId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh page
|
||||
*/
|
||||
export async function refresh(sessionId: string): Promise<void> {
|
||||
return invoke('browser_refresh', { sessionId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current URL
|
||||
*/
|
||||
export async function getCurrentUrl(sessionId: string): Promise<string> {
|
||||
return invoke('browser_get_url', { sessionId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page title
|
||||
*/
|
||||
export async function getTitle(sessionId: string): Promise<string> {
|
||||
return invoke('browser_get_title', { sessionId });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Element Interaction
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Find element by CSS selector
|
||||
*/
|
||||
export async function findElement(
|
||||
sessionId: string,
|
||||
selector: string
|
||||
): Promise<BrowserElementInfo> {
|
||||
return invoke('browser_find_element', { sessionId, selector });
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple elements
|
||||
*/
|
||||
export async function findElements(
|
||||
sessionId: string,
|
||||
selector: string
|
||||
): Promise<BrowserElementInfo[]> {
|
||||
return invoke('browser_find_elements', { sessionId, selector });
|
||||
}
|
||||
|
||||
/**
|
||||
* Click element
|
||||
*/
|
||||
export async function click(sessionId: string, selector: string): Promise<void> {
|
||||
return invoke('browser_click', { sessionId, selector });
|
||||
}
|
||||
|
||||
/**
|
||||
* Type text into element
|
||||
*/
|
||||
export async function typeText(
|
||||
sessionId: string,
|
||||
selector: string,
|
||||
text: string,
|
||||
clearFirst?: boolean
|
||||
): Promise<void> {
|
||||
return invoke('browser_type', { sessionId, selector, text, clearFirst });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element text
|
||||
*/
|
||||
export async function getText(sessionId: string, selector: string): Promise<string> {
|
||||
return invoke('browser_get_text', { sessionId, selector });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element attribute
|
||||
*/
|
||||
export async function getAttribute(
|
||||
sessionId: string,
|
||||
selector: string,
|
||||
attribute: string
|
||||
): Promise<string | null> {
|
||||
return invoke('browser_get_attribute', { sessionId, selector, attribute });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for element
|
||||
*/
|
||||
export async function waitForElement(
|
||||
sessionId: string,
|
||||
selector: string,
|
||||
timeoutMs?: number
|
||||
): Promise<BrowserElementInfo> {
|
||||
return invoke('browser_wait_for_element', {
|
||||
sessionId,
|
||||
selector,
|
||||
timeoutMs: timeoutMs ?? 10000,
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Advanced Operations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Execute JavaScript
|
||||
*/
|
||||
export async function executeScript(
|
||||
sessionId: string,
|
||||
script: string,
|
||||
args?: unknown[]
|
||||
): Promise<unknown> {
|
||||
return invoke('browser_execute_script', { sessionId, script, args });
|
||||
}
|
||||
|
||||
/**
|
||||
* Take screenshot
|
||||
*/
|
||||
export async function screenshot(sessionId: string): Promise<BrowserScreenshotResult> {
|
||||
return invoke('browser_screenshot', { sessionId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Take element screenshot
|
||||
*/
|
||||
export async function elementScreenshot(
|
||||
sessionId: string,
|
||||
selector: string
|
||||
): Promise<BrowserScreenshotResult> {
|
||||
return invoke('browser_element_screenshot', { sessionId, selector });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page source
|
||||
*/
|
||||
export async function getSource(sessionId: string): Promise<string> {
|
||||
return invoke('browser_get_source', { sessionId });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// High-Level Tasks
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Scrape page content
|
||||
*/
|
||||
export async function scrapePage(
|
||||
sessionId: string,
|
||||
selectors: string[],
|
||||
waitFor?: string,
|
||||
timeoutMs?: number
|
||||
): Promise<Record<string, string[]>> {
|
||||
return invoke('browser_scrape_page', {
|
||||
sessionId,
|
||||
selectors,
|
||||
waitFor,
|
||||
timeoutMs,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill form
|
||||
*/
|
||||
export async function fillForm(
|
||||
sessionId: string,
|
||||
fields: FormFieldData[],
|
||||
submitSelector?: string
|
||||
): Promise<void> {
|
||||
return invoke('browser_fill_form', { sessionId, fields, submitSelector });
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Browser Client Class (Convenience Wrapper)
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* High-level browser client for easier usage
|
||||
*/
|
||||
export class Browser {
|
||||
private sessionId: string | null = null;
|
||||
|
||||
/**
|
||||
* Start a new browser session
|
||||
*/
|
||||
async start(options?: {
|
||||
webdriverUrl?: string;
|
||||
headless?: boolean;
|
||||
browserType?: 'chrome' | 'firefox' | 'edge' | 'safari';
|
||||
windowWidth?: number;
|
||||
windowHeight?: number;
|
||||
}): Promise<string> {
|
||||
const result = await createSession(options);
|
||||
this.sessionId = result.session_id;
|
||||
return this.sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close browser session
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
if (this.sessionId) {
|
||||
await closeSession(this.sessionId);
|
||||
this.sessionId = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current session ID
|
||||
*/
|
||||
getSessionId(): string | null {
|
||||
return this.sessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to URL
|
||||
*/
|
||||
async goto(url: string): Promise<BrowserNavigationResult> {
|
||||
this.ensureSession();
|
||||
return navigate(this.sessionId!, url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find element
|
||||
*/
|
||||
async $(selector: string): Promise<BrowserElementInfo> {
|
||||
this.ensureSession();
|
||||
return findElement(this.sessionId!, selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find multiple elements
|
||||
*/
|
||||
async $$(selector: string): Promise<BrowserElementInfo[]> {
|
||||
this.ensureSession();
|
||||
return findElements(this.sessionId!, selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Click element
|
||||
*/
|
||||
async click(selector: string): Promise<void> {
|
||||
this.ensureSession();
|
||||
return click(this.sessionId!, selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type text
|
||||
*/
|
||||
async type(selector: string, text: string, clearFirst = false): Promise<void> {
|
||||
this.ensureSession();
|
||||
return typeText(this.sessionId!, selector, text, clearFirst);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for element
|
||||
*/
|
||||
async wait(selector: string, timeoutMs = 10000): Promise<BrowserElementInfo> {
|
||||
this.ensureSession();
|
||||
return waitForElement(this.sessionId!, selector, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Take screenshot
|
||||
*/
|
||||
async screenshot(): Promise<BrowserScreenshotResult> {
|
||||
this.ensureSession();
|
||||
return screenshot(this.sessionId!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute JavaScript
|
||||
*/
|
||||
async eval(script: string, args?: unknown[]): Promise<unknown> {
|
||||
this.ensureSession();
|
||||
return executeScript(this.sessionId!, script, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page source
|
||||
*/
|
||||
async source(): Promise<string> {
|
||||
this.ensureSession();
|
||||
return getSource(this.sessionId!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current URL
|
||||
*/
|
||||
async url(): Promise<string> {
|
||||
this.ensureSession();
|
||||
return getCurrentUrl(this.sessionId!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page title
|
||||
*/
|
||||
async title(): Promise<string> {
|
||||
this.ensureSession();
|
||||
return getTitle(this.sessionId!);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape page content
|
||||
*/
|
||||
async scrape(
|
||||
selectors: string[],
|
||||
waitFor?: string,
|
||||
timeoutMs?: number
|
||||
): Promise<Record<string, string[]>> {
|
||||
this.ensureSession();
|
||||
return scrapePage(this.sessionId!, selectors, waitFor, timeoutMs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill form
|
||||
*/
|
||||
async fillForm(fields: FormFieldData[], submitSelector?: string): Promise<void> {
|
||||
this.ensureSession();
|
||||
return fillForm(this.sessionId!, fields, submitSelector);
|
||||
}
|
||||
|
||||
private ensureSession(): void {
|
||||
if (!this.sessionId) {
|
||||
throw new Error('Browser session not started. Call start() first.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default export
|
||||
export default Browser;
|
||||
Reference in New Issue
Block a user