



























@@ -39,6 +39,10 @@ import {
3939import { buildCodexPluginAppCacheKey } from "../app-server/plugin-app-cache-key.js";
4040import type { v2 } from "../app-server/protocol.js";
4141import { requestCodexAppServerJson } from "../app-server/request.js";
42+import {
43+clearSharedCodexAppServerClientAndWait,
44+getSharedCodexAppServerClient,
45+} from "../app-server/shared-client.js";
4246import { buildCodexMigrationPlan } from "./plan.js";
4347import {
4448buildCodexPluginsConfigValue,
@@ -53,6 +57,16 @@ import { resolveCodexMigrationTargets } from "./targets.js";
5357const CODEX_PLUGIN_AUTH_REQUIRED_REASON = "auth_required";
5458const CODEX_PLUGIN_NOT_SELECTED_REASON = "not selected for migration";
5559const CODEX_CONFIG_PATCH_MODE_RETURN = "return";
60+const CODEX_PLUGIN_LOAD_WARNING =
61+"Some Codex plugins could not be migrated. Run `openclaw migrate codex` after onboarding.";
62+const TARGET_CODEX_MARKETPLACE_DISCOVERY_POLL_MS = 250;
63+const TARGET_CODEX_MARKETPLACE_DISCOVERY_TIMEOUT_MS = 30_000;
64+const TARGET_CODEX_MARKETPLACE_DISCOVERY_TIMEOUT_ENV =
65+"OPENCLAW_CODEX_MIGRATION_PLUGIN_LIST_TIMEOUT_MS";
66+67+export type CodexMigrationTargetAppServerPreparation = {
68+dispose: () => Promise<void>;
69+};
56705771class CodexPluginConfigConflictError extends Error {
5872constructor(readonly reason: string) {
@@ -65,6 +79,31 @@ function shouldReturnCodexPluginConfigPatch(ctx: MigrationProviderContext): bool
6579return ctx.providerOptions?.configPatchMode === CODEX_CONFIG_PATCH_MODE_RETURN;
6680}
678182+export function prepareTargetCodexAppServer(
83+ctx: MigrationProviderContext,
84+): CodexMigrationTargetAppServerPreparation {
85+const appServer = resolveTargetCodexAppServer(ctx);
86+const targets = resolveCodexMigrationTargets(ctx);
87+const ready = getSharedCodexAppServerClient({
88+startOptions: appServer.start,
89+timeoutMs: 60_000,
90+agentDir: targets.agentDir,
91+config: ctx.config,
92+}).then(
93+() => undefined,
94+() => undefined,
95+);
96+return {
97+async dispose() {
98+await ready;
99+await clearSharedCodexAppServerClientAndWait({
100+exitTimeoutMs: 2_000,
101+forceKillDelayMs: 250,
102+});
103+},
104+};
105+}
106+68107export async function applyCodexMigrationPlan(params: {
69108ctx: MigrationProviderContext;
70109plan?: MigrationPlan;
@@ -102,6 +141,10 @@ export async function applyCodexMigrationPlan(params: {
102141backupPath: params.ctx.backupPath,
103142 reportDir,
104143};
144+if (items.some(isCodexPluginLoadWarningItem)) {
145+result.warnings = [...new Set([...(result.warnings ?? []), CODEX_PLUGIN_LOAD_WARNING])];
146+result.nextSteps = [...new Set([CODEX_PLUGIN_LOAD_WARNING, ...(result.nextSteps ?? [])])];
147+}
105148await writeMigrationReport(result, { title: "Codex Migration Report" });
106149return result;
107150}
@@ -124,14 +167,14 @@ async function applyCodexPluginInstallItem(
124167identity: policy,
125168installEvenIfActive: true,
126169request: async (method, requestParams) =>
127-await requestCodexAppServerJson({
170+await requestTargetCodexAppServerJson({
128171 method,
129172 requestParams,
130173timeoutMs: 60_000,
131174startOptions: appServer.start,
132175agentDir: resolveCodexMigrationTargets(ctx).agentDir,
133176config: ctx.config,
134-isolated: true,
177+isolated: false,
135178}),
136179appCache: defaultCodexAppInventoryCache,
137180 appCacheKey,
@@ -163,17 +206,43 @@ async function applyCodexPluginInstallItem(
163206},
164207};
165208}
209+if (result.reason === "plugin_missing" || result.reason === "marketplace_missing") {
210+return {
211+ ...item,
212+status: "warning",
213+reason: result.reason,
214+message: `Codex plugin "${policy.pluginName}" could not be migrated automatically`,
215+details: {
216+ ...baseDetails,
217+warningReason: CODEX_PLUGIN_LOAD_WARNING,
218+},
219+};
220+}
166221return {
167222 ...item,
168223status: "error",
169224reason: result.reason,
170225details: baseDetails,
171226};
172227} catch (error) {
228+if (isCodexPluginInventoryLoadError(error)) {
229+return {
230+ ...item,
231+status: "warning",
232+reason: "plugin_inventory_unavailable",
233+message: `Codex plugin "${policy.pluginName}" could not be migrated automatically`,
234+details: {
235+ ...item.details,
236+code: "plugin_inventory_unavailable",
237+warningReason: CODEX_PLUGIN_LOAD_WARNING,
238+diagnostic: formatCodexMigrationError(error),
239+},
240+};
241+}
173242return {
174243 ...item,
175244status: "error",
176-reason: error instanceof Error ? error.message : String(error),
245+reason: formatCodexMigrationError(error),
177246details: {
178247 ...item.details,
179248code: "plugin_install_failed",
@@ -182,12 +251,99 @@ async function applyCodexPluginInstallItem(
182251}
183252}
184253254+function isCodexPluginInventoryLoadError(error: unknown): boolean {
255+const message = formatCodexMigrationError(error);
256+return message.includes("codex app-server plugin/list timed out");
257+}
258+259+function formatCodexMigrationError(error: unknown): string {
260+return error instanceof Error ? error.message : String(error);
261+}
262+185263function resolveTargetCodexAppServer(ctx: MigrationProviderContext) {
186264return resolveCodexAppServerRuntimeOptions({
187265pluginConfig: readCodexPluginConfig(ctx.config),
188266});
189267}
190268269+async function requestTargetCodexAppServerJson(params: {
270+method: string;
271+requestParams?: unknown;
272+timeoutMs: number;
273+startOptions: ReturnType<typeof resolveTargetCodexAppServer>["start"];
274+agentDir: string;
275+config: MigrationProviderContext["config"];
276+isolated?: boolean;
277+}): Promise<unknown> {
278+if (params.method !== "plugin/list") {
279+return await requestCodexAppServerJson(params);
280+}
281+282+const deadline = Date.now() + params.timeoutMs;
283+const discoveryTimeoutMs = targetCodexMarketplaceDiscoveryTimeoutMs();
284+const discoveryDeadline = Math.min(deadline, Date.now() + discoveryTimeoutMs);
285+let lastResponse: unknown;
286+let attempt = 0;
287+do {
288+attempt += 1;
289+const remainingMs = Math.max(1, discoveryDeadline - Date.now());
290+lastResponse = await requestCodexAppServerJson({
291+ ...params,
292+timeoutMs: remainingMs,
293+});
294+if (hasOpenAiCuratedMarketplace(lastResponse)) {
295+return lastResponse;
296+}
297+if (Date.now() >= discoveryDeadline) {
298+return lastResponse;
299+}
300+const waitMs = Math.min(
301+TARGET_CODEX_MARKETPLACE_DISCOVERY_POLL_MS,
302+discoveryDeadline - Date.now(),
303+);
304+await sleep(waitMs);
305+} while (Date.now() < discoveryDeadline);
306+307+return lastResponse;
308+}
309+310+function hasOpenAiCuratedMarketplace(response: unknown): boolean {
311+if (!response || typeof response !== "object" || !("marketplaces" in response)) {
312+return false;
313+}
314+const marketplaces = (response as { marketplaces?: unknown }).marketplaces;
315+return (
316+Array.isArray(marketplaces) &&
317+marketplaces.some(
318+(marketplace) =>
319+marketplace &&
320+typeof marketplace === "object" &&
321+(marketplace as { name?: unknown }).name === CODEX_PLUGINS_MARKETPLACE_NAME,
322+)
323+);
324+}
325+326+function targetCodexMarketplaceDiscoveryTimeoutMs(): number {
327+const configured = Number(process.env[TARGET_CODEX_MARKETPLACE_DISCOVERY_TIMEOUT_ENV]);
328+if (Number.isFinite(configured) && configured >= 0) {
329+return configured;
330+}
331+return TARGET_CODEX_MARKETPLACE_DISCOVERY_TIMEOUT_MS;
332+}
333+334+function isCodexPluginLoadWarningItem(item: MigrationItem): boolean {
335+return (
336+item.kind === "plugin" &&
337+item.action === "install" &&
338+item.status === "warning" &&
339+item.details?.warningReason === CODEX_PLUGIN_LOAD_WARNING
340+);
341+}
342+343+async function sleep(ms: number): Promise<void> {
344+await new Promise((resolve) => setTimeout(resolve, ms));
345+}
346+191347async function buildTargetCodexPluginAppCacheKey(ctx: MigrationProviderContext): Promise<string> {
192348const targets = resolveCodexMigrationTargets(ctx);
193349const appServer = resolveTargetCodexAppServer(ctx);
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。