Official

codex

Codex agent provider: runs the Codex CLI's app-server for each account.

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/codex@0.2.0

Permissions in 0.2.0

  • Provide agents agents.provideMediumAdds agents to the app.Provide the Codex agent and pass its tool calls to plugin tools
  • Run named programs processMediumStarts the listed programs.Run the Codex CLI, and ask or tell the installer that owns it (npm or Homebrew) about a newer versionPrograms: codexnpmbrew
  • Environment variables envMediumReads the listed environment variables.Find each account's Codex home, and pass extra app-server arguments set in CODEX_ARGSVariables: HOMECODEX_HOMECODEX_ARGS
  • Read files fs.readMediumReads files in the listed places.Read, once, the accounts the previous Codex provider keptPlaces: its own data folder

Files

instances.ts3.1 KB
// Codex accounts, each served as its own Convergence agent.
//
// One Codex install can hold several logins, kept apart by `CODEX_HOME`.
// The accounts are the plugin's stored value `instances` (plugin storage,
// key `instances`), a list like
//
//   [{ "id": "personal", "name": "Codex Personal", "home": "~/.codex_personal", "args": [] }]
//
// Each entry becomes the agent id `codex:<id>`, beside the default
// `codex`. Instances that share a home share Codex's own thread storage,
// so a chat can move between them; instances with different homes cannot,
// which is what `continuationKey` tells the UI.
import * as z from "zod";
export interface Instance {
  id: string;
  name: string | null;
  home: string | null;
  args: string[];
}
const entrySchema = z.object({
  id: z.unknown().optional(),
  name: z.unknown().optional(),
  home: z.unknown().optional(),
  args: z.array(z.unknown()).optional(),
});

/// The default account, which uses the ambient environment.
export function defaultAccount(): Instance {
  return { id: "", name: null, home: null, args: [] };
}

export function agentId(instance: Pick<Instance, "id">) {
  return instance.id ? `codex:${instance.id}` : "codex";
}

export function displayName(instance: Pick<Instance, "id" | "name">) {
  if (typeof instance.name === "string" && instance.name.trim()) return instance.name;
  return instance.id ? `Codex (${instance.id})` : "Codex";
}

/// Where this account keeps its threads and its login: its own home,
/// else `CODEX_HOME`, else `~/.codex`. `env` holds the login environment's
/// `HOME` and `CODEX_HOME` (either may be missing). `null` when no home
/// can be named.
export function homeOf(instance: Pick<Instance, "home">, env: Record<string, string> = {}) {
  const own = typeof instance.home === "string" ? instance.home.trim() : "";
  if (own) return expand(own, env.HOME);
  if (env.CODEX_HOME) return env.CODEX_HOME;
  return env.HOME ? `${env.HOME.replace(/\/+$/, "")}/.codex` : null;
}

function expand(path: string, home?: string) {
  if (path.startsWith("~/")) return home ? `${home.replace(/\/+$/, "")}/${path.slice(2)}` : path;
  return path;
}

/// Every configured account, the default one first. Entries with a
/// missing or repeated id are left out (and reported), never the default.
export function parseInstances(value: unknown) {
  const all = [defaultAccount()];
  if (value === null || value === undefined) return all;
  if (!Array.isArray(value)) {
    console.warn("the stored codex instances are not a list; only the default account is served");
    return all;
  }
  for (const raw of value) {
    const parsed = entrySchema.safeParse(raw);
    const entry = parsed.success ? parsed.data : {};
    const id = typeof entry?.id === "string" ? entry.id.trim() : "";
    if (!id || all.some((known) => known.id === id)) {
      console.warn("ignoring a codex instance with a missing or repeated id");
      continue;
    }
    all.push({
      id,
      name: typeof entry.name === "string" ? entry.name : null,
      home: typeof entry.home === "string" ? entry.home : null,
      args: Array.isArray(entry.args) ? entry.args.filter((arg) => typeof arg === "string") : [],
    });
  }
  return all;
}

Versions

VersionPublishedPlugin APISizePermissionsStatus
0.2.0latestOct 5, 2026>=2 <378.1 KB4 permissionsListed

Reviews and comments

0 threads · 0 reviews

No comments yet.