Skip to content

Commit 49e0b8d

Browse files
committed
Move a Bot's own browser onto per-person seats, and let Record run on a person's own computer
A Bot's browser (#868) is now a seat's, like everything else on a computer: each route resolves the asker's seat for the Bot and reaches that browser, on their own computer or on an isolated Bot's computer of its own, with the person named to the computer where people share one. Recorder connections are still filed under the Bot, but a seat on a person's computer reads, hands back and clears only that person's: before, Ben opening his own Bot screen ended Ada's live recording on her computer as left behind. The Bot's network policy is pushed to the seat's computer. Record is refused only on the computer every person shares. A person's computer is now told whose it is (COMPUTER_PERSON_ID, from the supervisor and the Sandbox template), so it records in its Bots' browsers there and still refuses a person's own browser.
1 parent 282ba57 commit 49e0b8d

18 files changed

Lines changed: 364 additions & 148 deletions

‎CHANGELOG.md‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -639,11 +639,14 @@ someone records, so recording a task needs no extension and no Connect step.
639639
disconnects; the Bot acts again only once the recording has ended. Taking control now records who
640640
took it, so on a shared Bot only that person can use the bar or record, someone else's recording
641641
shows only that one is running, and nobody else sees its name, its step count or its microphone.
642-
- **Record needs a computer the Bot does not share.** Where every Bot runs on one computer (Helm's
643-
shared mode, or `AGENT_COMPUTER_URL` with no supervisor), Record is refused in every Bot's browser
644-
and the panel says why: your Recorder connection would sit in the Bot's profile, where any other
645-
Bot's shell could read it. The bar still works. The computer refuses it too. With a supervisor
646-
(laptop, Desktop) or a Sandbox, each Bot has its own computer and Record works.
642+
- **Record runs on your own computer, never the one everyone shares.** Where every person's Bots run
643+
on one computer (Helm's shared mode, or `AGENT_COMPUTER_URL` with no supervisor), Record is refused
644+
in every Bot's browser and the panel says why: your Recorder connection would sit in the Bot's
645+
profile, where another person's Bot could read it. The bar still works. The computer refuses it too.
646+
With a supervisor (laptop, Desktop) or a Sandbox, a Bot's browser is on your own computer, beside
647+
only your own Bots, or on an isolated Bot's computer of its own, and Record works there. Your
648+
hand-back ends only your own recording: another person recording in the same Bot's browser on their
649+
own computer is never touched.
647650
- **One connection per recording.** Record mints a Recorder connection for the person who pressed it
648651
and Stop revokes it; Settings, Browsers lists it as "<Bot>'s browser" while it lives, and says
649652
"recording now" for any browser that is recording.

‎agent-computer/src/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import {
2020
mutatesBrowser,
2121
offeredToken,
2222
} from "./authorisation";
23-
import { isPlainBotId, seatIdFor } from "./bot-id";
2423
import { createBotBrowserHost, isBotBrowserPath } from "./bot-browser-host";
24+
import { isPlainBotId, seatIdFor } from "./bot-id";
2525
import { createBrowserHost, isBrowserPath } from "./browser-host";
2626
import { browserRuntimeFromEnv } from "./browser-runtime";
2727
import { detectChallenge } from "./challenge";

‎agent-computer/src/person-access.ts‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,18 @@ export function personHoldsTheWheel(since: string): ComputerControlState {
8686
*
8787
* - `people`: OpenBot Desktop's host-side computer (`COMPUTER_PEOPLE_ONLY=on`), which runs people's own browsers and
8888
* no Bot.
89-
* - `one`: a computer started for one id (`COMPUTER_BOT_ID`), which is how a supervisor starts each container and a
90-
* Sandbox each pod. Started for a person's browser, it runs that browser and nothing else.
91-
* - `shared`: neither, the one computer every Bot without its own reaches. No person's browser runs there.
89+
* - `one`: a computer started for one id (`COMPUTER_BOT_ID`): an isolated Bot's, or a person's own browser's, as the
90+
* supervisor and a Sandbox start them. Started for a person's browser, it runs that browser and nothing else.
91+
* - `person`: one person's computer (`COMPUTER_PERSON_ID`), carrying every Bot that person uses. Their Bots share it,
92+
* so their own browser does not run there, but Record in one of those Bots' browsers does: the connection it holds
93+
* is the person's own, beside only their own Bots' work.
94+
* - `shared`: none of these, the one computer every person's Bots reach. No person's browser runs there, and nobody
95+
* records in a Bot's browser there.
9296
*/
9397
export type ComputerOwner =
9498
| { kind: "people" }
9599
| { kind: "one"; id: string }
100+
| { kind: "person"; id: string }
96101
| { kind: "shared" };
97102

98103
export function computerOwner(
@@ -101,7 +106,9 @@ export function computerOwner(
101106
if (env[PEOPLE_ONLY_ENV]?.trim().toLowerCase() === "on")
102107
return { kind: "people" };
103108
const id = env.COMPUTER_BOT_ID?.trim();
104-
return id ? { kind: "one", id } : { kind: "shared" };
109+
if (id) return { kind: "one", id };
110+
const person = env.COMPUTER_PERSON_ID?.trim();
111+
return person ? { kind: "person", id: person } : { kind: "shared" };
105112
}
106113

107114
/** Why a person's browser is refused on a computer Bots run on. Said to whoever runs OpenBot: the server tells the
@@ -110,7 +117,7 @@ export const NOT_ITS_OWN_COMPUTER =
110117
"A person's own browser runs only on a computer of its own, and Bots run on this one. Give people's browsers their own computers (COMPUTER_SUPERVISOR_URL or a Sandbox), or OpenBot Desktop's.";
111118

112119
/**
113-
* Why Record is refused in a Bot's browser on the shared computer. Record hands the Recorder there a person's own
120+
* Why Record is refused in a Bot's browser on the computer every person shares (`shared`, not a person's own). Record hands the Recorder there a person's own
114121
* connection, kept in the Bot's profile until Stop, and every other Bot here can run a shell as the same user and read
115122
* it. Said to whoever runs OpenBot, as `NOT_ITS_OWN_COMPUTER` is; the server tells the person in its own words.
116123
*/

‎agent-computer/tests/bot-browser-computer.test.ts‎

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,36 @@ describe.skipIf(!asked)(
343343
).toBe(200);
344344
}, 60_000);
345345

346+
test("records on a person's own computer, where only their own Bots run", async () => {
347+
// How the supervisor and a Sandbox start a person's computer: named for the person, no Bot.
348+
const own = await startComputer("persons-computer", {
349+
OPENBOT_RECORDER_DIR: FIXTURE,
350+
COMPUTER_PERSON_ID: "user-1",
351+
});
352+
await takeWheel("user-1", own);
353+
const before = calls.length;
354+
const started = await start("user-1", "File an expense report", own);
355+
expect(started.status).toBe(200);
356+
expect(started.json.recordingBy).toBe("user-1");
357+
// The Recorder there was handed the person's connection and asked to record.
358+
expect(
359+
calls
360+
.slice(before)
361+
.map((each) => each.method)
362+
.filter((method) => method === "connect" || method === "record"),
363+
).toEqual(["connect", "record"]);
364+
expect(
365+
(
366+
await call("/bot-browser/recording", {
367+
body: { action: "stop" },
368+
on: own,
369+
})
370+
).status,
371+
).toBe(200);
372+
// The fixture Recorder's calls are one list for the file; the tests after this one start from none.
373+
calls.length = 0;
374+
}, 60_000);
375+
346376
test("refuses the bar, Record and narration while the Bot holds the wheel", async () => {
347377
for (const [path, body] of [
348378
["/bot-browser/tabs", { action: "new", url: page("sneaky") }],
@@ -982,9 +1012,9 @@ describe.skipIf(!asked)(
9821012
function serverOn(on: Computer) {
9831013
const provider: ComputerProvider = {
9841014
name: "this-test",
985-
isolation: "per-bot",
1015+
isolation: "per-person",
9861016
locate: async () => on.base,
987-
status: async (botId) => ({ botId, state: "ready" }),
1017+
status: async (seat) => ({ botId: seat.botId, state: "ready" }),
9881018
stop: async () => ({ wasRunning: true }),
9891019
reset: async () => ({ cleared: true }),
9901020
list: async () => [],
@@ -1069,8 +1099,9 @@ describe.skipIf(!asked)("who asked for the wheel", () => {
10691099
).toBe(200);
10701100

10711101
// The Bot asking, as the headless tools and the password service do: no source, so the Bot's.
1102+
// On the seat the routes reach for this person, as the headless tools resolve it.
10721103
const bots = await server.gateway.requestHelp(
1073-
botId,
1104+
await server.gateway.seatFor(botId, server.actor.id),
10741105
server.actor,
10751106
"Sign in to the bank, then hand back.",
10761107
"call-sign-in",

‎agent-computer/tests/person-browser-rules.test.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ import {
3030
forgetRecorderPins,
3131
PLAYWRIGHT_DEFAULTS_A_PERSON_DOES_NOT_GET,
3232
personBrowserEnv,
33-
recorderLaunchOptions,
3433
personNativeLaunch,
34+
recorderLaunchOptions,
3535
runnable,
3636
} from "../src/person-launch";
3737
import {
@@ -480,6 +480,11 @@ describe("which computer a person's browser may run on", () => {
480480
kind: "one",
481481
id: PERSON,
482482
});
483+
// A person's computer, carrying every Bot they use.
484+
expect(computerOwner({ COMPUTER_PERSON_ID: "u-ada" })).toEqual({
485+
kind: "person",
486+
id: "u-ada",
487+
});
483488
// OpenBot Desktop's host-side computer.
484489
expect(computerOwner({ COMPUTER_PEOPLE_ONLY: "on" })).toEqual({
485490
kind: "people",
@@ -493,6 +498,8 @@ describe("which computer a person's browser may run on", () => {
493498
for (const owner of [
494499
computerOwner({}),
495500
computerOwner({ COMPUTER_BOT_ID: "invoice-collector" }),
501+
// Nor on a person's own computer, where their Bots run: their browser gets a computer of its own.
502+
computerOwner({ COMPUTER_PERSON_ID: "u-ada" }),
496503
]) {
497504
expect(refusedHere(owner, PERSON)).toEqual({
498505
status: 503,

‎app/src/components/channels/chat-transcript.tsx‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import { attachmentUrl } from "@/lib/channels/attachments";
5353
import { readResponsibilityTurn } from "@/lib/channels/responsibility-turn";
5454
import { readFiring } from "@/lib/channels/routine-firing";
5555
import type { CardPlace } from "@/lib/copilot/carry-on";
56+
import { ToolPlaceContext } from "@/lib/copilot/tool-place";
5657
import { markdownComponents } from "@/lib/markdown";
5758
import { EASE_OUT, ENTRANCE_SECONDS } from "@/lib/motion";
5859
import { readToolName } from "@/lib/plugins/tool-name";
@@ -63,12 +64,6 @@ import {
6364
toolResultFailed,
6465
} from "@/lib/plugins/tool-result";
6566
import { readTeamBotConsent } from "@/lib/team-bots";
66-
import type { CardPlace } from "@/lib/copilot/carry-on";
67-
import { ToolPlaceContext } from "@/lib/copilot/tool-place";
68-
import {
69-
readConnectPrompt,
70-
SHOW_APP_ACCESS_TOOL,
71-
} from "@openbot/shared/connect-prompt";
7267
import { cn } from "@/lib/utils";
7368
import { VOICE_CHAT_ACTIVITY, type VoiceChatEntry } from "@/lib/voice/archive";
7469
import { AppAccessCard } from "./app-access-card";

‎app/src/components/computer/handoff-resume-notice.tsx‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
type PendingHandoff,
77
pendingHandoff,
88
rememberHandoff,
9-
type PendingHandoff,
109
wasHandoffAnswered,
1110
} from "@/lib/computers/handoff";
1211
import {

‎app/src/lib/computers/control.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { tryClient } from "@/lib/client";
1212
*/
1313

1414
export type { ComputerControlState as ControlState } from "@openbot/shared/computer-control";
15+
1516
import type {
1617
ComputerControlState as ControlState,
1718
RequestHelpInput,

0 commit comments

Comments
 (0)