Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions app/src/features/browser/HandsBars.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function AskCallout({ bot, task, hands }: { bot: Bot; task: string; hands
</Button>
<WindowButton hands={hands} />
</div>
<p className="mt-2 text-muted text-xs">{t.window.hint}</p>
</Callout>
</div>
);
Expand Down Expand Up @@ -83,6 +84,7 @@ export function HeldBar({
{focused ? t.hands.typing : t.hands.clickToType}
</span>
</div>
<p className="mt-2 pl-6.5 text-muted text-xs">{t.window.hint}</p>
</div>
);
}
Expand Down
11 changes: 11 additions & 0 deletions app/src/features/browser/window.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,24 @@ 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(() => {
fake.browser.ask(scout.id, "Sign in to your Google account");
});
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();

Expand Down
3 changes: 2 additions & 1 deletion app/src/i18n/en/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion app/src/i18n/es/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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é",
Expand Down
3 changes: 2 additions & 1 deletion app/src/i18n/pt-BR/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
6 changes: 4 additions & 2 deletions crates/botloftd/src/browser/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions crates/botloftd/src/tools/browser_catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ pub(super) fn tools() -> Vec<Value> {
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": {
Expand Down
5 changes: 5 additions & 0 deletions crates/botloftd/src/workspace/files.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <bot> abre numa janela própria, e o login fica com <bot>." 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 <bot> abre numa janela própria, e o login fica com <bot>." 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

Expand Down
Loading