browser relay: multi-session override, stale session recovery, better error feedback
- relay accepts new extension connections by closing old one (last wins, code 4000) - extension recognizes code 4000 and stops auto-reconnect (shows "replaced" badge) - fix ping interval race where old WS close handler killed new connection's pings - retry CDP commands on "Session with given id not found" by re-attaching debugger - describeError() maps known errors to user-friendly badge tooltips - persisted relay tokens restored on server start - user-scoped token salts, token regeneration/deletion endpoints - extension download endpoint, integrations UI for browser relay setup - browser relay env vars passed to pi-bridge sandboxes - browser screen layout with chat panel and prompt prefix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,8 @@ import { ensureDockerContainer } from "../terminal/websocket";
|
||||
import { getServerIntegration, getUserIntegration } from "officerdb";
|
||||
import { logger } from "./logger";
|
||||
import { parseFrontmatter } from "../skills/skills";
|
||||
import { getRelayPort } from "../browser/relay";
|
||||
import { registerUserToken } from "../browser/relay-auth";
|
||||
|
||||
export type PiEventHandler = (event: PiEvent) => void;
|
||||
|
||||
@@ -188,6 +190,23 @@ async function ensureGoogleTokenFile(userId: number, email: string): Promise<str
|
||||
return filePath;
|
||||
}
|
||||
|
||||
async function getBrowserRelayEnv(userId: number): Promise<Record<string, string>> {
|
||||
const port = getRelayPort();
|
||||
if (!port) return {};
|
||||
try {
|
||||
const integration = await getUserIntegration(userId, 'browser-relay');
|
||||
const salt = (integration?.config as { tokenSalt?: string })?.tokenSalt;
|
||||
if (!salt) return {};
|
||||
const token = registerUserToken(userId, port, salt);
|
||||
return {
|
||||
OFFICER_BROWSER_RELAY_PORT: String(port),
|
||||
OFFICER_BROWSER_RELAY_TOKEN: token,
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
type SandboxOptions = {
|
||||
userId: number;
|
||||
username: string;
|
||||
@@ -245,6 +264,7 @@ export async function spawnPi(
|
||||
|
||||
const googleConfigHost = await ensureGoogleConfigFile();
|
||||
await ensureGoogleTokenFile(sandbox.userId, sandbox.email);
|
||||
const browserRelayEnv = await getBrowserRelayEnv(sandbox.userId);
|
||||
|
||||
const envFlags = [
|
||||
'-e', `HOME=${containerHome}`,
|
||||
@@ -256,6 +276,7 @@ export async function spawnPi(
|
||||
'-e', `OFFICER_GOOGLE_CONFIG_PATH=/officer/google-oauth.json`,
|
||||
'-e', `OFFICER_GOOGLE_TOKEN_PATH=/officer/user/integrations/google.json`,
|
||||
'-e', `OFFICER_EMAIL_DB=/officer/emails.db`,
|
||||
...Object.entries(browserRelayEnv).flatMap(([k, v]) => ['-e', `${k}=${v}`]),
|
||||
];
|
||||
|
||||
const rel = relative(sandbox.homeDir, cwd);
|
||||
@@ -299,6 +320,7 @@ export async function spawnPi(
|
||||
const toolsDirs = [getGlobalToolsDir(), getUserToolsDir(email)].join(':');
|
||||
const googleConfigPath = await ensureGoogleConfigFile();
|
||||
const googleTokenPath = await ensureGoogleTokenFile(userId, email);
|
||||
const browserRelayEnv = await getBrowserRelayEnv(userId);
|
||||
|
||||
proc = Bun.spawn(args, {
|
||||
cwd,
|
||||
@@ -317,6 +339,7 @@ export async function spawnPi(
|
||||
OFFICER_GOOGLE_CONFIG_PATH: googleConfigPath,
|
||||
OFFICER_GOOGLE_TOKEN_PATH: googleTokenPath,
|
||||
OFFICER_EMAIL_DB: join(DATA_PATH, email, 'emails.db'),
|
||||
...browserRelayEnv,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user