Files
openclaw/extensions/codex/src/migration/source-files.ts
T
stevenlee-oai a00438a867 fix(codex): restore connected apps for token-authenticated runs (#115075)
* fix(codex): restore connected apps for token-authenticated runs

* fix(codex): keep app inventory protocol types private

* fix(codex): align native runtime with Codex 0.146.0

Co-authored-by: Steven Lee <stevenlee@openai.com>

* fix(codex): clean up latest app-server integration

Co-authored-by: Steven Lee <stevenlee@openai.com>

* fix(codex): keep internal protocol types private

* fix(ci): repair current main Codex landing gates

* fix(ci): format inherited code mode matrix

* fix(codex): reconcile native app-server contracts with main

Prepare a verified GitHub-hosted mainline merge while preserving the reviewed Codex 0.146.0 fixes and canonical OpenAI authentication.

Co-authored-by: Steven Lee <stevenlee@openai.com>

* fix(codex): keep QA evidence in its owning plugin

Resolve the current-main Code Mode test rename without resurrecting the retired core test path.

Co-authored-by: Steven Lee <stevenlee@openai.com>

* fix(codex): enforce canonical OpenAI app-server auth

Reject retired provider aliases without runtime compatibility, direct operators to the doctor migration, and remove the redundant OpenAI API-key predicate.

Co-authored-by: Steven Lee <stevenlee@openai.com>

* chore(codex): reconcile latest main dependency graph

Preserve current main dependency changes while preparing the original Codex PR for an ancestry-preserving signed mainline merge.

Co-authored-by: Steven Lee <stevenlee@openai.com>

* fix(codex): unify bundled Codex 0.146 runtimes

Keep the ACP adapter on the same 0.146.0 Codex release as the managed runtime, remove obsolete 0.145.0 platform artifacts and unused semver compatibility, and preserve the latest main dependency upgrades.

Co-authored-by: Steven Lee <stevenlee@openai.com>

---------

Co-authored-by: Peter Steinberger <peter@steipete.me>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-29 06:21:25 -04:00

176 lines
5.1 KiB
TypeScript

// Codex migration source file discovery stays filesystem-only and bounded.
import type { Dirent } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import { CODEX_PLUGINS_MARKETPLACE_NAME } from "../app-server/config.js";
import { exists, isDirectory, readJsonObject } from "./helpers.js";
const SKILL_FILENAME = "SKILL.md";
const MAX_SCAN_DEPTH = 6;
const MAX_DISCOVERED_DIRS = 2000;
export type CodexSkillSource = {
name: string;
source: string;
sourceLabel: string;
};
export type CodexPluginMigrationBlockCode =
| "plugin_disabled"
| "codex_subscription_required"
| "codex_account_unavailable"
| "plugin_read_unavailable"
| "app_inventory_unavailable"
| "app_inaccessible"
| "app_disabled"
| "app_missing";
export type CodexPluginMigrationAppFact = {
id: string;
name: string;
isAccessible?: boolean;
isEnabled?: boolean;
};
type CodexPluginMigrationBlock = {
code: CodexPluginMigrationBlockCode;
apps?: CodexPluginMigrationAppFact[];
error?: string;
};
export type CodexPluginSource = {
name: string;
source: string;
migratable: boolean;
marketplaceName?: typeof CODEX_PLUGINS_MARKETPLACE_NAME;
pluginName?: string;
installed?: boolean;
enabled?: boolean;
apps?: CodexPluginMigrationAppFact[];
migrationBlock?: CodexPluginMigrationBlock;
message?: string;
};
export type CodexMemorySource = {
id: string;
label: string;
path: string;
};
async function safeReadDir(dir: string): Promise<Dirent[]> {
return await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
}
export async function discoverSkillDirs(params: {
root: string | undefined;
sourceLabel: string;
excludeSystem?: boolean;
}): Promise<CodexSkillSource[]> {
if (!params.root || !(await isDirectory(params.root))) {
return [];
}
const discovered: CodexSkillSource[] = [];
async function visit(dir: string, depth: number): Promise<void> {
if (discovered.length >= MAX_DISCOVERED_DIRS || depth > MAX_SCAN_DEPTH) {
return;
}
const name = path.basename(dir);
if (params.excludeSystem && depth === 1 && name === ".system") {
return;
}
if (await exists(path.join(dir, SKILL_FILENAME))) {
discovered.push({ name, source: dir, sourceLabel: params.sourceLabel });
return;
}
for (const entry of await safeReadDir(dir)) {
if (entry.isDirectory()) {
await visit(path.join(dir, entry.name), depth + 1);
}
}
}
await visit(params.root, 0);
return discovered;
}
export async function discoverPluginDirs(codexHome: string): Promise<CodexPluginSource[]> {
const root = path.join(codexHome, "plugins", "cache");
if (!(await isDirectory(root))) {
return [];
}
const discovered = new Map<string, CodexPluginSource>();
async function visit(dir: string, depth: number): Promise<void> {
if (discovered.size >= MAX_DISCOVERED_DIRS || depth > MAX_SCAN_DEPTH) {
return;
}
const manifestPath = path.join(dir, ".codex-plugin", "plugin.json");
if (await exists(manifestPath)) {
const manifest = await readJsonObject(manifestPath);
const manifestName = typeof manifest.name === "string" ? manifest.name.trim() : "";
discovered.set(dir, {
name: manifestName || path.basename(dir),
source: dir,
migratable: false,
message:
"Cached Codex plugin bundle found. Review manually unless the plugin is also installed in the source Codex app-server inventory",
});
return;
}
for (const entry of await safeReadDir(dir)) {
if (entry.isDirectory()) {
await visit(path.join(dir, entry.name), depth + 1);
}
}
}
await visit(root, 0);
return [...discovered.values()].toSorted((a, b) => a.source.localeCompare(b.source));
}
async function discoverCodexMemoryFile(
candidate: CodexMemorySource,
): Promise<CodexMemorySource | undefined> {
try {
const stat = await fs.lstat(candidate.path);
if (stat.isSymbolicLink()) {
throw new Error(`Codex memory source must not be a symbolic link: ${candidate.path}`);
}
if (!stat.isFile()) {
throw new Error(`Codex memory source must be a regular file: ${candidate.path}`);
}
return candidate;
} catch (error) {
if (
error &&
typeof error === "object" &&
"code" in error &&
(error as { code?: unknown }).code === "ENOENT"
) {
return undefined;
}
throw error;
}
}
export async function discoverCodexMemorySources(codexHome: string): Promise<CodexMemorySource[]> {
const memoriesDir = path.join(codexHome, "memories");
const memoryFiles = (
await Promise.all(
[
{ id: "memory:codex:MEMORY.md", label: "Codex consolidated memory", name: "MEMORY.md" },
{
id: "memory:codex:memory_summary.md",
label: "Codex memory summary",
name: "memory_summary.md",
},
].map(
async (candidate) =>
await discoverCodexMemoryFile({
id: candidate.id,
label: candidate.label,
path: path.join(memoriesDir, candidate.name),
}),
),
)
).filter((entry): entry is CodexMemorySource => entry !== undefined);
return memoryFiles;
}