diff --git a/boat/doc-collector/src/ai/tools.ts b/boat/doc-collector/src/ai/tools.ts index 95786a4..fc5ac46 100644 --- a/boat/doc-collector/src/ai/tools.ts +++ b/boat/doc-collector/src/ai/tools.ts @@ -143,7 +143,7 @@ async function attemptInteraction(explorer: Explorer, candidate: InteractionCand const action = explorer.createAction(); for (const command of buildClickCommands(candidate.element, candidate.container)) { - const success = await action.attempt(command, buildPurpose(candidate), false); + const success = await action.attempt(command, buildPurpose(candidate)); if (success) { return true; } @@ -155,7 +155,7 @@ async function attemptInteraction(explorer: Explorer, candidate: InteractionCand async function restoreInteractionState(explorer: Explorer, restoreUrl: string, primaryCommand?: string | null): Promise { if (primaryCommand) { const action = explorer.createAction(); - const restored = await action.attempt(primaryCommand, `Restore initial state on ${restoreUrl}`, false); + const restored = await action.attempt(primaryCommand, `Restore initial state on ${restoreUrl}`); if (restored) { await wait(TAB_WAIT_MS); return; @@ -163,7 +163,7 @@ async function restoreInteractionState(explorer: Explorer, restoreUrl: string, p } const action = explorer.createAction(); - await action.attempt(`I.amOnPage(${JSON.stringify(restoreUrl)})`, `Restore page ${restoreUrl}`, false); + await action.attempt(`I.amOnPage(${JSON.stringify(restoreUrl)})`, `Restore page ${restoreUrl}`); } function buildTransition(candidate: InteractionCandidate, beforeState: WebPageState, afterState: WebPageState, changes: InteractionChanges): DocStateTransition { diff --git a/src/action-result.ts b/src/action-result.ts index 113392a..0389e50 100644 --- a/src/action-result.ts +++ b/src/action-result.ts @@ -166,16 +166,6 @@ export class ActionResult implements ActionResultData { return undefined; } - get browserLogsContent(): any[] { - if (this._browserLogs !== undefined) { - return this._browserLogs; - } - if (this.logFile) { - return ActionResult.loadBrowserLogsFromFile(this.logFile); - } - return []; - } - get ariaSnapshot(): string | null { if (this._ariaSnapshot !== undefined) { return this._ariaSnapshot; @@ -525,58 +515,6 @@ export class ActionResult implements ActionResultData { return stateString; } - private saveBrowserLogs(): void { - const logs = this.browserLogsContent; - if (!logs || logs.length === 0) return; - - try { - const outputDir = 'output'; - const stateHash = this.getStateHash(); - const filename = `${stateHash}.log`; - const filePath = join(outputDir, filename); - - // Ensure output directory exists - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - - // Format logs for saving - const formattedLogs = logs.map((log: any) => { - const timestamp = new Date().toISOString(); - const level = (log.type || log.level || 'LOG').toUpperCase(); - const message = log.text || log.message || String(log); - return `[${timestamp}] ${level}: ${message}`; - }); - - // Save log content to file - const logContent = `${formattedLogs.join('\n')}\n`; - fs.writeFileSync(filePath, logContent, 'utf8'); - } catch (error) { - // Silently fail to avoid breaking the main flow - console.error('Failed to save browser logs:', error); - } - } - - private saveHtmlOutput(): void { - try { - const outputDir = 'output'; - const stateHash = this.getStateHash(); - const filename = `${stateHash}.html`; - const filePath = join(outputDir, filename); - - // Ensure output directory exists - if (!fs.existsSync(outputDir)) { - fs.mkdirSync(outputDir, { recursive: true }); - } - - // Save HTML content to file - fs.writeFileSync(filePath, this.html, 'utf8'); - } catch (error) { - // Silently fail to avoid breaking the main flow - console.error('Failed to save HTML output:', error); - } - } - async diff(previousState: ActionResult | null): Promise { return new Diff(this, previousState); } @@ -717,10 +655,6 @@ export class Diff { return this._ariaDiffResult; } - get ariaRemoved(): string | null { - return this._ariaDiffResult; - } - get htmlDiff(): HtmlDiffResult | null { return this._htmlDiffResult; } diff --git a/src/action.ts b/src/action.ts index 7a4c180..4c7b989 100644 --- a/src/action.ts +++ b/src/action.ts @@ -5,23 +5,17 @@ import { container, recorder } from 'codeceptjs'; import * as codeceptjs from 'codeceptjs'; import { ActionResult } from './action-result.js'; import { clearActivity, setActivity } from './activity.ts'; -import { ExperienceCompactor } from './ai/experience-compactor.js'; -import { Navigator } from './ai/navigator.js'; -import type { Provider } from './ai/provider.js'; import { ConfigParser, outputPath } from './config.js'; import type { ExplorbotConfig } from './config.js'; -import type { UserResolveFunction } from './explorbot.ts'; import { Observability } from './observability.ts'; import type { PlaywrightRecorder } from './playwright-recorder.ts'; import type { StateManager } from './state-manager.js'; import { isFatalBrowserError, isNavigationTransitionError } from './utils/browser-errors.ts'; -import { extractCodeBlocks } from './utils/code-extractor.js'; import { captureHtmlForSnapshot, htmlCombinedSnapshot, minifyHtml } from './utils/html.js'; import { createDebug, setStepSpanParent, tag } from './utils/logger.js'; import { waitForPageReadiness } from './utils/page-readiness.ts'; import { codeceptJSSandbox, hasPlaywrightCommands, playwrightSandbox, sanitizeCodeBlock } from './utils/web-sandbox.ts'; import { safeFilename } from './utils/strings.ts'; -import { throttle } from './utils/throttle.ts'; const debugLog = createDebug('explorbot:action'); const CAPTURE_NAVIGATION_TRANSITION_ATTEMPTS = 3; @@ -34,7 +28,6 @@ class Action { // action info private action: string | null = null; - private expectation: string | null = null; public lastError: Error | null = null; public playwrightHelper: any; public playwrightGroupId: string | null = null; @@ -50,10 +43,6 @@ class Action { this.recorder = recorder; } - async caputrePageWithScreenshot(): Promise { - return this.capturePageState({ includeScreenshot: true }); - } - async saveScreenshot(): Promise { const currentState = this.stateManager.getCurrentState(); if (currentState?.screenshotFile) return currentState.screenshotFile; @@ -339,64 +328,7 @@ class Action { return this; } - async expect(codeOrFunction: string | ((I: CodeceptJS.I) => void)): Promise { - const codeString = typeof codeOrFunction === 'string' ? codeOrFunction : codeOrFunction.toString(); - this.expectation = codeString.toString(); - const expectationPreview = sanitizeCodeBlock(codeString) - .split('\n') - .map((line) => line.trim()) - .filter(Boolean) - .slice(0, 2) - .join(' '); - tag('step').log(`Expecting: ${expectationPreview || 'assertion'}`); - try { - debugLog('Executing expectation:', codeString); - - if (typeof codeOrFunction === 'function') { - codeceptJSSandbox(this.actor, codeOrFunction); - } else { - const sanitizedCode = sanitizeCodeBlock(codeString); - if (!sanitizedCode) { - throw new Error('No valid I.* commands found in code block'); - } - codeceptJSSandbox(this.actor, sanitizedCode); - } - await recorder.promise(); - debugLog('Expectation executed successfully'); - - // Get current state from state manager - const currentState = this.stateManager.getCurrentState(); - if (currentState) { - // Create ActionResult from current state for compatibility - this.actionResult = new ActionResult({ - url: currentState.fullUrl || '', - title: currentState.title, - timestamp: currentState.timestamp, - html: '', // Empty HTML for expectation state - }); - } - - return this; - } catch (err) { - tag('error').log('Expectation failed:', errorToString(err)); - this.lastError = err as Error; - await recorder.reset(); - await recorder.start(); - debugLog('Expectation failed:', errorToString(err)); - } finally { - clearActivity(); - } - - return this; - } - - public async waitForInteraction(): Promise { - // start with basic approach - await this.actor.wait(0.5); - return this; - } - - public async attempt(codeBlock: string, originalMessage?: string, experience = true): Promise { + public async attempt(codeBlock: string, originalMessage?: string): Promise { try { debugLog('Resolution attempt...'); setActivity('🦾 Acting in browser...', 'action'); @@ -404,19 +336,9 @@ class Action { if (!this.actionResult) { this.actionResult = ActionResult.fromState(this.stateManager.getCurrentState()!); } - const prevActionResult = this.actionResult; this.lastError = null; await this.execute(codeBlock); - if (!this.expectation && originalMessage) { - this.expectation = originalMessage; - } - - if (!this.expectation) { - return true; - } - - debugLog('Resolved Expectation:', this.expectation); return true; } catch (error) { this.lastError = error as Error; @@ -430,14 +352,6 @@ class Action { return this.actor; } - setActor(actor: CodeceptJS.I): void { - this.actor = actor; - } - - getCurrentState(): ActionResult | null { - return this.actionResult; - } - getActionResult(): ActionResult | null { return this.actionResult; } diff --git a/src/ai/conversation.ts b/src/ai/conversation.ts index c5b4997..1bf153d 100644 --- a/src/ai/conversation.ts +++ b/src/ai/conversation.ts @@ -21,14 +21,12 @@ const AUTO_COMPACT_ARIA_CHANGES_CUTOFF = 500; const AUTO_COMPACT_TARGETED_HTML_CUTOFF = 500; export class Conversation { - id: string; messages: ModelMessage[]; model: any; protectedPrefixCount = 0; private autoTrimRules: Map; constructor(messages: ModelMessage[] = [], model?: any) { - this.id = this.generateId(); this.messages = messages; this.model = model || ''; this.autoTrimRules = new Map(); @@ -45,20 +43,6 @@ export class Conversation { }); } - addUserImage(image: string): void { - if (!image || image.trim() === '') { - console.warn('Warning: Attempting to add empty image to conversation'); - return; - } - - const imageData = image.startsWith('data:') ? image : `data:image/png;base64,${image}`; - - this.messages.push({ - role: 'user', - content: [{ type: 'file', mediaType: 'image/png', data: imageData }], - }); - } - addAssistantText(text: string): void { // Skip empty or whitespace-only messages if (!text || text.trim() === '') { @@ -226,10 +210,6 @@ export class Conversation { return result; } - private generateId(): string { - return `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; - } - getToolExecutions(): ToolExecution[] { const toolCalls = new Map(); for (const message of this.messages) { diff --git a/src/ai/driller.ts b/src/ai/driller.ts index f7ce7df..a47a33d 100644 --- a/src/ai/driller.ts +++ b/src/ai/driller.ts @@ -606,11 +606,11 @@ export class Driller extends TaskAgent implements Agent { const action = this.explorer.createAction(); if (currentState?.url !== originalState.url) { - await action.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, `${reason} (restore URL)`, false); + await action.attempt(`I.amOnPage(${JSON.stringify(targetUrl)})`, `${reason} (restore URL)`); return; } - await action.attempt('I.pressKey("Escape")', `${reason} (restore state)`, false); + await action.attempt('I.pressKey("Escape")', `${reason} (restore state)`); } private async saveToExperience(state: ActionResult, results: InteractionResult[]): Promise { diff --git a/src/ai/navigator.ts b/src/ai/navigator.ts index 35cba2f..793305e 100644 --- a/src/ai/navigator.ts +++ b/src/ai/navigator.ts @@ -6,7 +6,7 @@ import type Action from '../action.ts'; import { ExperienceTracker, renderExperienceToc } from '../experience-tracker.js'; import Explorer from '../explorer.ts'; import { KnowledgeTracker } from '../knowledge-tracker.js'; -import { type WebPageState, normalizeUrl } from '../state-manager.js'; +import { normalizeUrl } from '../state-manager.js'; import { extractCodeBlocks } from '../utils/code-extractor.js'; import { HooksRunner } from '../utils/hooks-runner.ts'; import { createDebug, pluralize, tag } from '../utils/logger.js'; @@ -15,7 +15,6 @@ import { RulesLoader } from '../utils/rules-loader.ts'; import { extractStatePath } from '../utils/url-matcher.js'; import type { Agent } from './agent.js'; import type { Conversation } from './conversation.js'; -import { ExperienceCompactor } from './experience-compactor.js'; import type { Provider } from './provider.js'; import { Researcher } from './researcher.ts'; import { actionRule, locatorRule, unexpectedPopupRule } from './rules.js'; @@ -27,7 +26,6 @@ const debugLog = createDebug('explorbot:navigator'); class Navigator implements Agent { emoji = '🧭'; private provider: Provider; - private experienceCompactor: ExperienceCompactor; private knowledgeTracker: KnowledgeTracker; private experienceTracker: ExperienceTracker; private hooksRunner: HooksRunner; @@ -73,10 +71,9 @@ class Navigator implements Agent { `; private explorer: Explorer; - constructor(explorer: Explorer, provider: Provider, experienceCompactor: ExperienceCompactor, experienceTracker?: ExperienceTracker) { + constructor(explorer: Explorer, provider: Provider, experienceTracker?: ExperienceTracker) { this.provider = provider; this.explorer = explorer; - this.experienceCompactor = experienceCompactor; this.knowledgeTracker = new KnowledgeTracker(); this.experienceTracker = experienceTracker || new ExperienceTracker(); this.hooksRunner = new HooksRunner(explorer, explorer.getConfig()); @@ -764,7 +761,7 @@ class Navigator implements Agent { await this.explorer.switchToMainFrame(); - const verified = await action.attempt(codeBlock, message, false); + const verified = await action.attempt(codeBlock, message); if (verified) { tag('success').log('Verification passed'); diff --git a/src/ai/planner.ts b/src/ai/planner.ts index 355bb84..508e524 100644 --- a/src/ai/planner.ts +++ b/src/ai/planner.ts @@ -18,7 +18,7 @@ import type { Agent } from './agent.js'; import { Conversation } from './conversation.ts'; import type { Fisherman } from './fisherman.ts'; import { WithSessionDedup } from './planner/session-dedup.ts'; -import { getActiveStyle, getStyles } from './planner/styles.ts'; +import { getActiveStyle } from './planner/styles.ts'; import { WithSubPages, getPlannedByStateHash, getRegisteredPlan, registerPlan } from './planner/subpages.ts'; import type { Provider } from './provider.js'; import { POSSIBLE_SECTIONS, type Researcher } from './researcher.ts'; diff --git a/src/ai/provider.ts b/src/ai/provider.ts index 7b31064..10427e5 100644 --- a/src/ai/provider.ts +++ b/src/ai/provider.ts @@ -22,6 +22,8 @@ export class ContextLengthError extends Error {} let telemetryRegistered = false; +const CONTEXT_LENGTH_PATTERNS = ['reduce the length', 'context length', 'maximum context', 'token limit', 'too many tokens', 'max_tokens', 'context_length_exceeded', 'output truncated at maxtokens']; + function extractCachedTokens(usage: any): number { if (!usage) return 0; const direct = usage.inputTokenDetails?.cacheReadTokens ?? usage.cachedInputTokens; @@ -76,7 +78,6 @@ export class Provider { }, }; - static readonly CONTEXT_LENGTH_PATTERNS = ['reduce the length', 'context length', 'maximum context', 'token limit', 'too many tokens', 'max_tokens', 'context_length_exceeded', 'output truncated at maxtokens']; lastConversation: Conversation | null = null; constructor(config: AIConfig) { @@ -144,12 +145,12 @@ export class Provider { return parts.length > 0 ? parts.join('\n\n') : undefined; } - getProviderOptionsForAgent(agentName: string): Record | undefined { + private getProviderOptionsForAgent(agentName: string): Record | undefined { const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents]; return agentConfig?.providerOptions; } - getReasoningForAgent(agentName?: string): string | undefined { + private getReasoningForAgent(agentName?: string): string | undefined { if (!agentName) return undefined; const agentConfig = this.config.agents?.[agentName as keyof typeof this.config.agents]; return agentConfig?.reasoning; @@ -511,7 +512,7 @@ export class Provider { static isContextLengthError(error: any): boolean { const msg = (error?.message || error?.toString() || '').toLowerCase(); - return Provider.CONTEXT_LENGTH_PATTERNS.some((p) => msg.includes(p)); + return CONTEXT_LENGTH_PATTERNS.some((p) => msg.includes(p)); } static trimMessagesForRetry(messages: ModelMessage[]): ModelMessage[] | null { diff --git a/src/ai/researcher/deep-analysis.ts b/src/ai/researcher/deep-analysis.ts index f8925a4..96278c5 100644 --- a/src/ai/researcher/deep-analysis.ts +++ b/src/ai/researcher/deep-analysis.ts @@ -359,7 +359,7 @@ export function WithDeepAnalysis(Base: T) { const isCoordinateClick = el.commands[0].startsWith('I.clickXY('); if (!isCoordinateClick) { const hoverCmd = el.commands[0].replace('I.click(', 'I.moveCursorTo('); - await this.explorer.attemptAction(hoverCmd, undefined, false); + await this.explorer.attemptAction(hoverCmd, undefined); await new Promise((r) => setTimeout(r, 500)); await this.explorer.capturePageState(); @@ -405,7 +405,7 @@ export function WithDeepAnalysis(Base: T) { let clickCode: string | null = null; const action = this.explorer.createAction(); for (const cmd of commands) { - if (await action.attempt(cmd, undefined, false)) { + if (await action.attempt(cmd, undefined)) { clickCode = cmd; break; } diff --git a/src/ai/tools.ts b/src/ai/tools.ts index d13019f..614750c 100644 --- a/src/ai/tools.ts +++ b/src/ai/tools.ts @@ -86,8 +86,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) { for (let i = 0; i < commands.length; i++) { const command = transformContainsCommand(commands[i]); - const isLast = i === commands.length - 1; - const success = await action.attempt(command, explanation, isLast); + const success = await action.attempt(command, explanation); attempts.push({ command, @@ -120,7 +119,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) { retryCommands.push(`I.click('${disambiguated.xpath.replace(/'/g, "\\'")}')`); for (const retryCmd of retryCommands) { - if (!(await action.attempt(retryCmd, explanation, true))) { + if (!(await action.attempt(retryCmd, explanation))) { attempts.push({ command: retryCmd, success: false, error: action.lastError?.toString() }); continue; } @@ -209,7 +208,7 @@ export function createCodeceptJSTools(explorer: Explorer, task: Task) { const attempts: Array<{ command: string; success: boolean; error?: string }> = []; for (const command of commands) { - const success = await action.attempt(command, explanation, true); + const success = await action.attempt(command, explanation); attempts.push({ command, success, @@ -1016,7 +1015,7 @@ export function createAgentTools({ } const action = explorer.createAction(); - const visible = await action.attempt(`I.seeElement(${JSON.stringify(xpath)})`, 'xpathCheck visibility', false); + const visible = await action.attempt(`I.seeElement(${JSON.stringify(xpath)})`, 'xpathCheck visibility'); const liveElement = await WebElement.fromPlaywrightLocator(explorer.playwrightHelper.page.locator(`xpath=${xpath}`)); const matchesSummary = result.elements.map((el, i) => `${i + 1}. <${el.tag} ${el.keyAttrs}> text="${el.text}" html: ${el.outerHTML}`).join('\n'); diff --git a/src/explorbot.ts b/src/explorbot.ts index 0216a13..9f7e06a 100644 --- a/src/explorbot.ts +++ b/src/explorbot.ts @@ -54,7 +54,6 @@ export class ExplorBot { private config!: ExplorbotConfig; private options: ExplorBotOptions; private userResolveFn: UserResolveFunction | null = null; - public needsInput = false; private currentPlan?: Plan; private planFeature?: string; lastPlanError: Error | null = null; @@ -92,7 +91,6 @@ export class ExplorBot { if (!this.options.incognito) { await this.agentExperienceCompactor().autocompact(); } - if (this.userResolveFn) this.explorer.setUserResolve(this.userResolveFn); } catch (error) { const message = error instanceof Error ? error.message : String(error); console.error('\nFailed to start:', message); @@ -154,13 +152,6 @@ export class ExplorBot { getOptions(): ExplorBotOptions { return this.options; } - isReady(): boolean { - return this.explorer?.isStarted; - } - - getConfigParser(): ConfigParser { - return this.configParser; - } getProvider(): AIProvider { return this.provider; @@ -173,12 +164,9 @@ export class ExplorBot { config: this.config, }); - const agentEmoji = (agent as any).emoji || ''; const agentName = (agent as any).constructor.name.toLowerCase(); tag('debug').log(`Created ${agentName} agent`); - // Agent is stored by the calling method using a string key - return agent; } @@ -188,7 +176,7 @@ export class ExplorBot { agentNavigator(): Navigator { return (this.agents.navigator ||= this.createAgent(({ ai, explorer }) => { - return new Navigator(explorer, ai, this.agentExperienceCompactor(), explorer.getStateManager().getExperienceTracker()); + return new Navigator(explorer, ai, explorer.getStateManager().getExperienceTracker()); })); } @@ -246,10 +234,6 @@ export class ExplorBot { agentCaptain(): Captain { if (!this.agents.captain) { this.agents.captain = new Captain(this); - - const qm = this.agentQuartermaster(); - if (qm) this.agents.captain.setQuartermaster(qm); - this.agents.captain.setHistorian(this.agentHistorian()); } return this.agents.captain; } @@ -290,8 +274,6 @@ export class ExplorBot { const tools = createAgentTools({ explorer, researcher, navigator }); return new Rerunner(explorer, ai, tools); }); - const qm = this.agentQuartermaster(); - if (qm) this.agents.rerunner.setQuartermaster(qm); this.agents.rerunner.setHistorian(this.agentHistorian()); } return this.agents.rerunner; diff --git a/src/explorer.ts b/src/explorer.ts index 0dc83ba..5cb7eed 100644 --- a/src/explorer.ts +++ b/src/explorer.ts @@ -14,8 +14,7 @@ import { visuallyAnnotateContainers } from './ai/researcher/coordinates.ts'; import { RequestStore } from './api/request-store.ts'; import { XhrCapture } from './api/xhr-capture.ts'; import type { ExplorbotConfig } from './config.js'; -import { ConfigParser, outputPath } from './config.js'; -import type { UserResolveFunction } from './explorbot.js'; +import { ConfigParser } from './config.js'; import { KnowledgeTracker } from './knowledge-tracker.js'; import { PlaywrightRecorder } from './playwright-recorder.ts'; import { Reporter } from './reporter.ts'; @@ -64,7 +63,6 @@ class Explorer { private stateManager!: StateManager; private knowledgeTracker!: KnowledgeTracker; config: ExplorbotConfig; - private userResolveFn: UserResolveFunction | null = null; private options?: { show?: boolean; headless?: boolean; incognito?: boolean; session?: string }; private reporter!: Reporter; private otherTabs: TabInfo[] = []; @@ -164,17 +162,9 @@ class Explorer { } public getConfig(): ExplorbotConfig { - if (!this.config) { - throw new Error('Configuration not loaded. Call run() first.'); - } return this.config; } - public getConfigPath(): string | null { - const configParser = ConfigParser.getInstance(); - return configParser.getConfigPath(); - } - public getAIProvider(): AIProvider { return this.aiProvider; } @@ -183,10 +173,6 @@ class Explorer { return this.stateManager; } - public getCurrentUrl(): string { - return this.stateManager.getCurrentState()!.url || '?'; - } - public getKnowledgeTracker(): KnowledgeTracker { return this.knowledgeTracker; } @@ -227,10 +213,6 @@ class Explorer { return; } - if (!this.config) { - await this.initializeContainer(); - } - await codeceptjs.recorder.start(); await codeceptjs.container.started(null); storeListener(); @@ -359,8 +341,8 @@ class Explorer { return this.runWithBrowserRecovery('executeAction', () => this.createAction().execute(code)); } - async attemptAction(code: string, originalMessage?: string, experience = true): Promise { - return this.runWithBrowserRecovery('attemptAction', () => this.createAction().attempt(code, originalMessage, experience)); + async attemptAction(code: string, originalMessage?: string): Promise { + return this.runWithBrowserRecovery('attemptAction', () => this.createAction().attempt(code, originalMessage)); } getPlaywrightRecorder(): PlaywrightRecorder { @@ -459,11 +441,6 @@ class Explorer { } } - async reload() { - await this.closeOtherTabs(); - await this.playwrightHelper.page.reload(); - } - private resolveBrowserUrl(url?: string): string | null { if (!url) return null; try { @@ -608,10 +585,6 @@ class Explorer { this.otherTabs = []; } - setUserResolve(userResolveFn: UserResolveFunction): void { - this.userResolveFn = userResolveFn; - } - private listenToStateChanged(): void { if (!this.playwrightHelper) { debugLog('Playwright helper not available for state monitoring'); @@ -760,10 +733,6 @@ class Explorer { return true; } - async ensureActiveTestPageAvailable(): Promise { - return this.ensurePageAvailable(); - } - async handleExecutionError(error: unknown): Promise { const message = error instanceof Error ? error.message : String(error); tag('error').log(`Browser execution error: ${message}`); @@ -873,26 +842,6 @@ class Explorer { this.observedTestPages.clear(); } - async hasPlaywrightLocator(locatorFn: (page: any) => any, opts: { multiple?: boolean; contents?: boolean; success?: (locator: any) => Promise | void } = {}): Promise { - try { - const pwLocator = locatorFn(this.playwrightHelper.page); - const count = await pwLocator.count(); - if (opts.multiple ? count === 0 : count !== 1) return false; - if (opts.contents) { - const html = await pwLocator.first().innerHTML(); - if (!html?.trim()) return false; - } - if (opts.success) await opts.success(pwLocator); - return true; - } catch (error) { - if (this.isFatalBrowserError(error)) { - tag('warning').log(`hasPlaywrightLocator: ${error instanceof Error ? error.message : error}`); - await this.recoverFromBrowserError(); - } - return false; - } - } - async playwrightLocatorCount(locatorFn: (page: any) => any): Promise { try { const pwLocator = locatorFn(this.playwrightHelper.page); diff --git a/src/state-manager.ts b/src/state-manager.ts index b1dba9d..20dc1d7 100644 --- a/src/state-manager.ts +++ b/src/state-manager.ts @@ -2,10 +2,10 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs'; import { dirname, join } from 'node:path'; import matter from 'gray-matter'; import { ActionResult } from './action-result.js'; -import { ConfigParser, outputPath } from './config.js'; +import { ConfigParser } from './config.js'; import { ExperienceTracker } from './experience-tracker.js'; import { detectFocusArea } from './utils/aria.js'; -import { createDebug, tag } from './utils/logger.js'; +import { createDebug } from './utils/logger.js'; import { extractStatePath } from './utils/url-matcher.js'; const debugLog = createDebug('explorbot:state'); @@ -134,11 +134,6 @@ export class StateManager { }); } - /** - * Extract state path from full URL - * Removes domain, port, protocol - * Keeps path, query, and hash: /path/to/page?tab=users#section - */ /** * Update current state from ActionResult and record transition if state changed */ @@ -402,24 +397,6 @@ export class StateManager { /** * Get all context for current state (knowledge + experience) */ - getCurrentContext(): { - state: WebPageState; - knowledge: Knowledge[]; - experience: string[]; - recentTransitions: StateTransition[]; - } { - if (!this.currentState) { - throw new Error('No current state available'); - } - - return { - state: this.currentState, - knowledge: this.getRelevantKnowledge(), - experience: this.getRelevantExperience(), - recentTransitions: this.getRecentTransitions(), - }; - } - /** * Check if we've been in this state before */ @@ -455,45 +432,6 @@ export class StateManager { * If the last transition changed URL, returns the fromState (for URL change detection). * Otherwise returns the most recent toState with content (for diffing). */ - getPreviousState(): WebPageState | null { - if (this.stateHistory.length === 0) return null; - - const lastTransition = this.stateHistory[this.stateHistory.length - 1]; - - if (lastTransition.fromState?.url !== lastTransition.toState?.url) { - return lastTransition.fromState; - } - - for (let i = this.stateHistory.length - 1; i >= 0; i--) { - const toState = this.stateHistory[i].toState; - - if (!toState) continue; - if (toState.id === this.currentState?.id) continue; - - if (toState.html || toState.ariaSnapshot) { - return toState; - } - } - - return null; - } - - /** - * Load HTML content from file - */ - loadHtmlFromFile(htmlFile: string): string | null { - try { - const filePath = outputPath('states', htmlFile); - if (existsSync(filePath)) { - return readFileSync(filePath, 'utf8'); - } - return null; - } catch (error) { - debugLog('Failed to load HTML from file:', error); - return null; - } - } - /** * Clear state history (useful for testing or reset) * Note: This preserves the current state, only clears navigation history diff --git a/tests/integration/researcher-browser.test.ts b/tests/integration/researcher-browser.test.ts index b4d9a8c..1def527 100644 --- a/tests/integration/researcher-browser.test.ts +++ b/tests/integration/researcher-browser.test.ts @@ -103,7 +103,6 @@ describe('Researcher with real browser + aimock', () => { runWithBrowserRecovery: async (_label: string, operation: () => Promise) => operation(), createAction: () => ({ capturePageState: async () => ActionResult.fromState(state), - caputrePageWithScreenshot: async () => ActionResult.fromState(state), }), playwrightLocatorCount: async (cb: (p: any) => any) => { const locator = cb(page); diff --git a/tests/integration/researcher-sections.test.ts b/tests/integration/researcher-sections.test.ts index bf0eece..79726d3 100644 --- a/tests/integration/researcher-sections.test.ts +++ b/tests/integration/researcher-sections.test.ts @@ -47,7 +47,6 @@ function createMockExplorer(configOverrides: Record = {}, playw runWithBrowserRecovery: async (_label: string, operation: () => Promise) => operation(), createAction: () => ({ capturePageState: async () => ActionResult.fromState(fakeState), - caputrePageWithScreenshot: async () => ActionResult.fromState(fakeState), }), playwrightLocatorCount, playwrightHelper: { page: {} }, diff --git a/tests/integration/researcher.test.ts b/tests/integration/researcher.test.ts index 79da34b..38cf8a3 100644 --- a/tests/integration/researcher.test.ts +++ b/tests/integration/researcher.test.ts @@ -73,7 +73,6 @@ function createMockExplorer(state = fakeState) { runWithBrowserRecovery: async (_label: string, operation: () => Promise) => operation(), createAction: () => ({ capturePageState: async () => ActionResult.fromState(state), - caputrePageWithScreenshot: async () => ActionResult.fromState(state), }), playwrightLocatorCount: async () => 1, playwrightHelper: { page: {} }, diff --git a/tests/unit/conversation.test.ts b/tests/unit/conversation.test.ts index 321c016..def6229 100644 --- a/tests/unit/conversation.test.ts +++ b/tests/unit/conversation.test.ts @@ -81,7 +81,7 @@ describe('Conversation', () => { it('should not affect non-string message content', () => { const conversation = new Conversation(); conversation.addUserText('Text with content'); - conversation.addUserImage('base64encodedimage'); + conversation.messages.push({ role: 'user', content: [{ type: 'text', text: 'x' }] }); conversation.addUserText('Another content'); conversation.cleanupTag('page_html', '...cleaned...'); @@ -300,7 +300,7 @@ describe('Conversation', () => { it('should ignore non-string message content', () => { const conversation = new Conversation(); - conversation.addUserImage('base64encodedimage'); + conversation.messages.push({ role: 'user', content: [{ type: 'text', text: 'x' }] }); expect(conversation.hasTag('any_tag')).toBe(false); }); diff --git a/tests/unit/explorer-recovery-url.test.ts b/tests/unit/explorer-recovery-url.test.ts index d492bb2..aa43e4d 100644 --- a/tests/unit/explorer-recovery-url.test.ts +++ b/tests/unit/explorer-recovery-url.test.ts @@ -126,7 +126,7 @@ describe('Explorer recovery URL resolution', () => { }, }); - const result = await explorer.attemptAction('I.click("Menu")', undefined, false); + const result = await explorer.attemptAction('I.click("Menu")', undefined); expect(result).toBe(true); expect(attempts).toBe(2);