How ego-lite Resolves and Executes Site-Specific Tools (Node.js and Browser-Side)
ego-lite resolves site-specific tools by scanning a configurable learnings/ directory for manifests matching a URL's hostname, then dynamically importing Node.js tools or executing browser tools via Chrome DevTools Protocol (CDP) evaluation.
The ego-lite browser automation framework includes a pluggable site-skill subsystem that lets agents discover, load, and run custom tools defined for individual websites. This architecture supports both Node.js tools (executed in the agent's Node runtime) and browser-side tools (executed inside the remote browser page). This article explains the full resolution and execution flow with inline source references to the actual implementation.
Site-Skill Discovery: Matching URLs to Manifests
ego-lite begins tool resolution by identifying which site-skill, if any, applies to the current URL. This process involves four coordinated steps across the learning subsystem.
1. Locate the Site-Skills Root Directory
The siteSkillsRoot() function determines where site-specific learnings are stored. It prefers the EGO_BROWSER_AGENT_WORKSPACE environment variable when available, falling back to the repository-bundled learnings folder:
// package/ego-browser/src/learning/check-domain-learning.js
export function siteSkillsRoot(agentWorkspace) {
return agentWorkspace
? resolve(agentWorkspace, "learnings")
: resolve(__dirname, "..", "..", "learnings");
}
This allows deployments to separate code from data, storing per-site customizations outside the main package.
2. Enumerate Candidate Site Directories
The iterLearningDirs(root) function reads subdirectories under the root, returning each potential site-skill directory for manifest inspection:
// package/ego-browser/src/learning/check-domain-learning.js
export async function iterLearningDirs(root) {
const entries = await readdir(root, { withFileTypes: true });
return entries.filter((e) => e.isDirectory()).map((e) => join(root, e.name));
}
3. Load and Parse Manifests
Each site-skill directory must contain a manifest.json. The loadLearningManifest(siteDir) function reads and parses this file:
// package/ego-browser/src/learning/check-domain-learning.js
export async function loadLearningManifest(siteDir) {
const manifestPath = join(siteDir, "manifest.json");
const data = await readFile(manifestPath, "utf8");
return JSON.parse(data);
}
A valid manifest includes a domain field (the hostname it matches) and tool definitions under nodeTools and/or browserTools keys.
4. Match Hostname to Domain
The siteSkillsForUrl(url) function extracts the hostname and returns all manifests whose domain field matches:
// package/ego-browser/src/learning/check-domain-learning.js
export async function siteSkillsForUrl(url, options = {}) {
const hostname = urlHostname(url);
const roots = [siteSkillsRoot(options.agentWorkspace)];
const matches = [];
for (const root of roots) {
for (const siteDir of await iterLearningDirs(root)) {
const manifest = await loadLearningManifest(siteDir);
if (manifest.domain && manifest.domain === hostname) {
matches.push({ id: manifest.id, name: manifest.name, path: siteDir, ...manifest });
}
}
}
return matches;
}
The urlHostname() helper safely extracts the hostname using the standard URL constructor.
Aggregating Learned Context
Once matching site-skills are identified, loadLearnedContext(url) gathers all relevant information—including notes and tool signatures—into a unified structure passed to the agent:
// package/ego-browser/src/learning/index.ts
export async function loadLearnedContext(url: string, options = {}) {
const skills = await siteSkillsForUrl(url, options);
const context = {
notes: [] as string[],
nodeTools: {} as Record<string, ToolSchema>,
browserTools: {} as Record<string, ToolSchema>,
};
for (const skill of skills) {
// Load notes from notes/*.md files
// Merge tool schemas from manifest
}
return context;
}
At this point, the agent has:
siteId— the unique identifier from the manifestnodeTools— a map of tool names to{path, callable, args, ...}schemasbrowserTools— a map of tool names to{path, args, ...}schemas
Executing Node.js Site Tools
Node.js tools are standard JavaScript modules executed directly in the agent's Node runtime. The runSiteTool() function handles their resolution and invocation.
Tool Resolution and Validation
// package/ego-browser/src/helpers.ts
export async function runSiteTool(siteId, toolName, args = {}, ctx = {}, options = {}) {
const { siteDir, manifest } = await findSiteSkill(siteId, options);
const schema = toolSchemas(manifest, "nodeTools")[toolName];
const callable = schema?.callable;
if (!callable) {
throw new Error(`Node tool ${toolName} not found`);
}
// ...
}
The findSiteSkill() helper locates the manifest by siteId, and toolSchemas() extracts the tool definitions. The callable field specifies which exported function to invoke.
Path Security and Dynamic Import
Before loading, relativeSitePath() validates that the declared path is a safe relative path within the site-skill directory—preventing directory traversal attacks:
// package/ego-browser/src/learning/index.ts
export function relativeSitePath(siteDir: string, relPath: string, label: string): string {
const fullPath = resolve(siteDir, relPath);
if (!fullPath.startsWith(siteDir)) {
throw new Error(`${label} path escapes site directory: ${relPath}`);
}
return fullPath;
}
The tool module is then imported with cache-busting to ensure fresh loads on every call:
// package/ego-browser/src/helpers.ts
const toolPath = relativeSitePath(siteDir, schema.path, "Node tool");
const mod = await import(`${pathToFileURL(toolPath).href}?t=${Date.now()}`);
const fn = mod[callable];
if (typeof fn !== "function") {
throw new Error(`Callable ${callable} not a function`);
}
return fn(ctx, args);
The function receives ctx (the current browsing context) and args (the tool arguments), returning its result directly to the agent.
Executing Browser-Side Site Tools
Browser tools run inside the remote page's JavaScript environment, enabling direct DOM manipulation and access to page JavaScript state. ego-lite implements this via Chrome DevTools Protocol (CDP) evaluation.
Source Loading and Wrapping
The runSiteBrowserTool() function orchestrates browser tool execution:
// package/ego-browser/src/helpers.ts
export async function runSiteBrowserTool(siteId, toolName, args = {}, ctx = {}, options = {}) {
const { siteDir, manifest } = await findSiteSkill(siteId, options);
const schema = toolSchemas(manifest, "browserTools")[toolName];
const source = await loadBrowserToolSource(siteId, toolName, options);
const wrapped = wrapBrowserTool(source, args);
const result = await cdpEval(wrapped);
return result;
}
The loadBrowserToolSource() function reads the tool file from disk with the same path validation as Node tools. Then wrapBrowserTool() constructs a self-executing async function that injects arguments as a JSON literal:
// package/ego-browser/src/learning/index.ts
export function wrapBrowserTool(source: string, args: Record<string, unknown>): string {
return `(async () => { const __egoBrowserTool = ${source}; return await __egoBrowserTool(${JSON.stringify(args)}); })()`;
}
This wrapper ensures:
- The tool code executes in an async context
- Arguments are safely serialized and available to the tool function
- The return value is properly awaited and returned
CDP Evaluation in Page Context
The wrapped code is sent to the browser via cdpEval():
// package/ego-browser/src/cdp-eval.ts
export async function cdpEval(expression: string): Promise<unknown> {
const runtime = await getRuntime();
const result = await runtime.cdp('Runtime.evaluate', {
expression,
awaitPromise: true
});
if (result.exceptionDetails) {
throw new Error(`CDP evaluation error: ${result.exceptionDetails.text}`);
}
return result.result.value;
}
Key aspects of this implementation:
Runtime.evaluate— evaluates JavaScript in the page's main execution contextawaitPromise: true— automatically awaits returned promises- Exception propagation — CDP exceptions are converted to JavaScript errors
The result is returned to the agent, completing the browser tool execution cycle.
Practical Usage Examples
// 1️⃣ Load learned context for a page
const ctx = await site.learnContext('https://example.com/dashboard');
console.log('Available tools:', ctx.tools);
// 2️⃣ Run a Node-side tool (data extraction, API calls, etc.)
const resultNode = await site.runTool('example-com', 'extractData', {
selector: '.item'
});
console.log('Node tool result →', resultNode);
// 3️⃣ Run a browser-side tool (DOM interaction, page JavaScript)
const resultBrowser = await site.runBrowserTool('example-com', 'clickBuy', {
productId: 42
});
console.log('Browser tool result →', resultBrowser);
These calls are thin wrappers around the functions described above, exposed through the site helper object in package/ego-browser/src/helpers.ts.
Architecture Benefits
This dual-runtime design provides significant flexibility:
- Node.js tools — Ideal for data processing, external API calls, file operations, and complex logic requiring Node's standard library
- Browser tools — Essential for DOM manipulation, triggering JavaScript event handlers, accessing page globals, and working with single-page applications
- Unified discovery — Both tool types share the same manifest-based registration and URL matching system
- Security boundaries — Path validation prevents tools from escaping their site-skill directory; CDP execution runs in the browser's sandboxed context
Summary
- Site-skill discovery scans the
learnings/directory, matches manifests by hostname, and aggregates tool schemas and notes - Node.js tools are dynamically imported with cache-busting, validated paths, and invoked as exported functions with context and arguments
- Browser tools are read from disk, wrapped as async IIFEs with injected arguments, and executed via CDP's
Runtime.evaluatein the page context - Security is enforced through path traversal checks and sandboxed browser execution
- Extensibility allows new site customizations by simply adding manifest files and associated scripts without modifying core code
Frequently Asked Questions
How does ego-lite prevent malicious tools from accessing arbitrary files?
ego-lite uses relativeSitePath() in package/ego-browser/src/learning/index.ts to validate that all tool paths resolve within the site-skill directory. Any path attempting directory traversal (e.g., ../../../etc/passwd) triggers an error before file access occurs.
Can browser tools access Node.js APIs?
No. Browser tools execute entirely within the browser's JavaScript context via CDP. They do not have access to Node.js APIs, require(), or the file system. This isolation is fundamental to the security model—browser tools can only interact with the page and return serializable values.
What happens if a browser tool throws an error?
The cdpEval() function in package/ego-browser/src/cdp-eval.ts checks result.exceptionDetails after CDP evaluation. If the tool throws, the exception details are extracted and propagated as a JavaScript Error in the agent's Node runtime, allowing standard try/catch handling.
How are tool arguments validated?
Currently, tool schemas in manifests define expected arguments, but runtime validation relies on the tool implementation. The agent can use the schema information from loadLearnedContext() to pre-validate calls, though this is not enforced automatically in the core execution functions.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →