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