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
30 changes: 30 additions & 0 deletions app/src/features/desktop/AppOptions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// The options of the app a bot is using, right in its desktop panel (spec
// 24.9, 24.10): the real mouse and keyboard and use while the owner is
// away, the same switches as in the bot's details, where the owner looks.

import { useT } from "../../i18n";
import type { Bot } from "../../lib/protocol.gen";
import { useDesktopGrants } from "./DesktopGrants";
import { RealInput } from "./RealInput";
import { Unattended } from "./Unattended";

export function AppOptions({ bot, app }: { bot: Bot; app: string }) {
const words = useT().desktop.panel;
const grants = useDesktopGrants(bot.id);
const named = app.toLowerCase();
const grant =
grants?.find((grant) => grant.scope === "app" && grant.appName?.toLowerCase() === named) ??
grants?.find((grant) => grant.scope === "desktop");
if (!grant) {
return null;
}
return (
<section
aria-label={words.options(app)}
className="flex flex-wrap items-center gap-x-5 gap-y-1 rounded-lg border border-line bg-sunken px-3 pt-1 pb-2"
>
{grant.level === "act" && <RealInput bot={bot} grant={grant} app={app} />}
<Unattended bot={bot} grant={grant} app={app} />
</section>
);
}
2 changes: 1 addition & 1 deletion app/src/features/desktop/DesktopGrants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { RealInput } from "./RealInput";
import { Unattended } from "./Unattended";

/** The bot's grants, read when shown and kept current by `bot.desktop`. */
function useDesktopGrants(botId: Bot["id"]): DesktopGrant[] | null {
export function useDesktopGrants(botId: Bot["id"]): DesktopGrant[] | null {
const api = useApi();
const [grants, setGrants] = useState<DesktopGrant[] | null>(null);
useEffect(() => {
Expand Down
2 changes: 2 additions & 0 deletions app/src/features/desktop/DesktopPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { attempt } from "../../ui/toast";
import { BotAvatar } from "../bots/BotAvatar";
import { LiveView, type StageAction } from "../browser/LiveView";
import { ComputerFooter } from "../terminal/ComputerDock";
import { AppOptions } from "./AppOptions";
import { useDesktopView } from "./useDesktopView";

type Words = ReturnType<typeof useT>["desktop"]["panel"];
Expand Down Expand Up @@ -144,6 +145,7 @@ export function DesktopPanel({ bot, onClose }: { bot: Bot; onClose(): void }) {
<BotAvatar color={bot.color} size={18} mood={stopped ? "sleeping" : "working"} />
<span className="truncate">{actionText(state?.action ?? null, t)}</span>
</p>
<AppOptions bot={bot} app={window.app} />
</>
)}
<p className="mt-auto text-muted text-xs">{t.shortcut}</p>
Expand Down
16 changes: 16 additions & 0 deletions app/src/features/desktop/panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,20 @@ describe("the desktop panel", () => {
});
expect(await within(panel()).findByText("Shopping list - Notepad")).toBeDefined();
});

test("has the app's options right there, where the owner looks", async () => {
const { fake, scout } = await openScout();
act(() => {
fake.desktop.grant(scout.id, "C:Windows\notepad.exe", "Notepad", "act");
fake.desktop.use(scout.id, NOTES);
});
const options = await within(panel()).findByRole("region", { name: "Options for Notepad" });
const real = within(options).getByRole("switch", { name: "Real mouse and keyboard: Notepad" });
fireEvent.click(real);
fireEvent.click(screen.getByRole("button", { name: "Turn on" }));
await waitFor(() => expect(fake.desktop.grants[0]?.realInput).toBe(true));
expect(
within(options).getByRole("switch", { name: "While you are away: Notepad" }),
).toBeDefined();
});
});
1 change: 1 addition & 0 deletions app/src/i18n/en/desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const desktop = {
panel: {
label: (bot: string) => `${bot}'s desktop`,
screen: (bot: string, app: string) => `${app}, as ${bot} sees it`,
options: (app: string) => `Options for ${app}`,
heading: "Desktop",
live: "Live",
stoppedBadge: "Stopped",
Expand Down
1 change: 1 addition & 0 deletions app/src/i18n/es/desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const desktop: Messages["desktop"] = {
panel: {
label: (bot) => `Escritorio de ${bot}`,
screen: (bot, app) => `${app}, como lo ve ${bot}`,
options: (app) => `Opciones en ${app}`,
heading: "Escritorio",
live: "En vivo",
stoppedBadge: "Detenido",
Expand Down
1 change: 1 addition & 0 deletions app/src/i18n/pt-BR/desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const desktop: Messages["desktop"] = {
panel: {
label: (bot) => `Desktop de ${bot}`,
screen: (bot, app) => `${app}, como ${bot} vê`,
options: (app) => `Opções no ${app}`,
heading: "Desktop",
live: "Ao vivo",
stoppedBadge: "Parado",
Expand Down
2 changes: 1 addition & 1 deletion docs/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -1785,7 +1785,7 @@ No servidor `botloft` (11), como as do navegador.
- o que nunca é liberado (24.3).
O botão de confirmar só acende depois de o dono marcar "Entendi os riscos"; a caixa começa desmarcada toda vez. A data em que ele aceitou fica gravada com a permissão (`accepted_risks_at`) e fica lá se ele desliga.
- **Sem você na frente**: em cada permissão, de ver ou de usar, um interruptor, desligado de início. Ligar abre a tela de riscos acima; desligar é na hora. O app chama `desktop.setOptions {grantId, unattended, acceptedRisks}`; o daemon recusa ligar sem `acceptedRisks: true`.
- **Mouse e teclado de verdade**: em cada permissão de usar um app, um interruptor, desligado de início (só nas de usar). Ligar abre antes um diálogo, "Deixar <bot> usar seu mouse e teclado de verdade no <app>?", que diz o que muda: quando a acessibilidade não alcança, o bot move o cursor e digita como se fosse o dono; enquanto ele age, o mouse e o teclado são dele, com o aviso na tela; mexer no mouse ou numa tecla o para na hora, e Ctrl+Alt+Esc para todos; nunca campos de senha nem teclas do Windows. Desligar é na hora. O app chama `desktop.setOptions`.
- **Mouse e teclado de verdade**: em cada permissão de usar um app, um interruptor, desligado de início (só nas de usar). Ligar abre antes um diálogo, "Deixar <bot> usar seu mouse e teclado de verdade no <app>?", que diz o que muda: quando a acessibilidade não alcança, o bot move o cursor e digita como se fosse o dono; enquanto ele age, o mouse e o teclado são dele, com o aviso na tela; mexer no mouse ou numa tecla o para na hora, e Ctrl+Alt+Esc para todos; nunca campos de senha nem teclas do Windows. Desligar é na hora. O app chama `desktop.setOptions`. Os mesmos interruptores (mouse e teclado de verdade, nas permissões de usar, e "Sem você na frente") aparecem também no painel "Desktop" (24.9), embaixo da linha do que o bot fez, para o app da janela que ele usa (a permissão desse app pelo nome, senão a do desktop inteiro): é onde o dono está olhando quando o bot pede.

### 24.11 Dados

Expand Down
Loading