fix(omo-codex): reuse shared lsp mcp

This commit is contained in:
YeonGyu-Kim
2026-05-28 15:30:24 +09:00
parent 4f75a56ce5
commit 615ed40ba3
79 changed files with 378 additions and 5510 deletions
@@ -28,6 +28,17 @@ export async function pruneMarketplaceCache({ codexHome, marketplaceName, keepPl
}
}
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 });
+35 -3
View File
@@ -8,6 +8,7 @@ const LAZYCODEX_MARKETPLACE_SOURCE = {
source: "https://github.com/code-yeongyu/lazycodex.git",
ref: "main",
};
const SISYPHUS_LEGACY_MARKETPLACES = ["lazycodex", "code-yeongyu-codex-plugins"];
export async function updateCodexConfig({
configPath,
@@ -21,6 +22,11 @@ export async function updateCodexConfig({
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 = ensureFeatureEnabled(config, "plugins");
@@ -36,6 +42,14 @@ export async function updateCodexConfig({
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) {
if (marketplaceName === "sisyphuslabs") return LAZYCODEX_MARKETPLACE_SOURCE;
return {
@@ -46,7 +60,7 @@ function defaultMarketplaceSource(marketplaceName, repoRoot) {
function removeStaleMarketplacePluginBlocks(config, marketplaceName, keepPluginNames) {
return removeTomlSections(config, (header) => {
const pluginKey = parseQuotedPluginHeader(header);
const pluginKey = parsePluginHeaderKey(header);
if (pluginKey === null) return false;
const suffix = `@${marketplaceName}`;
if (!pluginKey.endsWith(suffix)) return false;
@@ -170,10 +184,28 @@ function parseTomlHeader(line) {
return trimmed.slice(1, -1);
}
function parseQuotedPluginHeader(header) {
function parsePluginHeaderKey(header) {
const prefix = "plugins.";
if (!header.startsWith(prefix)) return null;
return parseJsonString(header.slice(prefix.length));
return parseLeadingJsonString(header.slice(prefix.length));
}
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) {
@@ -3,10 +3,10 @@ import { join } from "node:path";
import { isRecord } from "./utils.mjs";
const MARKETPLACE_PATH = ".agents/plugins/marketplace.json";
const DEFAULT_MARKETPLACE_PATH = "packages/omo-codex/marketplace.json";
export async function readMarketplace(repoRoot) {
const marketplacePath = join(repoRoot, MARKETPLACE_PATH);
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");
@@ -22,10 +22,10 @@ export async function readMarketplace(repoRoot) {
};
}
export function resolvePluginSource(repoRoot, plugin) {
const sourcePath = localSourcePath(plugin.source);
export function resolvePluginSource(marketplaceRoot, plugin, options = {}) {
const sourcePath = localSourcePath(options.pathOverride ?? plugin.source);
const relativePath = sourcePath.slice(2);
return join(repoRoot, ...relativePath.split(/[\\/]/));
return join(marketplaceRoot, ...relativePath.split(/[\\/]/));
}
export async function readPluginManifest(pluginRoot) {
@@ -60,10 +60,21 @@ function normalizeMarketplacePlugin(plugin, index) {
throw new Error(`marketplace plugin ${index} name must be a non-empty string`);
}
validatePathSegment(plugin.name, "plugin name");
return {
name: plugin.name,
source: plugin.source,
};
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) {