fix(browser): hide browser tools unless a desktop is attached (#52309)

This commit is contained in:
James Long
2026-09-30 11:42:23 -04:00
committed by GitHub
parent 0159356be6
commit 82bb5ff88a
2 changed files with 32 additions and 2 deletions
+1 -1
View File
@@ -213,7 +213,6 @@ export type InternalPlugin = Plugin<Requirements | Scope.Scope>
const pre = [
ToolInputRepairPlugin.Plugin,
ConfigWorktreePlugin.Plugin,
BrowserPlugin,
ConfigMcpPlugin.Plugin,
McpCodeModeDefaultsPlugin.Plugin,
WellKnownPlugin.Plugin,
@@ -251,6 +250,7 @@ const post = [
ConfigInstructionPlugin.Plugin,
ConfigReferencePlugin.Plugin,
ConfigAgentPlugin.Plugin,
BrowserPlugin,
ConfigCommandPlugin.Plugin,
ConfigCompactionPlugin.Plugin,
ConfigFormatterPlugin.Plugin,
+31 -1
View File
@@ -19,9 +19,38 @@ type Attachment = {
export type Connection = Effect.Success<ReturnType<typeof make>>
export const make = Effect.fn("BrowserConnection.make")(function* (
ctx: Pick<Context, "rpc" | "session" | "location" | "event">,
ctx: Pick<Context, "agent" | "rpc" | "session" | "location" | "event">,
) {
const browsers = new Map<Session.ID, Attachment>()
// Appended after config, so every agent hides the browser until an attachment allows it.
yield* ctx.agent.transform((editor) =>
editor.list().forEach((agent) => agent.permissions.push({ action: "browser", resource: "*", effect: "deny" })),
)
// Each attachment adds and removes its own allow, so a replaced attachment keeps the new one's.
const grant = (sessionID: Session.ID) =>
Effect.acquireRelease(
ctx.session.get({ sessionID }).pipe(
Effect.flatMap((session) =>
ctx.session.update({
sessionID,
permissions: [...(session.permissions ?? []), { action: "browser", resource: "*", effect: "allow" }],
}),
),
Effect.orDie,
),
() =>
ctx.session.get({ sessionID }).pipe(
Effect.flatMap((session) => {
const permissions = session.permissions ?? []
const index = permissions.findIndex(
(rule) => rule.action === "browser" && rule.resource === "*" && rule.effect === "allow",
)
if (index === -1) return Effect.void
return ctx.session.update({ sessionID, permissions: permissions.toSpliced(index, 1) })
}),
Effect.ignore,
),
)
let active = true
const close = (sessionID: Session.ID, reason: "closed" | "replaced" = "closed") =>
Effect.gen(function* () {
@@ -69,6 +98,7 @@ export const make = Effect.fn("BrowserConnection.make")(function* (
}),
(browser) => (browsers.get(input.sessionID) === browser ? close(input.sessionID) : Effect.void),
)
yield* grant(input.sessionID)
yield* rpc.events
.emit("control", { type: "attached", connectionID: input.connectionID, version: 4 })
.pipe(Effect.orDie)