Bots now announce every click and typing step before it runs, and the web app draws a cursor in the bot's colour that glides to that point over the live computer screen, ripples on clicks and shows typing. - contracts: ComputerPointerPayload on the existing thread.computer event - executor: computer_act and browser_act announce pointer steps (best effort); the page-browser helper reports element screen boxes, which are stripped before results reach the model - web: agentPointerFeed + AgentCursor over the side-panel preview and the full-window screen, mapped through the noVNC letterbox - in-app browser: native WebContentsView on desktop (own session, http(s) only, clamped bounds, back/forward, reload, DevTools, load errors) and a sandboxed iframe on the web; replaces the dead panel, the fake 50%/40% cursors and the unimplemented devServer bridge Claude-Session: https://claude.ai/code/session_01WiGooXtsKrZQmdszepRrNi Co-authored-by: Claude <noreply@anthropic.com>
40 lines
1.5 KiB
TypeScript
40 lines
1.5 KiB
TypeScript
const LOCAL_HOST = /^(localhost|127(?:\.\d{1,3}){3}|\[::1\]|0\.0\.0\.0|[\w-]+\.local(?:host)?)$/i;
|
|
|
|
/**
|
|
* Turns what someone types in the address bar into an http(s) URL. Local dev servers default to
|
|
* http, everything else to https. Returns null for input that is not an address.
|
|
*/
|
|
export function normalizeBrowserUrl(input: string): string | null {
|
|
const value = input.trim();
|
|
if (!value || /\s/.test(value)) return null;
|
|
const hasScheme = /^[a-z][a-z\d+.-]*:\/\//i.test(value);
|
|
if (!hasScheme && /^[a-z][a-z\d+.-]*:(?!\d)/i.test(value)) return null;
|
|
let candidate = value;
|
|
if (!hasScheme) {
|
|
const host = value.split(/[/?#]/, 1)[0]!.replace(/:\d+$/, "");
|
|
if (!LOCAL_HOST.test(host) && !host.includes(".") && !/^\d+$/.test(host)) return null;
|
|
candidate = `${LOCAL_HOST.test(host) ? "http" : "https"}://${value}`;
|
|
}
|
|
try {
|
|
const url = new URL(candidate);
|
|
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
if (url.username || url.password || !url.hostname) return null;
|
|
return url.toString();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sandbox for the web fallback. A page on the app's own origin must not get same-origin
|
|
* access, or it could reach into the app around the sandbox.
|
|
*/
|
|
export function browserFrameSandbox(url: string, appOrigin: string): string {
|
|
const base = "allow-scripts allow-forms allow-popups allow-modals allow-downloads";
|
|
try {
|
|
return new URL(url).origin === appOrigin ? base : `${base} allow-same-origin`;
|
|
} catch {
|
|
return base;
|
|
}
|
|
}
|