mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-24 04:19:48 +00:00
Add a post-install seam so the wizard can prompt the user to import their existing Codex CLI state (skills, archived config/hooks, advisory cached plugins) through the existing `openclaw migrate codex` flow once the harness plugin is in place. Fires on both fresh installs and repair runs; the user can decline at any time. Trigger sites, both routing through one helper: - src/plugins/provider-auth-choice.ts: after `ensureCodexRuntimePluginForModelSelection` reports `installed: true`, dynamically import `offerPostInstallMigrations` and call it before the wizard moves on. - src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.ts: same call shape with `nonInteractive: true`, so the helper emits a hint line only and never mutates state. Helper (src/wizard/setup.post-install-migration.ts) is generic, not Codex-hardcoded — it resolves migration providers via the manifest `migrationProviders` contract, filters to providers owned by plugins the caller flags as installed in this onboarding step, runs `provider.detect`, and on TTY hands accepted runs to `migrateDefaultCommand`. All detect, prompt, and migrate failures are swallowed so onboarding never aborts on this optional offer. Also harden the Codex app-server subprocess lifecycle now that `detect()` runs from a hotter onboarding path: isolate the plugin-install `plugin/read` call (extensions/codex/src/migration/apply.ts) and have the isolated request wait for child exit with a SIGKILL fallback (extensions/codex/src/app-server/request.ts) so parents are not held open by an orphaned codex binary. Tests: - src/wizard/setup.post-install-migration.test.ts (new, 10 cases) - src/commands/onboard-non-interactive/local/auth-choice.plugin-providers.test.ts extended with hint-call assertions and a not-required-no-offer case.
127 lines
4.2 KiB
TypeScript
127 lines
4.2 KiB
TypeScript
import { existsSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { modelSelectionShouldEnsureCodexPlugin } from "../agents/openai-codex-routing.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import type { PluginInstallRecord } from "../config/types.plugins.js";
|
|
import { enablePluginInConfig } from "../plugins/enable.js";
|
|
import { loadInstalledPluginIndexInstallRecords } from "../plugins/installed-plugin-index-records.js";
|
|
import type { RuntimeEnv } from "../runtime.js";
|
|
import { resolveUserPath } from "../utils.js";
|
|
import type { WizardPrompter } from "../wizard/prompts.js";
|
|
|
|
export const CODEX_RUNTIME_PLUGIN_ID = "codex";
|
|
const CODEX_RUNTIME_PLUGIN_LABEL = "Codex";
|
|
const CODEX_RUNTIME_PLUGIN_NPM_SPEC = "@openclaw/codex";
|
|
|
|
function isInstalledRecordPresentOnDisk(
|
|
record: PluginInstallRecord | undefined,
|
|
env: NodeJS.ProcessEnv,
|
|
): boolean {
|
|
const installPath = record?.installPath?.trim();
|
|
if (!installPath) {
|
|
return false;
|
|
}
|
|
return existsSync(path.join(resolveUserPath(installPath, env), "package.json"));
|
|
}
|
|
|
|
export type CodexRuntimePluginInstallResult = {
|
|
cfg: OpenClawConfig;
|
|
required: boolean;
|
|
installed: boolean;
|
|
status?: "installed" | "skipped" | "failed" | "timed_out";
|
|
};
|
|
|
|
export function selectedModelShouldEnsureCodexRuntimePlugin(params: {
|
|
cfg: OpenClawConfig;
|
|
model?: string;
|
|
}): boolean {
|
|
return modelSelectionShouldEnsureCodexPlugin({
|
|
config: params.cfg,
|
|
model: params.model,
|
|
});
|
|
}
|
|
|
|
export async function ensureCodexRuntimePluginForModelSelection(params: {
|
|
cfg: OpenClawConfig;
|
|
model?: string;
|
|
prompter: WizardPrompter;
|
|
runtime: RuntimeEnv;
|
|
workspaceDir?: string;
|
|
}): Promise<CodexRuntimePluginInstallResult> {
|
|
if (!selectedModelShouldEnsureCodexRuntimePlugin({ cfg: params.cfg, model: params.model })) {
|
|
return {
|
|
cfg: params.cfg,
|
|
required: false,
|
|
installed: false,
|
|
};
|
|
}
|
|
const existingRecords = await loadInstalledPluginIndexInstallRecords({ env: process.env });
|
|
if (isInstalledRecordPresentOnDisk(existingRecords[CODEX_RUNTIME_PLUGIN_ID], process.env)) {
|
|
const repair = await repairCodexRuntimePluginInstallForModelSelection({
|
|
cfg: params.cfg,
|
|
model: params.model,
|
|
env: process.env,
|
|
});
|
|
for (const change of repair.changes) {
|
|
params.runtime.log?.(change);
|
|
}
|
|
for (const warning of repair.warnings) {
|
|
params.runtime.log?.(`Codex update warning: ${warning}`);
|
|
}
|
|
const enableResult = enablePluginInConfig(params.cfg, CODEX_RUNTIME_PLUGIN_ID);
|
|
return {
|
|
cfg: enableResult.enabled ? enableResult.config : params.cfg,
|
|
required: true,
|
|
installed: true,
|
|
status: "installed",
|
|
};
|
|
}
|
|
const { ensureOnboardingPluginInstalled } = await import("./onboarding-plugin-install.js");
|
|
const result = await ensureOnboardingPluginInstalled({
|
|
cfg: params.cfg,
|
|
entry: {
|
|
pluginId: CODEX_RUNTIME_PLUGIN_ID,
|
|
label: CODEX_RUNTIME_PLUGIN_LABEL,
|
|
install: {
|
|
npmSpec: CODEX_RUNTIME_PLUGIN_NPM_SPEC,
|
|
defaultChoice: "npm",
|
|
},
|
|
trustedSourceLinkedOfficialInstall: true,
|
|
preferRemoteInstall: true,
|
|
},
|
|
prompter: params.prompter,
|
|
runtime: params.runtime,
|
|
...(params.workspaceDir !== undefined ? { workspaceDir: params.workspaceDir } : {}),
|
|
promptInstall: false,
|
|
autoConfirmSingleSource: true,
|
|
});
|
|
return {
|
|
cfg: result.cfg,
|
|
required: true,
|
|
installed: result.installed,
|
|
status: result.status,
|
|
};
|
|
}
|
|
|
|
export async function repairCodexRuntimePluginInstallForModelSelection(params: {
|
|
cfg: OpenClawConfig;
|
|
model?: string;
|
|
env?: NodeJS.ProcessEnv;
|
|
}): Promise<{ required: boolean; changes: string[]; warnings: string[] }> {
|
|
if (!selectedModelShouldEnsureCodexRuntimePlugin({ cfg: params.cfg, model: params.model })) {
|
|
return { required: false, changes: [], warnings: [] };
|
|
}
|
|
const { repairMissingPluginInstallsForIds } =
|
|
await import("./doctor/shared/missing-configured-plugin-install.js");
|
|
const result = await repairMissingPluginInstallsForIds({
|
|
cfg: params.cfg,
|
|
pluginIds: [CODEX_RUNTIME_PLUGIN_ID],
|
|
...(params.env !== undefined ? { env: params.env } : {}),
|
|
});
|
|
return {
|
|
required: true,
|
|
changes: result.changes,
|
|
warnings: result.warnings,
|
|
};
|
|
}
|