feat(omo-codex): batch 94 (13 files)

This commit is contained in:
YeonGyu-Kim
2026-05-30 19:12:17 +09:00
parent b4655c18d7
commit 2a0d8822eb
13 changed files with 1048 additions and 0 deletions
@@ -0,0 +1,84 @@
import { basename, join } from "node:path";
import { copyFile, lstat, mkdir, readdir, rm, symlink, writeFile } from "node:fs/promises";
import { exists } from "./utils.mjs";
const MANIFEST_FILE = ".installed-agents.json";
export async function linkCachedPluginAgents({ codexHome, pluginRoot, platform = process.platform }) {
const bundledAgents = await discoverBundledAgents(pluginRoot);
if (bundledAgents.length === 0) {
await writeManifest(pluginRoot, []);
return [];
}
const agentsDir = join(codexHome, "agents");
await mkdir(agentsDir, { recursive: true });
const linked = [];
for (const agentPath of bundledAgents) {
const linkPath = join(agentsDir, basename(agentPath));
if (platform === "win32") {
await replaceWithCopy(linkPath, agentPath);
} else {
await replaceWithSymlink(linkPath, agentPath);
}
linked.push({ name: basename(agentPath), path: linkPath, target: agentPath });
}
await writeManifest(pluginRoot, linked.map((entry) => entry.path));
return linked;
}
async function discoverBundledAgents(pluginRoot) {
const componentsRoot = join(pluginRoot, "components");
if (!(await exists(componentsRoot))) return [];
const componentEntries = await readdir(componentsRoot, { withFileTypes: true });
const agents = [];
for (const entry of componentEntries) {
if (!entry.isDirectory()) continue;
const agentsRoot = join(componentsRoot, entry.name, "agents");
if (!(await exists(agentsRoot))) continue;
const agentEntries = await readdir(agentsRoot, { withFileTypes: true });
for (const file of agentEntries) {
if (!file.isFile() || !file.name.endsWith(".toml")) continue;
agents.push(join(agentsRoot, file.name));
}
}
agents.sort();
return agents;
}
async function replaceWithSymlink(linkPath, target) {
await prepareReplacement(linkPath);
await symlink(target, linkPath);
}
async function replaceWithCopy(linkPath, target) {
await prepareReplacement(linkPath);
await copyFile(target, linkPath);
}
async function prepareReplacement(linkPath) {
if (!(await lstatExists(linkPath))) return;
const entryStat = await lstat(linkPath);
if (entryStat.isDirectory() && !entryStat.isSymbolicLink()) {
throw new Error(`${linkPath} already exists and is a directory; refusing to replace`);
}
await rm(linkPath, { force: true });
}
async function writeManifest(pluginRoot, agentPaths) {
const manifestPath = join(pluginRoot, MANIFEST_FILE);
const payload = { agents: [...agentPaths].sort() };
await writeFile(manifestPath, `${JSON.stringify(payload, null, "\t")}\n`);
}
async function lstatExists(path) {
try {
await lstat(path);
return true;
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
throw error;
}
}
@@ -0,0 +1,245 @@
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { cp, lstat, mkdir, readFile, readdir, readlink, rename, rm, symlink, writeFile } from "node:fs/promises";
import { createCachedMcpRuntimeArgRewriter } from "./mcp-runtime-cache.mjs";
import { exists, isRecord } from "./utils.mjs";
import { COMMAND_SHIM_MARKER } from "./command-shim.mjs";
import { removeLegacyCodexComponentBins } from "./legacy-bins.mjs";
export async function installCachedPlugin({ codexHome, marketplaceName, name, runCommand, sourcePath, version }) {
await maybeRunNpmInstall(sourcePath, runCommand);
await maybeRunNpmBuild(sourcePath, runCommand);
const targetPath = join(codexHome, "plugins", "cache", marketplaceName, name, version);
await replaceDirectory(sourcePath, targetPath, shouldCopyPluginPath);
await rewriteCachedPackageLocalFileDependencies(targetPath, sourcePath);
await maybeRunNpmInstall(targetPath, runCommand, ["install", "--omit=dev"]);
await rewriteCachedMcpManifest(targetPath, sourcePath);
return { name, version, path: targetPath };
}
export async function pruneMarketplaceCache({ codexHome, marketplaceName, keepPluginNames }) {
const cacheRoot = join(codexHome, "plugins", "cache", marketplaceName);
if (!(await exists(cacheRoot))) return;
const keep = new Set(keepPluginNames);
const entries = await readdir(cacheRoot, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isDirectory() || keep.has(entry.name)) continue;
await rm(join(cacheRoot, entry.name), { recursive: true, force: true });
}
}
export async function pruneMarketplacePluginCaches({ codexHome, marketplaceName, pluginNames }) {
const cacheRoot = join(codexHome, "plugins", "cache", marketplaceName);
if (!(await exists(cacheRoot))) return;
for (const pluginName of pluginNames) {
await rm(join(cacheRoot, pluginName), { recursive: true, force: true });
}
if ((await readdir(cacheRoot)).length === 0) {
await rm(cacheRoot, { recursive: true, force: true });
}
}
export async function linkCachedPluginBins({ binDir, pluginRoot, platform = process.platform }) {
const binLinks = await discoverPackageBins(pluginRoot);
await mkdir(binDir, { recursive: true });
await removeLegacyCodexComponentBins(binDir, platform);
const linked = [];
for (const link of binLinks) {
const linkPath = await linkCachedPluginBin(binDir, link, platform);
linked.push({ name: link.name, path: linkPath, target: link.target });
}
return linked;
}
async function linkCachedPluginBin(binDir, link, platform) {
if (platform === "win32") {
const linkPath = join(binDir, `${link.name}.cmd`);
await replaceCommandShim(linkPath, link.target);
return linkPath;
}
const linkPath = join(binDir, link.name);
await replaceSymlink(linkPath, link.target);
return linkPath;
}
async function maybeRunNpmInstall(cwd, runCommand, args = ["install"]) {
if (!(await exists(join(cwd, "package.json")))) return;
await runCommand("npm", args, { cwd });
}
async function maybeRunNpmBuild(cwd, runCommand) {
if (!(await exists(join(cwd, "package.json")))) return;
const packageJson = JSON.parse(await readFile(join(cwd, "package.json"), "utf8"));
if (!isRecord(packageJson.scripts) || typeof packageJson.scripts.build !== "string") return;
await runCommand("npm", ["run", "build"], { cwd });
}
async function replaceDirectory(sourcePath, targetPath, filter) {
await mkdir(dirname(targetPath), { recursive: true });
const tempPath = join(dirname(targetPath), `.tmp-${basename(targetPath)}-${process.pid}-${Date.now()}`);
await rm(tempPath, { recursive: true, force: true });
await cp(sourcePath, tempPath, {
recursive: true,
filter: (source) => filter(source, sourcePath),
});
await rm(targetPath, { recursive: true, force: true });
await rename(tempPath, targetPath);
}
async function discoverPackageBins(root) {
const links = [];
await collectPackageBins(root, root, links);
return links;
}
async function collectPackageBins(directory, root, links) {
const entries = await readdir(directory, { withFileTypes: true });
const packageJsonPath = join(directory, "package.json");
if (entries.some((entry) => entry.isFile() && entry.name === "package.json")) {
await appendPackageBinLinks(packageJsonPath, directory, links);
}
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
const childPath = join(directory, entry.name);
if (!childPath.startsWith(root)) continue;
await collectPackageBins(childPath, root, links);
}
}
async function appendPackageBinLinks(packageJsonPath, packageRoot, links) {
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
if (!isRecord(packageJson)) return;
const bin = packageJson.bin;
if (typeof bin === "string" && typeof packageJson.name === "string") {
links.push({ name: basename(packageJson.name), target: join(packageRoot, bin) });
return;
}
if (!isRecord(bin)) return;
for (const [name, target] of Object.entries(bin)) {
if (typeof target !== "string") continue;
links.push({ name, target: join(packageRoot, target) });
}
}
async function replaceSymlink(linkPath, targetPath) {
if (await existingNonSymlink(linkPath)) {
throw new Error(`${linkPath} already exists and is not a symlink`);
}
await rm(linkPath, { force: true });
await symlink(targetPath, linkPath);
}
async function replaceCommandShim(linkPath, targetPath) {
if (await existingNonShim(linkPath)) {
throw new Error(`${linkPath} already exists and is not a command shim`);
}
await writeFile(linkPath, `@echo off\r\n${COMMAND_SHIM_MARKER}\r\nnode "${targetPath}" %*\r\n`);
}
async function existingNonShim(path) {
try {
const stat = await lstat(path);
if (!stat.isFile()) return true;
const content = await readFile(path, "utf8");
if (content.includes(COMMAND_SHIM_MARKER)) return false;
throw new Error(`${path} already exists and is not a generated command shim`);
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
throw error;
}
}
async function existingNonSymlink(path) {
try {
const stat = await lstat(path);
if (!stat.isSymbolicLink()) return true;
await readlink(path);
return false;
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
throw error;
}
}
function shouldCopyPluginPath(path, root) {
const relative = path === root ? "" : path.slice(root.length + sep.length);
if (relative === "") return true;
const parts = relative.split(sep);
if (parts[parts.length - 1] === "package-lock.json") return false;
return !parts.some((part) => part === ".git" || part === "node_modules");
}
export async function rewriteCachedMcpManifest(pluginRoot, sourceRoot = pluginRoot) {
const manifestPath = join(pluginRoot, ".mcp.json");
if (!(await exists(manifestPath))) return;
const raw = await readFile(manifestPath, "utf8");
const parsed = JSON.parse(raw);
if (!isRecord(parsed) || !isRecord(parsed.mcpServers)) return;
let changed = false;
const rewriteRuntimeArg = createCachedMcpRuntimeArgRewriter();
for (const [serverName, server] of Object.entries(parsed.mcpServers)) {
if (!isRecord(server)) continue;
if (server.cwd === "." || server.cwd === "./") {
delete server.cwd;
changed = true;
}
if (!Array.isArray(server.args)) continue;
const nextArgs = await Promise.all(
server.args.map((arg) => rewriteRuntimeArg({ arg, pluginRoot, serverName, sourceRoot })),
);
if (nextArgs.some((value, index) => value !== server.args[index])) {
server.args = nextArgs;
changed = true;
}
}
if (changed) await writeFile(manifestPath, `${JSON.stringify(parsed, null, "\t")}\n`);
}
async function rewriteCachedPackageLocalFileDependencies(pluginRoot, sourceRoot) {
const packageJsonPaths = [];
await collectPackageJsonPaths(pluginRoot, pluginRoot, packageJsonPaths);
for (const packageJsonPath of packageJsonPaths) {
const raw = await readFile(packageJsonPath, "utf8");
const parsed = JSON.parse(raw);
if (!isRecord(parsed)) continue;
const packageDir = dirname(packageJsonPath);
const sourcePackageDir = join(sourceRoot, relative(pluginRoot, packageDir));
let changed = false;
for (const field of ["dependencies", "optionalDependencies", "peerDependencies"]) {
const dependencies = parsed[field];
if (!isRecord(dependencies)) continue;
for (const [name, specifier] of Object.entries(dependencies)) {
if (typeof specifier !== "string" || !specifier.startsWith("file:")) continue;
const filePath = specifier.slice("file:".length);
if (filePath.length === 0 || isAbsolute(filePath)) continue;
const targetPath = resolve(packageDir, filePath);
if (isPathInside(targetPath, pluginRoot)) continue;
dependencies[name] = `file:${resolve(sourcePackageDir, filePath)}`;
changed = true;
}
}
if (changed) await writeFile(packageJsonPath, `${JSON.stringify(parsed, null, "\t")}\n`);
}
}
async function collectPackageJsonPaths(directory, root, paths) {
const entries = await readdir(directory, { withFileTypes: true });
if (entries.some((entry) => entry.isFile() && entry.name === "package.json")) {
paths.push(join(directory, "package.json"));
}
for (const entry of entries) {
if (!entry.isDirectory()) continue;
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
const childPath = join(directory, entry.name);
if (!childPath.startsWith(root)) continue;
await collectPackageJsonPaths(childPath, root, paths);
}
}
function isPathInside(candidatePath, rootPath) {
const pathFromRoot = relative(rootPath, candidatePath);
return pathFromRoot === "" || (!pathFromRoot.startsWith("..") && !isAbsolute(pathFromRoot));
}
@@ -0,0 +1 @@
export const COMMAND_SHIM_MARKER = ":: generated by oh-my-openagent Codex installer";
@@ -0,0 +1,229 @@
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import { ensureCodexMultiAgentV2Config } from "./multi-agent-v2-config.mjs";
import { appendBlock, findTomlSection, replaceOrInsertSetting } from "./toml-editor.mjs";
import { exists } from "./utils.mjs";
const LEGACY_CODEX_PLUGIN_MARKETPLACE = ["code", "yeongyu", "codex", "plugins"].join("-");
const SISYPHUS_LEGACY_MARKETPLACES = ["lazycodex", LEGACY_CODEX_PLUGIN_MARKETPLACE];
const MANAGED_CODEX_AGENT_NAMES = [
"codex-ultrawork-reviewer",
"explorer",
"librarian",
"metis",
"momus",
"plan",
];
export async function updateCodexConfig({
configPath,
repoRoot,
marketplaceName,
marketplaceSource = defaultMarketplaceSource(marketplaceName, repoRoot),
pluginNames,
trustedHookStates = [],
agentConfigs = [],
}) {
await mkdir(dirname(configPath), { recursive: true });
let config = "";
if (await exists(configPath)) config = await readFile(configPath, "utf8");
for (const legacyMarketplaceName of legacyMarketplaceNames(marketplaceName)) {
config = removeMarketplaceBlock(config, legacyMarketplaceName);
config = removeStaleMarketplacePluginBlocks(config, legacyMarketplaceName, new Set());
config = removeStaleMarketplaceHookStateBlocks(config, legacyMarketplaceName, new Set());
}
config = removeStaleMarketplacePluginBlocks(config, marketplaceName, new Set(pluginNames));
config = removeStaleMarketplaceHookStateBlocks(config, marketplaceName, new Set(pluginNames));
config = removeStaleManagedAgentBlocks(config, new Set(agentConfigs.map((agentConfig) => agentConfig.name)));
config = ensureFeatureEnabled(config, "plugins");
config = ensureFeatureEnabled(config, "plugin_hooks");
config = ensureCodexMultiAgentV2Config(config);
config = ensureMarketplaceBlock(config, marketplaceName, marketplaceSource);
for (const pluginName of pluginNames) {
config = ensurePluginEnabled(config, `${pluginName}@${marketplaceName}`);
}
for (const state of trustedHookStates) {
config = ensureHookTrusted(config, state.key, state.trustedHash);
}
for (const agentConfig of agentConfigs) {
config = ensureAgentConfig(config, agentConfig);
}
await writeFile(configPath, config.trimEnd() + "\n");
}
function legacyMarketplaceNames(marketplaceName) {
return marketplaceName === "sisyphuslabs" ? SISYPHUS_LEGACY_MARKETPLACES : [];
}
function removeMarketplaceBlock(config, marketplaceName) {
return removeTomlSections(config, (header) => header === `marketplaces.${marketplaceName}`);
}
function defaultMarketplaceSource(marketplaceName, repoRoot) {
return {
sourceType: "local",
source: repoRoot,
};
}
function removeStaleMarketplacePluginBlocks(config, marketplaceName, keepPluginNames) {
return removeTomlSections(config, (header) => {
const pluginKey = parsePluginHeaderKey(header);
if (pluginKey === null) return false;
const suffix = `@${marketplaceName}`;
if (!pluginKey.endsWith(suffix)) return false;
return !keepPluginNames.has(pluginKey.slice(0, -suffix.length));
});
}
function removeStaleMarketplaceHookStateBlocks(config, marketplaceName, keepPluginNames) {
return removeTomlSections(config, (header) => {
const prefix = "hooks.state.";
if (!header.startsWith(prefix)) return false;
const hookKey = parseJsonString(header.slice(prefix.length));
if (hookKey === null) return false;
const separator = hookKey.indexOf(":");
if (separator === -1) return false;
const pluginKey = hookKey.slice(0, separator);
const suffix = `@${marketplaceName}`;
if (!pluginKey.endsWith(suffix)) return false;
return !keepPluginNames.has(pluginKey.slice(0, -suffix.length));
});
}
function removeStaleManagedAgentBlocks(config, keepAgentNames) {
const managedAgentNames = new Set(MANAGED_CODEX_AGENT_NAMES);
return splitTomlSections(config)
.filter((section) => {
if (section.header === null) return true;
const agentName = parseAgentHeaderName(section.header);
if (agentName === null || !managedAgentNames.has(agentName) || keepAgentNames.has(agentName)) return true;
return !section.text.includes(`config_file = ${JSON.stringify(`./agents/${agentName}.toml`)}`);
})
.map((section) => section.text)
.join("")
.replace(/\n{3,}/g, "\n\n");
}
function ensureFeatureEnabled(config, featureName) {
const section = findTomlSection(config, "features");
if (!section) return appendBlock(config, `[features]\n${featureName} = true\n`);
return replaceOrInsertSetting(config, section, featureName, "true");
}
function ensureMarketplaceBlock(config, marketplaceName, source) {
const header = `marketplaces.${marketplaceName}`;
const block = [
`[${header}]`,
`last_updated = "${new Date().toISOString().replace(/\.\d{3}Z$/, "Z")}"`,
`source_type = ${JSON.stringify(source.sourceType)}`,
`source = ${JSON.stringify(source.source)}`,
source.ref === undefined ? null : `ref = ${JSON.stringify(source.ref)}`,
"",
].filter((line) => line !== null).join("\n");
const section = findTomlSection(config, header);
if (section) return config.slice(0, section.start) + block + config.slice(section.end);
return appendBlock(config, block);
}
function ensurePluginEnabled(config, pluginKey) {
const header = `plugins.${JSON.stringify(pluginKey)}`;
const section = findTomlSection(config, header);
if (!section) return appendBlock(config, `[${header}]\nenabled = true\n`);
return replaceOrInsertSetting(config, section, "enabled", "true");
}
function ensureHookTrusted(config, key, trustedHash) {
const header = `hooks.state.${JSON.stringify(key)}`;
const section = findTomlSection(config, header);
if (!section) return appendBlock(config, `[${header}]\ntrusted_hash = ${JSON.stringify(trustedHash)}\n`);
return replaceOrInsertSetting(config, section, "trusted_hash", JSON.stringify(trustedHash));
}
function ensureAgentConfig(config, agentConfig) {
const header = `agents.${tomlKeySegment(agentConfig.name)}`;
const section = findTomlSection(config, header);
const configFile = JSON.stringify(agentConfig.configFile);
if (!section) return appendBlock(config, `[${header}]\nconfig_file = ${configFile}\n`);
return replaceOrInsertSetting(config, section, "config_file", configFile);
}
function tomlKeySegment(value) {
return /^[A-Za-z0-9_-]+$/.test(value) ? value : JSON.stringify(value);
}
function removeTomlSections(config, shouldRemove) {
return splitTomlSections(config)
.filter((section) => section.header === null || !shouldRemove(section.header))
.map((section) => section.text)
.join("")
.replace(/\n{3,}/g, "\n\n");
}
function splitTomlSections(config) {
const lines = config.match(/[^\n]*\n?|$/g) ?? [];
const sections = [];
let current = { header: null, text: "" };
for (const line of lines) {
if (line.length === 0) break;
const header = parseTomlHeader(line);
if (header !== null) {
if (current.text.length > 0) sections.push(current);
current = { header, text: line };
} else {
current.text += line;
}
}
if (current.text.length > 0) sections.push(current);
return sections;
}
function parseTomlHeader(line) {
const trimmed = line.trim();
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
if (trimmed.startsWith("[[")) return null;
return trimmed.slice(1, -1);
}
function parsePluginHeaderKey(header) {
const prefix = "plugins.";
if (!header.startsWith(prefix)) return null;
return parseLeadingJsonString(header.slice(prefix.length));
}
function parseAgentHeaderName(header) {
const prefix = "agents.";
if (!header.startsWith(prefix)) return null;
const key = header.slice(prefix.length);
return key.startsWith('"') ? parseLeadingJsonString(key) : key;
}
function parseLeadingJsonString(value) {
if (!value.startsWith('"')) return parseJsonString(value);
let escaped = false;
for (let index = 1; index < value.length; index += 1) {
const char = value[index];
if (escaped) {
escaped = false;
continue;
}
if (char === "\\") {
escaped = true;
continue;
}
if (char === '"') return parseJsonString(value.slice(0, index + 1));
}
return null;
}
function parseJsonString(value) {
try {
const parsed = JSON.parse(value);
return typeof parsed === "string" ? parsed : null;
} catch {
return null;
}
}
@@ -0,0 +1,84 @@
import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { exists, isRecord } from "./utils.mjs";
const EVENT_LABELS = new Map([
["PreToolUse", "pre_tool_use"],
["PermissionRequest", "permission_request"],
["PostToolUse", "post_tool_use"],
["PreCompact", "pre_compact"],
["PostCompact", "post_compact"],
["SessionStart", "session_start"],
["UserPromptSubmit", "user_prompt_submit"],
["SubagentStart", "subagent_start"],
["SubagentStop", "subagent_stop"],
["Stop", "stop"],
]);
export async function trustedHookStatesForPlugin({ marketplaceName, pluginName, pluginRoot }) {
const manifestPath = join(pluginRoot, ".codex-plugin", "plugin.json");
if (!(await exists(manifestPath))) return [];
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
if (!isRecord(manifest) || typeof manifest.hooks !== "string") return [];
const hooksPath = join(pluginRoot, manifest.hooks);
if (!(await exists(hooksPath))) return [];
const parsed = JSON.parse(await readFile(hooksPath, "utf8"));
if (!isRecord(parsed) || !isRecord(parsed.hooks)) return [];
const keySource = `${pluginName}@${marketplaceName}:${stripDotSlash(manifest.hooks)}`;
const states = [];
for (const [eventName, groups] of Object.entries(parsed.hooks)) {
if (!Array.isArray(groups)) continue;
const eventLabel = EVENT_LABELS.get(eventName);
if (eventLabel === undefined) continue;
for (const [groupIndex, group] of groups.entries()) {
if (!isRecord(group) || !Array.isArray(group.hooks)) continue;
for (const [handlerIndex, handler] of group.hooks.entries()) {
if (!isRecord(handler) || handler.type !== "command") continue;
if (handler.async === true) continue;
if (typeof handler.command !== "string" || handler.command.trim() === "") continue;
const key = `${keySource}:${eventLabel}:${groupIndex}:${handlerIndex}`;
states.push({
key,
trustedHash: commandHookHash(eventLabel, group.matcher, handler),
});
}
}
}
return states;
}
function commandHookHash(eventName, matcher, handler) {
const command = handler.command;
const timeout = Math.max(Number(handler.timeout ?? 600), 1);
const normalizedHandler = {
type: "command",
command,
timeout,
async: false,
};
if (typeof handler.statusMessage === "string") normalizedHandler.statusMessage = handler.statusMessage;
const identity = {
event_name: eventName,
hooks: [normalizedHandler],
};
if (typeof matcher === "string") identity.matcher = matcher;
return `sha256:${createHash("sha256").update(JSON.stringify(canonicalJson(identity))).digest("hex")}`;
}
function canonicalJson(value) {
if (Array.isArray(value)) return value.map(canonicalJson);
if (!isRecord(value)) return value;
const result = {};
for (const key of Object.keys(value).sort()) {
result[key] = canonicalJson(value[key]);
}
return result;
}
function stripDotSlash(value) {
return value.startsWith("./") ? value.slice(2) : value;
}
@@ -0,0 +1,57 @@
import { lstat, readFile, readlink, rm } from "node:fs/promises";
import { join } from "node:path";
import { COMMAND_SHIM_MARKER } from "./command-shim.mjs";
const LEGACY_CODEX_COMPONENT_BINS = [
{ name: "codex-comment-checker", component: "comment-checker" },
{ name: "codex-rules", component: "rules" },
{ name: "codex-start-work-continuation", component: "start-work-continuation" },
{ name: "codex-telemetry", component: "telemetry" },
{ name: "codex-ultrawork", component: "ultrawork" },
];
export async function removeLegacyCodexComponentBins(binDir, platform) {
for (const entry of LEGACY_CODEX_COMPONENT_BINS) {
const linkPath = join(binDir, platform === "win32" ? `${entry.name}.cmd` : entry.name);
await removeLegacyCodexComponentBin(linkPath, entry.component, platform);
}
}
async function removeLegacyCodexComponentBin(linkPath, component, platform) {
try {
const stat = await lstat(linkPath);
if (platform !== "win32") {
if (!stat.isSymbolicLink()) return;
const target = await readlink(linkPath);
if (isManagedLegacyComponentTarget(target, component)) await rm(linkPath, { force: true });
return;
}
if (!stat.isFile()) return;
const content = await readFile(linkPath, "utf8");
if (content.includes(COMMAND_SHIM_MARKER)) await rm(linkPath, { force: true });
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return;
throw error;
}
}
function isManagedLegacyComponentTarget(target, component) {
const parts = target.split(/[\\/]+/);
const suffixStart = parts.length - 4;
const suffix = parts.slice(-4);
return (
suffix[0] === "components" &&
suffix[1] === component &&
suffix[2] === "dist" &&
suffix[3] === "cli.js" &&
hasPluginCachePrefix(parts, suffixStart)
);
}
function hasPluginCachePrefix(parts, endExclusive) {
for (let index = 0; index < endExclusive - 1; index += 1) {
if (parts[index] === "plugins" && parts[index + 1] === "cache") return true;
}
return false;
}
@@ -0,0 +1,104 @@
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { isRecord } from "./utils.mjs";
const DEFAULT_MARKETPLACE_PATH = "packages/omo-codex/marketplace.json";
export async function readMarketplace(repoRoot, options = {}) {
const marketplacePath = options.marketplacePath ?? join(repoRoot, DEFAULT_MARKETPLACE_PATH);
const raw = await readFile(marketplacePath, "utf8");
const parsed = JSON.parse(raw);
if (!isRecord(parsed)) throw new Error("marketplace.json must be an object");
if (typeof parsed.name !== "string" || parsed.name.trim() === "") {
throw new Error("marketplace.json name must be a non-empty string");
}
validatePathSegment(parsed.name, "marketplace name");
if (!Array.isArray(parsed.plugins)) throw new Error("marketplace.json plugins must be an array");
return {
name: parsed.name,
plugins: parsed.plugins.map((plugin, index) => normalizeMarketplacePlugin(plugin, index)),
};
}
export function resolvePluginSource(marketplaceRoot, plugin, options = {}) {
const sourcePath = localSourcePath(options.pathOverride ?? plugin.source);
const relativePath = sourcePath.slice(2);
return join(marketplaceRoot, ...relativePath.split(/[\\/]/));
}
export async function readPluginManifest(pluginRoot) {
const raw = await readFile(join(pluginRoot, ".codex-plugin", "plugin.json"), "utf8");
const parsed = JSON.parse(raw);
if (!isRecord(parsed)) throw new Error(`${pluginRoot} plugin.json must be an object`);
if (typeof parsed.name !== "string" || parsed.name.trim() === "") {
throw new Error(`${pluginRoot} plugin.json name must be a non-empty string`);
}
const manifest = { name: parsed.name };
if (parsed.version !== undefined) {
if (typeof parsed.version !== "string" || parsed.version.trim() === "") {
throw new Error(`${pluginRoot} plugin.json version must be a non-empty string`);
}
manifest.version = parsed.version.trim();
}
return manifest;
}
export function validatePathSegment(value, label) {
if (!/^[A-Za-z0-9._+-]+$/.test(value)) {
throw new Error(`${label} contains unsupported characters: ${value}`);
}
if (value === "." || value === "..") {
throw new Error(`${label} must not be a path traversal segment`);
}
}
function normalizeMarketplacePlugin(plugin, index) {
if (!isRecord(plugin)) throw new Error(`marketplace plugin ${index} must be an object`);
if (typeof plugin.name !== "string" || plugin.name.trim() === "") {
throw new Error(`marketplace plugin ${index} name must be a non-empty string`);
}
validatePathSegment(plugin.name, "plugin name");
if (plugin.source === undefined || typeof plugin.source === "string") {
if (typeof plugin.source === "string") validateLocalSourcePath(plugin.source);
return {
name: plugin.name,
source: plugin.source,
};
}
if (isRecord(plugin.source) && plugin.source.source === "local" && typeof plugin.source.path === "string") {
validateLocalSourcePath(plugin.source.path);
return {
name: plugin.name,
source: { source: "local", path: plugin.source.path },
};
}
throw new Error("local plugin source must be a string path or { source: \"local\", path } object");
}
function localSourcePath(source) {
if (typeof source === "string") return validateLocalSourcePath(source);
if (
isRecord(source) &&
source.source === "local" &&
typeof source.path === "string"
) {
return validateLocalSourcePath(source.path);
}
throw new Error("local plugin source must be a string path or { source: \"local\", path } object");
}
function validateLocalSourcePath(path) {
if (!path.startsWith("./")) {
throw new Error("local plugin source path must start with ./");
}
const relative = path.slice(2);
if (relative.length === 0) throw new Error("local plugin source path must not be empty");
for (const part of relative.split(/[\\/]/)) {
if (part === "" || part === "." || part === "..") {
throw new Error("local plugin source path must stay within the marketplace root");
}
}
return path;
}
@@ -0,0 +1,77 @@
import { existsSync } from "node:fs";
import { cp } from "node:fs/promises";
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
export function createCachedMcpRuntimeArgRewriter({ copyDist = cp } = {}) {
const copiedDistRoots = new Map();
return async function rewriteCachedMcpRuntimeArg({ arg, pluginRoot, serverName, sourceRoot }) {
return rewriteCachedMcpRuntimeArgWithCache({ arg, pluginRoot, serverName, sourceRoot }, { copiedDistRoots, copyDist });
};
}
export async function rewriteCachedMcpRuntimeArg(args) {
return createCachedMcpRuntimeArgRewriter()(args);
}
async function rewriteCachedMcpRuntimeArgWithCache({ arg, pluginRoot, serverName, sourceRoot }, { copiedDistRoots, copyDist }) {
if (typeof arg !== "string" || (!arg.startsWith("./") && !arg.startsWith("../"))) return arg;
const fallback = resolveCachedRuntimePath(pluginRoot, sourceRoot, arg);
const targetPath = resolve(pluginRoot, arg);
const runtimePath = isPathInside(targetPath, pluginRoot) ? targetPath : resolve(sourceRoot, arg);
const packageRoot = resolveExternalMcpPackageRoot(runtimePath, sourceRoot);
if (packageRoot === undefined) return fallback;
const distRoot = join(packageRoot, "dist");
const distPath = relative(distRoot, runtimePath);
if (distPath.startsWith("..") || isAbsolute(distPath)) return fallback;
const cachedRoot = join(pluginRoot, "mcp", safePathSegment(serverName));
const cacheKey = `${distRoot}\0${cachedRoot}`;
let copyPromise = copiedDistRoots.get(cacheKey);
if (copyPromise === undefined) {
copyPromise = copyDist(distRoot, join(cachedRoot, "dist"), { recursive: true });
copiedDistRoots.set(cacheKey, copyPromise);
}
await copyPromise;
return join(cachedRoot, "dist", distPath);
}
function resolveExternalMcpPackageRoot(runtimePath, sourceRoot) {
const packagesRoot = findPackagesRoot(sourceRoot);
if (packagesRoot === undefined) return undefined;
if (!isPathInside(runtimePath, packagesRoot)) return undefined;
let packageRoot = dirname(runtimePath);
while (packageRoot !== packagesRoot) {
if (existsSync(join(packageRoot, "package.json")) && isPathInside(runtimePath, join(packageRoot, "dist"))) {
return packageRoot;
}
const parent = dirname(packageRoot);
if (parent === packageRoot) return undefined;
packageRoot = parent;
}
return undefined;
}
function findPackagesRoot(path) {
let current = resolve(path);
for (let index = 0; index < 8; index++) {
if (basename(current) === "packages") return current;
const parent = dirname(current);
if (parent === current) return undefined;
current = parent;
}
return undefined;
}
function resolveCachedRuntimePath(pluginRoot, sourceRoot, runtimePath) {
const targetPath = resolve(pluginRoot, runtimePath);
if (isPathInside(targetPath, pluginRoot)) return targetPath;
return resolve(sourceRoot, runtimePath);
}
function isPathInside(candidatePath, rootPath) {
const pathFromRoot = relative(rootPath, candidatePath);
return pathFromRoot === "" || (!pathFromRoot.startsWith("..") && !isAbsolute(pathFromRoot));
}
function safePathSegment(value) {
return value.replace(/[^A-Za-z0-9._-]/g, "_");
}
@@ -0,0 +1,32 @@
import { appendBlock, findTomlSection, removeSetting, replaceOrInsertSetting } from "./toml-editor.mjs";
const CODEX_MULTI_AGENT_V2_HEADER = "features.multi_agent_v2";
const CODEX_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION = 10000;
export function ensureCodexMultiAgentV2Config(config) {
const normalizedConfig = removeFeatureFlagSetting(config, "multi_agent_v2");
const section = findTomlSection(normalizedConfig, CODEX_MULTI_AGENT_V2_HEADER);
const maxThreadsValue = CODEX_MULTI_AGENT_V2_MAX_CONCURRENT_THREADS_PER_SESSION.toString();
if (!section) {
return appendBlock(
normalizedConfig,
`[${CODEX_MULTI_AGENT_V2_HEADER}]\nenabled = true\nmax_concurrent_threads_per_session = ${maxThreadsValue}\n`,
);
}
const enabledConfig = replaceOrInsertSetting(normalizedConfig, section, "enabled", "true");
const updatedSection = findTomlSection(enabledConfig, CODEX_MULTI_AGENT_V2_HEADER);
if (!updatedSection) {
return appendBlock(
enabledConfig,
`[${CODEX_MULTI_AGENT_V2_HEADER}]\nenabled = true\nmax_concurrent_threads_per_session = ${maxThreadsValue}\n`,
);
}
return replaceOrInsertSetting(enabledConfig, updatedSection, "max_concurrent_threads_per_session", maxThreadsValue);
}
function removeFeatureFlagSetting(config, featureName) {
const section = findTomlSection(config, "features");
if (!section) return config;
return removeSetting(config, section, featureName);
}
@@ -0,0 +1,19 @@
import { spawn } from "node:child_process";
export async function defaultRunCommand(command, args, options) {
await new Promise((resolvePromise, reject) => {
const child = spawn(command, args, {
cwd: options.cwd,
stdio: "inherit",
});
child.once("error", reject);
child.once("exit", (code, signal) => {
if (code === 0) {
resolvePromise();
return;
}
const suffix = signal ? `signal ${signal}` : `exit code ${code}`;
reject(new Error(`${command} ${args.join(" ")} failed in ${options.cwd} with ${suffix}`));
});
});
}
@@ -0,0 +1,54 @@
import { cp, mkdir, rename, rm, writeFile } from "node:fs/promises";
import { join, sep } from "node:path";
import { rewriteCachedMcpManifest } from "./cache.mjs";
const INSTALLED_MARKETPLACES_DIR = ".tmp/marketplaces";
export async function writeInstalledMarketplaceSnapshot({ codexHome, marketplace, plugins }) {
const marketplaceRoot = installedMarketplaceRoot(codexHome, marketplace.name);
await mkdir(marketplaceRoot, { recursive: true });
await writeMarketplaceManifest(marketplaceRoot, marketplace);
const snapshotPlugins = [];
for (const plugin of plugins) {
snapshotPlugins.push(await writeSnapshotPlugin(marketplaceRoot, plugin));
}
return snapshotPlugins;
}
export function installedMarketplaceRoot(codexHome, marketplaceName) {
return join(codexHome, INSTALLED_MARKETPLACES_DIR, marketplaceName);
}
async function writeMarketplaceManifest(marketplaceRoot, marketplace) {
const manifestDir = join(marketplaceRoot, ".agents", "plugins");
await mkdir(manifestDir, { recursive: true });
const tempPath = join(manifestDir, `.marketplace-${process.pid}-${Date.now()}.json.tmp`);
await writeFile(tempPath, `${JSON.stringify(marketplace, null, "\t")}\n`);
await rename(tempPath, join(manifestDir, "marketplace.json"));
}
async function writeSnapshotPlugin(marketplaceRoot, plugin) {
const pluginsDir = join(marketplaceRoot, "plugins");
await mkdir(pluginsDir, { recursive: true });
const targetPath = join(pluginsDir, plugin.name);
const tempPath = join(pluginsDir, `.tmp-${plugin.name}-${process.pid}-${Date.now()}`);
await rm(tempPath, { recursive: true, force: true });
await cp(plugin.sourcePath, tempPath, {
recursive: true,
filter: (source) => shouldCopyMarketplaceSourcePath(source, plugin.sourcePath),
});
await rm(targetPath, { recursive: true, force: true });
await rename(tempPath, targetPath);
await rewriteCachedMcpManifest(targetPath, plugin.sourcePath);
return { name: plugin.name, path: targetPath };
}
function shouldCopyMarketplaceSourcePath(path, root) {
const relative = path === root ? "" : path.slice(root.length + sep.length);
if (relative === "") return true;
const parts = relative.split(sep);
if (parts[parts.length - 1] === "package-lock.json") return false;
return !parts.some((part) => part === ".git" || part === "node_modules");
}
@@ -0,0 +1,47 @@
export function findTomlSection(config, header) {
const headerLine = `[${header}]`;
const lines = config.match(/[^\n]*\n?|$/g) ?? [];
let offset = 0;
let start = -1;
for (const line of lines) {
if (line.length === 0) break;
const trimmed = line.trim();
if (start === -1) {
if (trimmed === headerLine) start = offset;
} else if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
return { start, end: offset, text: config.slice(start, offset) };
}
offset += line.length;
}
if (start === -1) return null;
return { start, end: config.length, text: config.slice(start) };
}
export function replaceOrInsertSetting(config, section, key, value) {
const linePattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*$`, "m");
const replacement = linePattern.test(section.text)
? section.text.replace(linePattern, `${key} = ${value}`)
: insertSetting(section.text, key, value);
return config.slice(0, section.start) + replacement + config.slice(section.end);
}
export function removeSetting(config, section, key) {
const linePattern = new RegExp(`^${escapeRegExp(key)}\\s*=.*(?:\\n|$)`, "m");
const replacement = section.text.replace(linePattern, "");
return config.slice(0, section.start) + replacement + config.slice(section.end);
}
export function appendBlock(config, block) {
const prefix = config.trimEnd();
return `${prefix}${prefix.length > 0 ? "\n\n" : ""}${block.trimEnd()}\n`;
}
function insertSetting(sectionText, key, value) {
const lines = sectionText.split("\n");
lines.splice(1, 0, `${key} = ${value}`);
return lines.join("\n");
}
function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
@@ -0,0 +1,15 @@
import { constants as fsConstants } from "node:fs";
import { access } from "node:fs/promises";
export async function exists(path) {
try {
await access(path, fsConstants.F_OK);
return true;
} catch {
return false;
}
}
export function isRecord(value) {
return typeof value === "object" && value !== null && !Array.isArray(value);
}