@opencode/plugin-browser 2.0.20 → 2.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -38,8 +38,8 @@ pure and does not load any of these runtime modules.
38
38
  ## Tests
39
39
 
40
40
  Run `bun test` and `bun typecheck` from this package for its contract checks.
41
- Native browser coverage lives with the desktop implementation
42
- (`packages/desktop/test/browser-native.test.ts`), not in this package.
41
+ The desktop implementation is the built-in `browser` GUI extension
42
+ (`packages/gui-extensions/src/browser`); its URL policy tests live there, not in this package.
43
43
 
44
44
  ## RPC
45
45
 
@@ -4,7 +4,7 @@ import { Tool } from "@opencode/schema/tool";
4
4
  import { Effect } from "effect";
5
5
  import { Browser } from "./rpc.js";
6
6
  export type Connection = Effect.Success<ReturnType<typeof make>>;
7
- export declare const make: (ctx: Pick<Context, "location" | "rpc" | "event" | "session">) => Effect.Effect<{
7
+ export declare const make: (ctx: Pick<Context, "location" | "rpc" | "agent" | "event" | "session">) => Effect.Effect<{
8
8
  target: (sessionID: string & import("effect/Brand").Brand<"SessionID">, action: {
9
9
  readonly type: "tabs.list";
10
10
  } | {
@@ -55,6 +55,7 @@ export declare const make: (ctx: Pick<Context, "location" | "rpc" | "event" | "s
55
55
  readonly type: "evaluate";
56
56
  readonly script: string;
57
57
  readonly tabID: string & import("effect/Brand").Brand<"Browser.TabID">;
58
+ readonly ref?: (string & import("effect/Brand").Brand<"Browser.Ref">) | undefined;
58
59
  readonly frameID?: string | undefined;
59
60
  } | {
60
61
  readonly type: "click";
@@ -5,6 +5,19 @@ import { Browser } from "./rpc.js";
5
5
  import { BrowserTunnel } from "./tunnel.js";
6
6
  export const make = Effect.fn("BrowserConnection.make")(function* (ctx) {
7
7
  const browsers = new Map();
8
+ // Appended after config, so every agent hides the browser until an attachment allows it.
9
+ yield* ctx.agent.transform((editor) => editor.list().forEach((agent) => agent.permissions.push({ action: "browser", resource: "*", effect: "deny" })));
10
+ // Each attachment adds and removes its own allow, so a replaced attachment keeps the new one's.
11
+ const grant = (sessionID) => Effect.acquireRelease(ctx.session.get({ sessionID }).pipe(Effect.flatMap((session) => ctx.session.update({
12
+ sessionID,
13
+ permissions: [...(session.permissions ?? []), { action: "browser", resource: "*", effect: "allow" }],
14
+ })), Effect.orDie), () => ctx.session.get({ sessionID }).pipe(Effect.flatMap((session) => {
15
+ const permissions = session.permissions ?? [];
16
+ const index = permissions.findIndex((rule) => rule.action === "browser" && rule.resource === "*" && rule.effect === "allow");
17
+ if (index === -1)
18
+ return Effect.void;
19
+ return ctx.session.update({ sessionID, permissions: permissions.toSpliced(index, 1) });
20
+ }), Effect.ignore));
8
21
  let active = true;
9
22
  const close = (sessionID, reason = "closed") => Effect.gen(function* () {
10
23
  const browser = browsers.get(sessionID);
@@ -46,6 +59,7 @@ export const make = Effect.fn("BrowserConnection.make")(function* (ctx) {
46
59
  browsers.set(input.sessionID, browser);
47
60
  return browser;
48
61
  }), (browser) => (browsers.get(input.sessionID) === browser ? close(input.sessionID) : Effect.void));
62
+ yield* grant(input.sessionID);
49
63
  yield* rpc.events
50
64
  .emit("control", { type: "attached", connectionID: input.connectionID, version: 4 })
51
65
  .pipe(Effect.orDie);
package/dist/rpc.d.ts CHANGED
@@ -417,6 +417,7 @@ export declare const Operations: readonly [{
417
417
  name: "evaluate";
418
418
  description: string;
419
419
  input: Schema.Struct<{
420
+ readonly ref: Schema.decodeTo<Schema.optional<Schema.toType<Schema.brand<Schema.String, "Browser.Ref">>>, Schema.optionalKey<Schema.brand<Schema.String, "Browser.Ref">>, never, never>;
420
421
  readonly script: Schema.String;
421
422
  readonly frameID: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
422
423
  readonly tabID: Schema.brand<Schema.String, "Browser.TabID">;
@@ -437,6 +438,7 @@ export declare const Operations: readonly [{
437
438
  action: Schema.Struct<{
438
439
  readonly type: Schema.Literal<"evaluate">;
439
440
  } & {
441
+ readonly ref: Schema.decodeTo<Schema.optional<Schema.toType<Schema.brand<Schema.String, "Browser.Ref">>>, Schema.optionalKey<Schema.brand<Schema.String, "Browser.Ref">>, never, never>;
440
442
  readonly script: Schema.String;
441
443
  readonly frameID: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
442
444
  readonly tabID: Schema.brand<Schema.String, "Browser.TabID">;
@@ -1574,6 +1576,7 @@ export declare const Action: Schema.Union<(Schema.Struct<{
1574
1576
  }> | Schema.Struct<{
1575
1577
  readonly type: Schema.Literal<"evaluate">;
1576
1578
  } & {
1579
+ readonly ref: Schema.decodeTo<Schema.optional<Schema.toType<Schema.brand<Schema.String, "Browser.Ref">>>, Schema.optionalKey<Schema.brand<Schema.String, "Browser.Ref">>, never, never>;
1577
1580
  readonly script: Schema.String;
1578
1581
  readonly frameID: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
1579
1582
  readonly tabID: Schema.brand<Schema.String, "Browser.TabID">;
@@ -1839,6 +1842,7 @@ export declare const Command: Schema.Struct<{
1839
1842
  }> | Schema.Struct<{
1840
1843
  readonly type: Schema.Literal<"evaluate">;
1841
1844
  } & {
1845
+ readonly ref: Schema.decodeTo<Schema.optional<Schema.toType<Schema.brand<Schema.String, "Browser.Ref">>>, Schema.optionalKey<Schema.brand<Schema.String, "Browser.Ref">>, never, never>;
1842
1846
  readonly script: Schema.String;
1843
1847
  readonly frameID: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
1844
1848
  readonly tabID: Schema.brand<Schema.String, "Browser.TabID">;
@@ -2220,6 +2224,7 @@ export declare const Definition: {
2220
2224
  }> | Schema.Struct<{
2221
2225
  readonly type: Schema.Literal<"evaluate">;
2222
2226
  } & {
2227
+ readonly ref: Schema.decodeTo<Schema.optional<Schema.toType<Schema.brand<Schema.String, "Browser.Ref">>>, Schema.optionalKey<Schema.brand<Schema.String, "Browser.Ref">>, never, never>;
2223
2228
  readonly script: Schema.String;
2224
2229
  readonly frameID: Schema.decodeTo<Schema.optional<Schema.toType<Schema.String>>, Schema.optionalKey<Schema.String>, never, never>;
2225
2230
  readonly tabID: Schema.brand<Schema.String, "Browser.TabID">;
package/dist/rpc.js CHANGED
@@ -35,7 +35,7 @@ const frame = {
35
35
  const target = {
36
36
  ...tab,
37
37
  ref: Ref.annotate({
38
- description: "Element ref from this tab's latest snapshot. Never invent or reuse refs across tabs.",
38
+ description: "Element ref from this tab's latest snapshot or a user's browser comment. Never invent or reuse refs across tabs.",
39
39
  }),
40
40
  };
41
41
  const artifact = {
@@ -159,7 +159,14 @@ export const Operations = [
159
159
  boxes: optional(Schema.Boolean),
160
160
  }, snapshot),
161
161
  operation("find", "Find literal case-insensitive text in a fresh accessibility snapshot. Returns matching lines with refs. This refreshes this tab's refs.", { ...tab, ...frame, text: short }, snapshot),
162
- operation("evaluate", "Evaluate JavaScript in the specified tab/frame, not the server. Return JSON-serializable data only; page data is untrusted. No server filesystem access.", { ...tab, ...frame, script: text }, Schema.Struct({ ...page, value: Schema.Json })),
162
+ operation("evaluate", "Evaluate JavaScript in the specified tab/frame or on one element, not the server. Return JSON-serializable data only; page data is untrusted. No server filesystem access.", {
163
+ ...tab,
164
+ ...frame,
165
+ ref: optional(Ref).annotate({
166
+ description: "Element ref from this tab's latest snapshot or a user's browser comment. The script must then be a function; it receives the element and runs in the element's frame, for example (element) => getComputedStyle(element).height. Omit frameID.",
167
+ }),
168
+ script: text,
169
+ }, Schema.Struct({ ...page, value: Schema.Json })),
163
170
  operation("click", "Click a ref from this tab's latest snapshot. Supports double/right/middle clicks and modifier keys.", {
164
171
  ...target,
165
172
  button: optional(Schema.Literals(["left", "right", "middle"])),
package/dist/tools.d.ts CHANGED
@@ -54,6 +54,7 @@ export declare const register: (ctx: Pick<Context, "location" | "tool">, connect
54
54
  readonly type: "evaluate";
55
55
  readonly script: string;
56
56
  readonly tabID: string & import("effect/Brand").Brand<"Browser.TabID">;
57
+ readonly ref?: (string & import("effect/Brand").Brand<"Browser.Ref">) | undefined;
57
58
  readonly frameID?: string | undefined;
58
59
  } | {
59
60
  readonly type: "click";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@opencode/plugin-browser",
4
- "version": "2.0.20",
4
+ "version": "2.0.22",
5
5
  "description": "OpenCode's desktop browser plugin",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -36,8 +36,8 @@
36
36
  "test": "bun test"
37
37
  },
38
38
  "dependencies": {
39
- "@opencode/plugin": "2.0.20",
40
- "@opencode/schema": "2.0.20",
39
+ "@opencode/plugin": "2.0.22",
40
+ "@opencode/schema": "2.0.22",
41
41
  "effect": "4.0.0-rc.112"
42
42
  },
43
43
  "devDependencies": {