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

legacy.test.ts2.8 KB
// Scripted scenarios for the codex conversion.
import { test } from "node:test";
import assert from "node:assert/strict";
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { activate } from "./main.ts";
import * as Effect from "effect/Effect";
import { liveLayer } from "convergence/effect";
import type { Api } from "convergence";
import { adoptLegacy as adoptLegacyEffect, legacyDir, mergeInstances } from "./legacy.ts";
import type { HostScript } from "../sdk/testing.ts";
import { diskFs, fakeApi, memoryStorage, settle } from "../sdk/testing.ts";

async function dataFolder(instances: string) {
  const data = await mkdtemp(join(tmpdir(), "codex-legacy-"));
  await mkdir(join(data, "legacy"), { recursive: true });
  await writeFile(join(data, "legacy", "instances.json"), instances);
  return data;
}

function apiWith(data: string, storage: HostScript, fs = diskFs()) {
  const api = fakeApi({ onHost: storage, fs });
  api.paths = { data };
  return api;
}

test("the stored accounts keep theirs and gain the legacy ones they lack", () => {
  assert.deepEqual(mergeInstances([{ id: "a", name: "Mine" }], [{ id: "a", name: "Old" }, { id: "b" }]), [
    { id: "a", name: "Mine" },
    { id: "b" },
  ]);
  assert.equal(mergeInstances(null, { id: "a" }), null, "not a list");
});

test("the Rust plugin's accounts register, taken into storage once", async (t) => {
  const data = await dataFolder(JSON.stringify([{ id: "work", name: "Codex Work", home: "~/.codex_work" }]));
  const storage = memoryStorage();
  const api = apiWith(data, storage);
  t.after(activate(api));
  await settle(20);
  assert.deepEqual(
    api.registered.map((def) => def.id),
    ["codex", "codex:work"],
  );
  assert.deepEqual(storage.values.get("instances"), [{ id: "work", name: "Codex Work", home: "~/.codex_work" }]);
  assert.ok(storage.values.get("legacy"));
  await writeFile(join(data, "legacy", "instances.json"), JSON.stringify([{ id: "late" }]));
  assert.equal(await adoptLegacy(api), false, "read once");
});

test("a broken file is left out, a refused read is tried again", async () => {
  const data = await dataFolder("{ not json");
  const storage = memoryStorage();
  assert.equal(await adoptLegacy(apiWith(data, storage)), false);
  assert.equal(storage.values.has("instances"), false);
  assert.ok(storage.values.get("legacy"));

  const refused = memoryStorage();
  assert.equal(await adoptLegacy(apiWith(data, refused, diskFs({ allow: () => false }))), false);
  assert.equal(refused.values.has("legacy"), false);
});

const adoptLegacy = (api: Api) =>
  Effect.runPromise(
    adoptLegacyEffect(legacyDir(api)).pipe(
      Effect.provide(
        liveLayer(
          api,
          () => Promise.reject(new Error("unexpected handler")),
          () => {},
        ),
      ),
    ),
  );

Versions

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

Reviews and comments

0 threads · 0 reviews

No comments yet.