diff --git a/README.md b/README.md index 3560085..8602727 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,4 @@ -

- - Browser Use - -

+Browser Agent Template # Browser Agent Template diff --git a/app/_components/agent-chat.tsx b/app/_components/agent-chat.tsx index aeee9e9..172a295 100644 --- a/app/_components/agent-chat.tsx +++ b/app/_components/agent-chat.tsx @@ -22,17 +22,36 @@ const BETA_TERMS_HREF = "https://vercel.com/docs/release-phases/public-beta-agre type AgentStatus = ReturnType["status"]; -/** The latest cloud-browser liveUrl, found anywhere in the conversation (the - * open_cloud_browser tool result or the agent's text). */ +/** True only for an https URL whose host is exactly the live-view host. A + * startsWith/substring check is unsafe — it also passes + * `https://live.browser-use.com.evil.com`. */ +function isLiveUrl(url: unknown): url is string { + if (typeof url !== "string") return false; + try { + const parsed = new URL(url); + return parsed.protocol === "https:" && parsed.hostname === "live.browser-use.com"; + } catch { + return false; + } +} + +/** The latest cloud-browser liveUrl, taken from the open_cloud_browser tool's + * structured output. We read the tool result (not the model's chat text) so the + * URL is canonical — scraping the prose can capture trailing markdown like `**` + * and corrupt the wss host, which breaks the live view. */ function extractLiveUrl(messages: readonly EveMessage[]): string | null { - // Origin-safe: only match when the host is followed by a path/query/fragment - // or a boundary — so userinfo tricks like ...com@evil.com don't slip through. - const re = /\bhttps:\/\/live\.browser-use\.com(?:[/?#][^\s"'\\]*)?(?=$|[\s"'\\])/; let found: string | null = null; for (const message of messages) { for (const part of message.parts) { - const match = JSON.stringify(part).match(re); - if (match) found = match[0]; + if (part.type !== "dynamic-tool" || part.toolName !== "open_cloud_browser") continue; + const out = part.output; + let url: unknown; + if (out && typeof out === "object" && "liveUrl" in out) { + url = (out as { liveUrl?: unknown }).liveUrl; + } else if (typeof out === "string") { + url = out.match(/https:\/\/live\.browser-use\.com[^\s"'\\]*/)?.[0]; + } + if (isLiveUrl(url)) found = url; } } return found; diff --git a/public/banner.png b/public/banner.png new file mode 100644 index 0000000..3a8a467 Binary files /dev/null and b/public/banner.png differ