From dd3224efd87c1c7744ad724e601aac197e1e3cc0 Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:01:24 -0300 Subject: [PATCH 1/2] feat(browser): point the owner to the window when a sign-in fails The window sign-in only showed under the live screen, which goes away once the owner takes control to sign in. Now a line under the help callout and the "in control" bar says to use Sign in in a window when the site won't let them in, and the help card in the chat says the same. The bot's rules and browser_ask_owner tell it to suggest the window when a site refuses to sign in to its browser, and to open the page again afterwards. README and spec 21.11 describe it. --- README.md | 4 +++- app/src/features/browser/HandsBars.tsx | 2 ++ app/src/features/browser/window.test.tsx | 11 +++++++++++ app/src/i18n/en/browser.ts | 3 ++- app/src/i18n/es/browser.ts | 3 ++- app/src/i18n/pt-BR/browser.ts | 3 ++- crates/botloftd/src/tools/browser_catalog.rs | 6 ++++-- crates/botloftd/src/workspace/files.rs | 5 +++++ docs/spec.md | 4 +++- 9 files changed, 34 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a141609d..d3276d59 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,9 @@ you decide what they may do. - **Let them hand work to each other.** Bots send messages and tasks to each other. Every message is saved before it is sent and retried until it arrives. - **Watch them work.** See every page a bot opens in its browser, live, and take control to sign in - for it. Watch the screens it designs take shape while it writes them. Open the files it made. + for it. Sites that refuse to sign in there, like Google, take one click more: sign in in a real + window of the bot's browser, close it, and the bot keeps the login. Watch the screens it designs + take shape while it writes them. Open the files it made. - **Schedule routines.** Weekday mornings, every two hours or any cron schedule, with missed runs and overlaps handled. - **Leave them running.** Bots keep working after you close Botloft. They start when you sign in, diff --git a/app/src/features/browser/HandsBars.tsx b/app/src/features/browser/HandsBars.tsx index 768342f2..9da03be9 100644 --- a/app/src/features/browser/HandsBars.tsx +++ b/app/src/features/browser/HandsBars.tsx @@ -30,6 +30,7 @@ export function AskCallout({ bot, task, hands }: { bot: Bot; task: string; hands +

{t.window.hint}

); @@ -83,6 +84,7 @@ export function HeldBar({ {focused ? t.hands.typing : t.hands.clickToType} +

{t.window.hint}

); } diff --git a/app/src/features/browser/window.test.tsx b/app/src/features/browser/window.test.tsx index c63955e6..19488414 100644 --- a/app/src/features/browser/window.test.tsx +++ b/app/src/features/browser/window.test.tsx @@ -45,6 +45,14 @@ describe("the browser in a window of its own", () => { await waitFor(() => expect(within(panel()).queryByText(/keeps the login/)).toBeNull()); }); + test("is suggested while the owner is in control, where a sign-in may fail", async () => { + await openScout(); + fireEvent.click(within(panel()).getByRole("button", { name: "Take control" })); + expect(await within(panel()).findByText("You are in control")).toBeDefined(); + expect(within(panel()).getByText(/won't let you sign in here\?/)).toBeDefined(); + expect(within(panel()).getByRole("button", { name: "Sign in in a window" })).toBeDefined(); + }); + test("is offered when the bot asks for a hand", async () => { const { fake, scout } = await openScout(); act(() => { @@ -52,6 +60,9 @@ describe("the browser in a window of its own", () => { }); const asking = await within(panel()).findByText("Sign in to your Google account"); expect(asking).toBeDefined(); + const hint = /won't let you sign in here\? Use Sign in in a window/; + expect(within(panel()).getByText(hint)).toBeDefined(); + expect(screen.getByText(/won't let you sign in there, use Sign in in a window/)).toBeDefined(); fireEvent.click(within(panel()).getByRole("button", { name: "Sign in in a window" })); expect(await within(panel()).findByText(/keeps the login/)).toBeDefined(); diff --git a/app/src/i18n/en/browser.ts b/app/src/i18n/en/browser.ts index 33688120..1a21372b 100644 --- a/app/src/i18n/en/browser.ts +++ b/app/src/i18n/en/browser.ts @@ -87,11 +87,12 @@ export const browser = { title: "Open in a window", body: (bot: string) => `Sign in in the window that opened, then close it. ${bot} waits meanwhile and keeps the login.`, + hint: "Site won't let you sign in here? Use Sign in in a window, and close the window when you're done.", }, help: { needs: (bot: string) => `${bot} needs you in the browser`, asks: (bot: string, task: string) => `${bot} asks: ${task}`, - why: "Take control, do it on the page yourself and give it back. The bot doesn't see what you type in password fields.", + why: "Take control, do it on the page yourself and give it back. If the site won't let you sign in there, use Sign in in a window in the browser panel. The bot doesn't see what you type in password fields.", take: "Take the browser", done: "Done", wontDo: "I won't do it", diff --git a/app/src/i18n/es/browser.ts b/app/src/i18n/es/browser.ts index cca1a261..e519b773 100644 --- a/app/src/i18n/es/browser.ts +++ b/app/src/i18n/es/browser.ts @@ -87,11 +87,12 @@ export const browser: Messages["browser"] = { title: "Abierto en una ventana", body: (bot) => `Inicia sesión en la ventana que se abrió y luego ciérrala. ${bot} espera mientras tanto y conserva la sesión.`, + hint: "¿El sitio no te deja iniciar sesión aquí? Usa Iniciar sesión en una ventana y ciérrala al terminar.", }, help: { needs: (bot) => `${bot} te necesita en el navegador`, asks: (bot, task) => `${bot} pide: ${task}`, - why: "Toma el control, hazlo en la página y devuélvelo. El bot no ve lo que escribes en campos de contraseña.", + why: "Toma el control, hazlo en la página y devuélvelo. Si el sitio no te deja iniciar sesión ahí, usa Iniciar sesión en una ventana en el panel del navegador. El bot no ve lo que escribes en campos de contraseña.", take: "Tomar el navegador", done: "Listo", wontDo: "No lo haré", diff --git a/app/src/i18n/pt-BR/browser.ts b/app/src/i18n/pt-BR/browser.ts index fe7ef25c..18bfb4d6 100644 --- a/app/src/i18n/pt-BR/browser.ts +++ b/app/src/i18n/pt-BR/browser.ts @@ -89,11 +89,12 @@ export const browser: Messages["browser"] = { title: "Aberto numa janela", body: (bot) => `Entre na conta na janela que abriu e depois feche-a. ${bot} espera enquanto isso e fica com o login.`, + hint: "O site não deixa entrar aqui? Use Entrar numa janela e feche a janela quando terminar.", }, help: { needs: (bot) => `${bot} precisa de você no navegador`, asks: (bot, task) => `${bot} pede: ${task}`, - why: "Assuma o controle, faça isso na página e devolva. O bot não vê o que você digita em campos de senha.", + why: "Assuma o controle, faça isso na página e devolva. Se o site não deixar entrar por ali, use Entrar numa janela no painel do navegador. O bot não vê o que você digita em campos de senha.", take: "Assumir o navegador", done: "Pronto", wontDo: "Não vou fazer", diff --git a/crates/botloftd/src/tools/browser_catalog.rs b/crates/botloftd/src/tools/browser_catalog.rs index 3c5edd9b..511c45bf 100644 --- a/crates/botloftd/src/tools/browser_catalog.rs +++ b/crates/botloftd/src/tools/browser_catalog.rs @@ -157,8 +157,10 @@ pub(super) fn tools() -> Vec { until they are done: signing in to their account, solving a captcha, typing a \ code sent to their phone, anything only they should or can do. Open the page \ first; they take over your browser, and it comes back to you with the page as \ - they left it. To sign in where a site refuses your browser, they may open it \ - in a window of its own; then it comes back closed, with their logins kept. \ + they left it. When a site refuses to sign in to your browser (it says the \ + browser may not be secure, or the sign-in fails), say in the task that they \ + should sign in in a window: they open your browser in a window of its own, and \ + it comes back closed, with their logins kept. \ Never ask for passwords, codes or card numbers in the chat: ask with this tool, \ so the owner types them where you never see them.", "inputSchema": { diff --git a/crates/botloftd/src/workspace/files.rs b/crates/botloftd/src/workspace/files.rs index bcd129ff..a4936f08 100644 --- a/crates/botloftd/src/workspace/files.rs +++ b/crates/botloftd/src/workspace/files.rs @@ -181,6 +181,11 @@ app; the other bots of your crew send you messages too. call `browser_ask_owner`: they take over your browser, do it themselves and give it back. Never ask for passwords, codes or card numbers in the chat. +- Some sites refuse to sign in to your browser (\"this browser may not be + secure\", a sign-in that fails or loops). Then ask with `browser_ask_owner` + and tell the owner, in their language, to use the button that signs in + in a window, in your browser panel. When they close it, open the page + again with `browser_open`: the login stays. - Every `.html` file you write in the work folder or in this folder shows up as a live screen in the owner's design area, built in front of them while you write it. When you design pages or app screens, write one screen per diff --git a/docs/spec.md b/docs/spec.md index 055d9cf6..c18a355d 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -1430,7 +1430,9 @@ O login do Google, e o de outros sites que olham mais que o user agent e `naviga - **Enquanto a janela está aberta:** `BrowserState` fica `closed`, com `window` e `control: "owner"`. As tools do bot esperam como com o dono no controle (21.10). `browser_close` e o fechamento por falta de uso não a tocam, e quem respondia a um pedido de ajuda não devolve o navegador ao bot. - **Ao fechar a janela:** o navegador volta ao bot, fechado; a próxima tool abre o sem janela no mesmo perfil, com os cookies do login (visto com o Edge 154: um cookie gravado na janela aparece no sem janela, 19). Um pedido de ajuda aberto (`browser_ask_owner`) recebe **Pronto**, e a tool diz ao bot que o navegador fechou e que os logins ficam; ele abre a página de novo com `browser_open`. -**No app** (21.8): "Entrar numa janela" fica ao lado de "Assumir o controle" embaixo da tela ao vivo, no aviso de um pedido de ajuda e na faixa do dono no controle. Embaixo da tela, a explicação ganha: "Para sites que recusam o login aqui, como o Google: o navegador de abre numa janela própria, e o login fica com ." Com a janela aberta, uma faixa no painel diz "Aberto numa janela": entre na conta na janela que abriu e feche-a depois; o bot espera enquanto isso e fica com o login. A janela não fecha pelo app: fechá-la à força pode perder o login ainda não gravado. +**No app** (21.8): "Entrar numa janela" fica ao lado de "Assumir o controle" embaixo da tela ao vivo, no aviso de um pedido de ajuda e na faixa do dono no controle. Embaixo da tela, a explicação ganha: "Para sites que recusam o login aqui, como o Google: o navegador de abre numa janela própria, e o login fica com ." Com a janela aberta, uma faixa no painel diz "Aberto numa janela": entre na conta na janela que abriu e feche-a depois; o bot espera enquanto isso e fica com o login. A janela não fecha pelo app: fechá-la à força pode perder o login ainda não gravado. Onde o dono faz o login pelo painel (o aviso de um pedido de ajuda e a faixa do dono no controle), uma linha lembra: "O site não deixa entrar aqui? Use Entrar numa janela e feche a janela quando terminar." O cartão de ajuda no chat diz o mesmo. + +**Para o bot:** as regras (5.1) dizem que, quando um site recusa o login no navegador dele ("este navegador pode não ser seguro", um login que falha ou volta ao começo), ele pede ajuda com `browser_ask_owner` e diz ao dono, na língua dele, para usar o botão de entrar numa janela; depois abre a página de novo. A descrição de `browser_ask_owner` diz o mesmo. ### 21.12 Marcos From af060ab5899dcfda86512d56c9c67faf9d98b490 Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Sat, 3 Oct 2026 18:17:33 -0300 Subject: [PATCH 2/2] fix(browser): wait longer for the user agent data On a busy CI runner chrome://version took over 2 s to load, the read gave up, and the brands went empty again. --- crates/botloftd/src/browser/agent.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/botloftd/src/browser/agent.rs b/crates/botloftd/src/browser/agent.rs index b78e5801..b7fafa10 100644 --- a/crates/botloftd/src/browser/agent.rs +++ b/crates/botloftd/src/browser/agent.rs @@ -19,8 +19,10 @@ const SECURE_PAGE: &str = "chrome://version"; const READ_METADATA: &str = "navigator.userAgentData ? navigator.userAgentData\ .getHighEntropyValues(['architecture', 'bitness', 'fullVersionList', 'model', \ 'platformVersion', 'wow64']) : null"; -/// Tries while the page loads, 50 ms apart. -const TRIES: usize = 40; +/// Tries while the page loads, 50 ms apart: up to 10 s. Usually under +/// 100 ms, but a busy CI runner took over 2 s, and without the data the +/// brands go empty. +const TRIES: usize = 200; /// The params of `Emulation.setUserAgentOverride` for every tab. Run before /// anything else reads `events`: the page it reads from opens and closes