diff --git a/apps/x/apps/main/src/browser/control-service.ts b/apps/x/apps/main/src/browser/control-service.ts index a7549e8d..7c97ea7a 100644 --- a/apps/x/apps/main/src/browser/control-service.ts +++ b/apps/x/apps/main/src/browser/control-service.ts @@ -254,20 +254,6 @@ export class ElectronBrowserControlService implements IBrowserControlService { const page = await browserViewManager.readPageSummary(signal, { waitForReady: false }) ?? undefined; return buildSuccessResult('wait', `Waited ${duration}ms for the page to settle.`, page); } - - case 'eval': { - const code = input.code; - if (!code) { - return buildErrorResult('eval', 'code is required for eval.'); - } - await browserViewManager.ensureActiveTabReady(signal); - const result = await browserViewManager.executeScript(code, signal); - if (!result.ok) { - return buildErrorResult('eval', result.error); - } - const success = buildSuccessResult('eval', 'Evaluated script in the active tab.'); - return { ...success, result: result.result }; - } } } catch (error) { return buildErrorResult( diff --git a/apps/x/apps/main/src/browser/view.ts b/apps/x/apps/main/src/browser/view.ts index 0b43e346..d319c5fb 100644 --- a/apps/x/apps/main/src/browser/view.ts +++ b/apps/x/apps/main/src/browser/view.ts @@ -78,41 +78,6 @@ function abortIfNeeded(signal?: AbortSignal): void { throw signal.reason instanceof Error ? signal.reason : new Error('Browser action aborted'); } -const EVAL_RESULT_MAX_BYTES = 200_000; - -function safeSerialize(value: unknown): unknown { - const seen = new WeakSet(); - const coerce = (v: unknown): unknown => { - if (v === null || v === undefined) return v; - const t = typeof v; - if (t === 'string' || t === 'number' || t === 'boolean') return v; - if (t === 'bigint') return (v as bigint).toString(); - if (t === 'function' || t === 'symbol') return `[${t}]`; - if (typeof v === 'object') { - if (seen.has(v as object)) return '[circular]'; - seen.add(v as object); - if (Array.isArray(v)) return v.map(coerce); - const out: Record = {}; - for (const [k, val] of Object.entries(v as Record)) { - out[k] = coerce(val); - } - return out; - } - return String(v); - }; - - const coerced = coerce(value); - try { - const json = JSON.stringify(coerced); - if (json && json.length > EVAL_RESULT_MAX_BYTES) { - return { truncated: true, preview: json.slice(0, EVAL_RESULT_MAX_BYTES) }; - } - } catch { - return String(value); - } - return coerced; -} - async function sleep(ms: number, signal?: AbortSignal): Promise { if (ms <= 0) return; abortIfNeeded(signal); @@ -813,17 +778,6 @@ export class BrowserViewManager extends EventEmitter { await this.waitForWebContentsSettle(activeTab, signal); } - async executeScript(code: string, signal?: AbortSignal): Promise<{ ok: true; result: unknown } | { ok: false; error: string }> { - try { - const wrapped = `(async () => { ${code} \n})()`; - const raw = await this.executeOnActiveTab(wrapped, signal); - const serialized = safeSerialize(raw); - return { ok: true, result: serialized }; - } catch (error) { - return { ok: false, error: error instanceof Error ? error.message : 'Script evaluation failed.' }; - } - } - getState(): BrowserState { return this.snapshotState(); } diff --git a/apps/x/packages/core/src/application/assistant/skills/browser-control/skill.ts b/apps/x/packages/core/src/application/assistant/skills/browser-control/skill.ts index 2dfee06e..868ce8e8 100644 --- a/apps/x/packages/core/src/application/assistant/skills/browser-control/skill.ts +++ b/apps/x/packages/core/src/application/assistant/skills/browser-control/skill.ts @@ -94,17 +94,6 @@ Wait for the page to settle, useful after async UI changes. Parameters: - ` + "`ms`" + `: milliseconds to wait (optional) -### eval -Run arbitrary JavaScript in the active tab and return its value. Use this as an escape hatch when the structured actions above are insufficient — for example, submitting a form (` + "`form.submit()`" + `), reading DOM state (` + "`document.querySelector(...).textContent`" + `), or computing something that requires page-scoped APIs. - -Parameters: -- ` + "`code`" + `: JavaScript source. The code runs inside an ` + "`async`" + ` IIFE, so you can ` + "`await`" + ` freely. The final expression's value (or a ` + "`return`" + `ed value) is serialized back. Non-serializable values (DOM nodes, functions) are coerced to placeholder strings. Large results are truncated. - -Example: -- ` + "`{ action: \"eval\", code: \"return document.querySelector('meta[name=user-login]')?.content ?? null\" }`" + ` - -Security: ` + "`eval`" + ` runs in the active tab's origin with the user's cookies. Do not exfiltrate credentials, cookies, or localStorage contents to third-party origins. - ## Companion Tools ### load-browser-skill @@ -112,7 +101,7 @@ Rowboat caches a library of browser skills (from ` + "`browser-use/browser-harne You can also proactively call ` + "`load-browser-skill({ action: \"list\", site: \"\" })`" + ` when you know you're about to work on a site, to see what skills exist even if ` + "`suggestedSkills`" + ` is empty (e.g. before navigating). -These skills are written against a Python harness, so treat them as **reference knowledge** — adapt the recipes into the actions above, especially structured browser actions plus ` + "`eval`" + ` for the ` + "`js(...)`" + ` and ` + "`http_get(...)`" + ` style calls they use. **Do not look for or call ` + "`http-fetch`" + ` — it is not available.** If a harness recipe suggests hitting a public API directly, either use ` + "`eval`" + ` with ` + "`fetch()`" + ` inside the page context when that is truly necessary, or fall back to reading and interacting with the page itself. The selectors, DOM gotchas, and sequencing are the durable part; the exact function names are not. +These skills are written against a Python harness, so treat them as **reference knowledge**. Reuse the selectors, timing, and sequencing, but adapt them to Rowboat's structured browser actions. **Do not look for or call ` + "`http-fetch`" + `.** If a browser-harness recipe suggests ` + "`js(...)`" + ` or ` + "`http_get(...)`" + ` style shortcuts, treat those as non-portable and fall back to reading and interacting with the page itself. ## Important Rules @@ -121,8 +110,7 @@ These skills are written against a Python harness, so treat them as **reference - If the tool says the snapshot is stale, call ` + "`read-page`" + ` again. - After navigation, clicking, typing, pressing, or scrolling, use the returned page snapshot instead of assuming the page state. - **Always check ` + "`suggestedSkills`" + ` after ` + "`navigate`" + `, ` + "`new-tab`" + `, or ` + "`read-page`" + `, and load the matching domain or interaction skill before acting.** Skipping this step is the single most common way to waste a dozen failed clicks on a site whose quirks are already documented. If the array is empty, proceed normally — but don't skip the check. -- Prefer structured actions (click/type/press) over ` + "`eval`" + ` when both work. Reach for ` + "`eval`" + ` when the site fights synthetic events, when you need to submit a form directly, or when you need to read DOM state the structured actions don't surface. -- Do not try to use ` + "`http-fetch`" + `. If a browser-harness recipe mentions ` + "`http_get(...)`" + ` or a public API shortcut, adapt it to ` + "`eval`" + ` or DOM-based browsing instead. +- Do not try to use ` + "`http-fetch`" + `. If a browser-harness recipe mentions ` + "`http_get(...)`" + ` or a public API shortcut, adapt it to DOM-based browsing instead. - Use Rowboat's browser for live interaction. Use web search tools for research where a live session is unnecessary. - Do not wrap browser URLs or browser pages in ` + "```filepath" + ` blocks. Filepath cards are only for real files on disk, not web pages or browser tabs. - If you mention a page the browser opened, use plain text for the URL/title instead of trying to create a clickable file card. diff --git a/apps/x/packages/shared/src/browser-control.ts b/apps/x/packages/shared/src/browser-control.ts index c8277712..e4eb112d 100644 --- a/apps/x/packages/shared/src/browser-control.ts +++ b/apps/x/packages/shared/src/browser-control.ts @@ -51,7 +51,6 @@ export const BrowserControlActionSchema = z.enum([ 'press', 'scroll', 'wait', - 'eval', ]); const BrowserElementTargetFields = { @@ -71,7 +70,6 @@ export const BrowserControlInputSchema = z.object({ ms: z.number().int().positive().max(30000).optional(), maxElements: z.number().int().positive().max(100).optional(), maxTextLength: z.number().int().positive().max(20000).optional(), - code: z.string().min(1).max(50000).optional(), ...BrowserElementTargetFields, }).strict().superRefine((value, ctx) => { const needsElementTarget = value.action === 'click' || value.action === 'type'; @@ -116,14 +114,6 @@ export const BrowserControlInputSchema = z.object({ message: 'Provide an element index or selector.', }); } - - if (value.action === 'eval' && !value.code) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - path: ['code'], - message: 'code is required for eval.', - }); - } }); export const SuggestedBrowserSkillSchema = z.object({ @@ -139,7 +129,6 @@ export const BrowserControlResultSchema = z.object({ error: z.string().optional(), browser: BrowserStateSchema, page: BrowserPageSnapshotSchema.optional(), - result: z.unknown().optional(), suggestedSkills: z.array(SuggestedBrowserSkillSchema).optional(), });