mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-21 12:55:09 +00:00
* clean-up bunch of files for re-write * more clean-up and adding basic agent * Minor fix moved types into respective files. * Deleted bunch of old files backup Update gitignore Deleted a bunch of files Remove message manager Deleted old docs Update rules rename Profiler to profiler * Temporarily adding old code * Adding two small things back * backup * Implemented LangChainProvider and updated cursor rules backup LangChainProvider curosr rules * Implement tests for LangChainProvider -- unit test and integration test integration test passes integration test backup * Tool Design Tools Desing tools design * NavigationTool ready NavigationTool ready NavigationTool ready NaivgationTool ready backup * MessageManager MessageManager backup * Fixed integration test * Agent design new Updated agent design and added bunch of /NTN commands agent new design * Delete old agent design * MessageManagerReadOnly class * PlannerTool ready PlannerTool almost ready * ToolManager and DoneTool * Integration of BrowserAgent * BrowserAgent implementation v0.1 * BrowserAgent small fix v0.2 * Tool calling design too call design tool design claude * Update agent tool design with // NTN * add zod-to-json npm install * BrowserAGent v0.3 * BrowserAgent v0.4 * BrowserAgent v0.5 * fixes * Build error fixes in my NEWLY added code build errors fix * Build error fixes in old code (integration work) backup * Comment StreamEventProcessor for now, it is not used * Small build error fix * Small rename * Added integration test to check structuredLLM and changed to 4o-mini change default to nxtscape integration test * Small docstring * Simplified BrowserAgent code and added integration test Simplified BrowserAgent code BrowserAGent integrationt est * Update CLAUDE.md with project memory and instructions on how to write code Update CLAUDE.md with project memory and instructions on how to write code Project Memory * Just a mova.. Moved ToolManager outside. Build works. * TabOperations tool TabOperations Tool and fixing some test tab operations * Update CLAUDE.md * Added ClassificationTool classifiction tool classification prommpt * Refactored and simplified PlannerTool unit test and integration test * Updated Plnnaer tool * Update CLAUDE.md * BrowserAgent modified to do classification BrowserAgent with classification * minor fix to ToolManager * Instead of ToolCall and ToolResult -- just updating message manager once * minor fix to BrowserAgent integration test * Changed done to "done_tool" * Updated CLAUDE.md to reflect understanding of claude * Uncommented stream event processor * Renamed EventBus to StreamEventBus * Commented StreamEventProcessor * Event Processor * Integrated EventProcessor with BrowserAgent Added EventProcessor to BrowserAgetn * Renamed StreamEventBus to EventBus * Made EventBus required parameter in ExecutionContext * PlanGenerator rewrite PlanGenerator rewrite backup * For simple task, explicitly tell it to call done tool * Max attempts for simple task * backup * Revert "backup" This reverts commit 7d79a3d4d5774bfef79ec9827878b74edad3593f. * Consolidating where EventBus and EventProcessor are created and initialized backup * Update CLAUDE.md Update CLAUDE.md * Improving agent loop code Cleaned up processTooCall classification task * Create test-writer subAgent test-agent-prompt test agent prompt test-agent-prompt Update test-writer.md * BrowserAgent test Browseragent test BrowserAgent test * BrowserAgent refactor backup backup * Minor fixes * Minor fix * minor change -- NEW AGENT LOOP IS WORKING WELL * Update cursor rules * Small change * Improved BrowserAgent integration test Improved BrowserAgent integration test * Small change * Update CLAUDE.md * Different tools * FindElementTool is ready Find element update backup find element backup * Updated to test strings to say "tests..." * ScrollTool is ready * RefreshStateTool is updated as well * MessageManager updated * SearchTool is ready backup * Interaction Element is also ready * Add debugMessage emitter * ValidatorTool ready and tests are passing Validation Tool validator tool backup backup * GroupTabs tool ready * Registered all the tools * Planning changed to 5 steps * BrowserAgent integration test fix * Minor string changes * backup * Removed too many confusing events in EventProcessor -- there is only event.info right now * Abort control implemented backup Abort * Formatter for toolResult Formatter for toolResult backup * Always render using Markdown * Minor fix --------- Co-authored-by: Nikhil Sonti <nikhilsv92@gmail.com>
270 lines
8.9 KiB
TypeScript
270 lines
8.9 KiB
TypeScript
import { z } from 'zod';
|
|
import { NxtscapeTool } from '../base/NxtscapeTool';
|
|
import { ToolConfig } from '../base/ToolConfig';
|
|
import { ExecutionContext } from '@/lib/runtime/ExecutionContext';
|
|
import { Session, SessionSchema } from './SessionManagementTool';
|
|
|
|
/**
|
|
* Schema for resume session input
|
|
*/
|
|
export const ResumeSessionInputSchema = z.object({
|
|
sessionId: z.string().min(1), // ID of session to resume (required)
|
|
newWindow: z.boolean().optional(), // Whether to open in new window (default true)
|
|
focusWindow: z.boolean().optional() // Whether to focus the window after opening (default true)
|
|
});
|
|
|
|
export type ResumeSessionInput = z.infer<typeof ResumeSessionInputSchema>;
|
|
|
|
/**
|
|
* Schema for session execution output
|
|
*/
|
|
export const SessionExecutionOutputSchema = z.object({
|
|
success: z.boolean(), // Whether the operation succeeded
|
|
sessionName: z.string().optional(), // Name of resumed session
|
|
tabsOpened: z.number(), // Number of tabs successfully opened
|
|
windowId: z.number().optional(), // ID of window where tabs were opened
|
|
failedTabs: z.array(z.object({
|
|
url: z.string(), // URL that failed to open
|
|
title: z.string(), // Title of tab that failed
|
|
error: z.string() // Error message
|
|
})).optional(), // Tabs that failed to open
|
|
message: z.string() // Human-readable summary message
|
|
});
|
|
|
|
export type SessionExecutionOutput = z.infer<typeof SessionExecutionOutputSchema>;
|
|
|
|
/**
|
|
* Tool for executing/resuming saved browser sessions
|
|
*/
|
|
export class SessionExecutionTool extends NxtscapeTool<ResumeSessionInput, SessionExecutionOutput> {
|
|
private static readonly STORAGE_KEY = 'nxtscape_sessions';
|
|
|
|
constructor(executionContext: ExecutionContext) {
|
|
const config: ToolConfig<ResumeSessionInput, SessionExecutionOutput> = {
|
|
name: 'session_execution',
|
|
description: 'Resume a saved browser session by opening all its tabs. By default opens in a new window.',
|
|
category: 'sessions',
|
|
version: '1.0.0',
|
|
inputSchema: ResumeSessionInputSchema,
|
|
outputSchema: SessionExecutionOutputSchema,
|
|
examples: [
|
|
{
|
|
description: 'Resume a session in a new window',
|
|
input: {
|
|
sessionId: 'sess_123abc',
|
|
newWindow: true
|
|
},
|
|
output: {
|
|
success: true,
|
|
sessionName: 'Morning Work Session',
|
|
tabsOpened: 5,
|
|
windowId: 2,
|
|
message: 'Successfully resumed "Morning Work Session" with 5 tabs in new window'
|
|
}
|
|
},
|
|
{
|
|
description: 'Resume a session in current window',
|
|
input: {
|
|
sessionId: 'sess_456def',
|
|
newWindow: false,
|
|
focusWindow: true
|
|
},
|
|
output: {
|
|
success: true,
|
|
sessionName: 'Research Project',
|
|
tabsOpened: 3,
|
|
windowId: 1,
|
|
message: 'Successfully resumed "Research Project" with 3 tabs in current window'
|
|
}
|
|
}
|
|
],
|
|
streamingConfig: {
|
|
displayName: 'Session Execution',
|
|
icon: '🔄',
|
|
progressMessage: 'Resuming session...'
|
|
}
|
|
};
|
|
|
|
super(config, executionContext);
|
|
}
|
|
|
|
/**
|
|
* Override: Generate contextual display message based on arguments
|
|
*/
|
|
getProgressMessage(args: ResumeSessionInput): string {
|
|
try {
|
|
// Note: args should already be parsed by StreamEventProcessor
|
|
|
|
const sessionId = args?.sessionId;
|
|
const newWindow = args?.newWindow ?? true;
|
|
|
|
if (sessionId) {
|
|
return newWindow
|
|
? `Opening session ${sessionId} in new window...`
|
|
: `Opening session ${sessionId} in current window...`;
|
|
}
|
|
|
|
return 'Resuming session...';
|
|
} catch {
|
|
return 'Resuming session...';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Override: Format session execution result for display
|
|
*/
|
|
FormatResultForUI(output: SessionExecutionOutput): string {
|
|
if (!output.success) {
|
|
return `❌ ${output.message}`;
|
|
}
|
|
|
|
const tabText = output.tabsOpened === 1 ? 'tab' : 'tabs';
|
|
let result = `🔄 Resumed "${output.sessionName}" with ${output.tabsOpened} ${tabText}`;
|
|
|
|
if (output.failedTabs && output.failedTabs.length > 0) {
|
|
const failedCount = output.failedTabs.length;
|
|
const failedText = failedCount === 1 ? 'tab' : 'tabs';
|
|
result += ` (${failedCount} ${failedText} failed to open)`;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
protected async execute(input: ResumeSessionInput): Promise<SessionExecutionOutput> {
|
|
try {
|
|
// Load the session
|
|
const session = await this.loadSession(input.sessionId);
|
|
|
|
if (!session) {
|
|
return {
|
|
success: false,
|
|
tabsOpened: 0,
|
|
message: `Session with ID ${input.sessionId} not found`
|
|
};
|
|
}
|
|
|
|
if (session.tabs.length === 0) {
|
|
return {
|
|
success: false,
|
|
sessionName: session.name,
|
|
tabsOpened: 0,
|
|
message: `Session "${session.name}" has no tabs to open`
|
|
};
|
|
}
|
|
|
|
// Determine where to open tabs
|
|
let targetWindowId: number | undefined;
|
|
const newWindow = input.newWindow ?? true;
|
|
const focusWindow = input.focusWindow ?? true;
|
|
|
|
if (newWindow) {
|
|
// Create new window with first tab
|
|
const firstTab = session.tabs[0];
|
|
const newWindowObj = await chrome.windows.create({
|
|
url: firstTab.url,
|
|
focused: focusWindow
|
|
});
|
|
|
|
if (!newWindowObj?.id) {
|
|
throw new Error('Failed to create new window');
|
|
}
|
|
|
|
targetWindowId = newWindowObj.id;
|
|
} else {
|
|
// Use current window - leverage BrowserContext's getCurrentWindow
|
|
try {
|
|
const currentWindow = await this.browserContext.getCurrentWindow();
|
|
targetWindowId = currentWindow.id;
|
|
} catch (error) {
|
|
throw new Error('Could not determine target window');
|
|
}
|
|
}
|
|
|
|
// Open remaining tabs
|
|
const tabsToOpen = newWindow ? session.tabs.slice(1) : session.tabs;
|
|
const failedTabs = [];
|
|
let successCount = newWindow ? 1 : 0; // First tab already opened if new window
|
|
|
|
// If we're opening in the current window and using BrowserContext features,
|
|
// we could use openTab for the first tab to get better integration
|
|
// For now, we'll keep using chrome.tabs.create for consistency
|
|
|
|
for (const tab of tabsToOpen) {
|
|
try {
|
|
await chrome.tabs.create({
|
|
windowId: targetWindowId,
|
|
url: tab.url,
|
|
active: false // Don't focus each tab as it opens
|
|
});
|
|
successCount++;
|
|
} catch (error) {
|
|
console.warn('[session_execution] Failed to open tab:', tab.url, error);
|
|
failedTabs.push({
|
|
url: tab.url,
|
|
title: tab.title,
|
|
error: error instanceof Error ? error.message : String(error)
|
|
});
|
|
}
|
|
}
|
|
|
|
// Focus the window if requested
|
|
if (focusWindow && targetWindowId) {
|
|
try {
|
|
await chrome.windows.update(targetWindowId, { focused: true });
|
|
} catch (error) {
|
|
console.warn('[session_execution] Failed to focus window:', error);
|
|
}
|
|
}
|
|
|
|
const totalTabs = session.tabs.length;
|
|
const hasFailures = failedTabs.length > 0;
|
|
const windowLocation = newWindow ? 'new window' : 'current window';
|
|
|
|
return {
|
|
success: successCount > 0,
|
|
sessionName: session.name,
|
|
tabsOpened: successCount,
|
|
windowId: targetWindowId,
|
|
failedTabs: hasFailures ? failedTabs : undefined,
|
|
message: hasFailures
|
|
? `Resumed "${session.name}" with ${successCount}/${totalTabs} tabs in ${windowLocation} (${failedTabs.length} failed)`
|
|
: `Successfully resumed "${session.name}" with ${successCount} tab(s) in ${windowLocation}`
|
|
};
|
|
} catch (error) {
|
|
console.error('[session_execution] Error:', error);
|
|
return {
|
|
success: false,
|
|
tabsOpened: 0,
|
|
message: `Error resuming session: ${error instanceof Error ? error.message : String(error)}`
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a specific session from chrome storage
|
|
*/
|
|
private async loadSession(sessionId: string): Promise<Session | null> {
|
|
try {
|
|
const result = await chrome.storage.local.get([SessionExecutionTool.STORAGE_KEY]);
|
|
const sessions = result[SessionExecutionTool.STORAGE_KEY] || [];
|
|
|
|
// Find the session by ID
|
|
const sessionData = sessions.find((session: any) => session.id === sessionId);
|
|
|
|
if (!sessionData) {
|
|
return null;
|
|
}
|
|
|
|
// Validate session against schema
|
|
try {
|
|
return SessionSchema.parse(sessionData);
|
|
} catch (error) {
|
|
console.warn('[session_execution] Invalid session data found:', error);
|
|
return null;
|
|
}
|
|
} catch (error) {
|
|
throw new Error(`Failed to load session from storage: ${error instanceof Error ? error.message : String(error)}`);
|
|
}
|
|
}
|
|
}
|