Official

opencode

OpenCode agent provider: runs opencode serve and talks to it over HTTP and server-sent events.

The app opens the listing; nothing installs until an agent in your Plugins workspace has read the files and you enable the plugin. In a terminal: cvg install convergence/opencode@0.2.0

Permissions in 0.2.0

  • Read files fs.readMediumReads files in the listed places.Read, once, the servers the previous OpenCode provider keptPlaces: its own data folder
  • Provide agents agents.provideMediumAdds agents to the app.Provide the OpenCode agent, and serve plugin tools to it through the host's loopback MCP server
  • Run named programs processMediumStarts the listed programs.Run the OpenCode server (`opencode serve`, or the binary you choose in Settings), read its catalog from the command line when the server cannot answer, upgrade it (`opencode upgrade`), and ask or tell the npm installation that owns it about a newer versionPrograms: opencodenpm${settings.binaryPath}
  • Network access netMediumConnects to the listed hosts.Talk to the OpenCode server it starts on this computer, at the port it picks for each launch, and to the external server you choose in SettingsHosts: localhost:*${settings.serverUrl}
  • Environment variables envMediumReads the listed environment variables.Expand ~ in a configured binary, and read the OpenCode settings you set in the environment: the binary to run, an external server's address, and the server's user name and passwordVariables: HOMEOPENCODE_PATHOPENCODE_SERVER_URLOPENCODE_SERVER_USERNAMEOPENCODE_SERVER_PASSWORDCONVERGENCE_OPENCODE_SERVER_PASSWORD

Files

permission.ts3.5 KB
// The session permission ruleset Convergence pushes for a chat (the Rust
// `permission.rs`).
//
// OpenCode decides a request against the rules stored on the session, and
// a session created without rules inherits whatever the chosen agent
// happens to allow. Pushing an explicit ruleset is the only way the
// composer's permission mode means the same thing here as anywhere else.
import { permissionMode } from "../sdk/agent.ts";

export interface Rule {
  permission: string;
  pattern: string;
  action: string;
}

const rule = (permission: string, pattern: string, action: string): Rule => ({ permission, pattern, action });

/// The ruleset for a shared permission mode. The server applies the
/// **last** matching rule, so order carries meaning: a narrow rule only
/// wins when it comes after the broad one it refines.
///
/// Auto is read as Supervised: OpenCode has no approval reviewer, so the
/// only honest reading of "let the agent approve routine actions" is to
/// keep asking. The option this plugin publishes leaves Auto out for the
/// same reason; a chat stored before that still carries the value.
export function ruleset(mode: string): Rule[] {
  if (mode === permissionMode.FULL) {
    // Reads outside the workspace are a permission of their own that the
    // wildcard does not cover.
    return [rule("*", "*", "allow"), rule("external_directory", "*", "allow")];
  }
  // "Auto-accept edits" promises edits without a prompt and a prompt for
  // everything else, so the mode changes this one action and no other.
  const edits = mode === permissionMode.AUTO_EDITS ? "allow" : "ask";
  return [
    rule("*", "*", "ask"),
    rule("read", "*", "allow"),
    // OpenCode asks before reading environment files whatever the agent
    // allows, and a blanket read rule would quietly remove that.
    rule("read", "*.env", "ask"),
    rule("read", "*.env.*", "ask"),
    rule("read", "*.env.example", "allow"),
    rule("glob", "*", "allow"),
    rule("grep", "*", "allow"),
    rule("lsp", "*", "allow"),
    rule("skill", "*", "allow"),
    rule("todowrite", "*", "allow"),
    rule("bash", "*", "ask"),
    rule("edit", "*", edits),
    rule("webfetch", "*", "ask"),
    rule("websearch", "*", "ask"),
    rule("codesearch", "*", "ask"),
    rule("external_directory", "*", "ask"),
    rule("doom_loop", "*", "ask"),
    // The question tool only elicits an answer; asking to ask is noise.
    rule("question", "*", "allow"),
  ];
}

function sameRule(a: Rule | null | undefined, b: Rule | null | undefined): boolean {
  return a?.permission === b?.permission && a?.pattern === b?.pattern && a?.action === b?.action;
}

/// Whether the rules a session carries already end with `wanted`. A pushed
/// ruleset is appended to the session's rules rather than replacing them
/// (1.18.29), which is still correct because the last match wins, but
/// pushing the same rules on every resume would grow a long-lived chat's
/// list without bound.
export function alreadyApplies(current: unknown, wanted: readonly Rule[]): boolean {
  if (!Array.isArray(current) || !wanted.length || current.length < wanted.length) return false;
  const tail = current.slice(current.length - wanted.length);
  return tail.every((entry, index) => sameRule(entry as Rule, wanted[index]));
}

/// Whether the mode answers permission requests without the user. The
/// ruleset alone is not enough: OpenCode starts subagent sessions with
/// rules of their own, so a request can still reach us in full access.
export function repliesAutomatically(mode: string): boolean {
  return mode === permissionMode.FULL;
}

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <394.8 KB5 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.