mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:50:43 +00:00
fix: install codex runtime plugin during doctor
This commit is contained in:
@@ -381,6 +381,60 @@ describe("repairMissingConfiguredPluginInstalls", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("installs the missing configured Codex runtime plugin from the beta npm tag", async () => {
|
||||
mocks.installPluginFromNpmSpec.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
pluginId: "codex",
|
||||
targetDir: "/tmp/openclaw-plugins/codex",
|
||||
version: "2026.5.2-beta.1",
|
||||
npmResolution: {
|
||||
name: "@openclaw/codex",
|
||||
version: "2026.5.2-beta.1",
|
||||
resolvedSpec: "@openclaw/codex@2026.5.2-beta.1",
|
||||
integrity: "sha512-codex-beta",
|
||||
resolvedAt: "2026-05-01T00:00:00.000Z",
|
||||
},
|
||||
});
|
||||
|
||||
const { repairMissingPluginInstallsForIds } =
|
||||
await import("./missing-configured-plugin-install.js");
|
||||
const result = await repairMissingPluginInstallsForIds({
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.4",
|
||||
agentRuntime: { id: "codex" },
|
||||
},
|
||||
},
|
||||
},
|
||||
pluginIds: ["codex"],
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(mocks.resolveProviderInstallCatalogEntries).toHaveBeenCalled();
|
||||
expect(mocks.installPluginFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@openclaw/codex@beta",
|
||||
expectedPluginId: "codex",
|
||||
}),
|
||||
);
|
||||
expect(mocks.writePersistedInstalledPluginIndexInstallRecords).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
codex: expect.objectContaining({
|
||||
source: "npm",
|
||||
spec: "@openclaw/codex@beta",
|
||||
installPath: "/tmp/openclaw-plugins/codex",
|
||||
version: "2026.5.2-beta.1",
|
||||
}),
|
||||
}),
|
||||
{ env: {} },
|
||||
);
|
||||
expect(result.changes).toEqual([
|
||||
'Installed missing configured plugin "codex" from @openclaw/codex@beta.',
|
||||
]);
|
||||
expect(result.warnings).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not install a blocked downloadable plugin from explicit channel ids", async () => {
|
||||
mocks.listChannelPluginCatalogEntries.mockReturnValue([
|
||||
{
|
||||
|
||||
@@ -30,6 +30,15 @@ type DownloadableInstallCandidate = {
|
||||
defaultChoice?: PluginPackageInstall["defaultChoice"];
|
||||
};
|
||||
|
||||
const RUNTIME_PLUGIN_INSTALL_CANDIDATES: readonly DownloadableInstallCandidate[] = [
|
||||
// Runtime-only configs do not have a provider/channel integration catalog entry.
|
||||
{
|
||||
pluginId: "codex",
|
||||
label: "Codex",
|
||||
npmSpec: "@openclaw/codex@beta",
|
||||
},
|
||||
];
|
||||
|
||||
function buildOpenClawClawHubSpec(npmSpec: string): string | undefined {
|
||||
const parsed = parseRegistryNpmSpec(npmSpec);
|
||||
if (!parsed?.name.startsWith("@openclaw/")) {
|
||||
@@ -192,6 +201,16 @@ function collectDownloadableInstallCandidates(params: {
|
||||
});
|
||||
}
|
||||
|
||||
for (const entry of RUNTIME_PLUGIN_INSTALL_CANDIDATES) {
|
||||
if (!configuredPluginIds.has(entry.pluginId) && !params.missingPluginIds.has(entry.pluginId)) {
|
||||
continue;
|
||||
}
|
||||
if (params.blockedPluginIds?.has(entry.pluginId)) {
|
||||
continue;
|
||||
}
|
||||
candidates.set(entry.pluginId, entry);
|
||||
}
|
||||
|
||||
return [...candidates.values()].toSorted((left, right) =>
|
||||
left.pluginId.localeCompare(right.pluginId),
|
||||
);
|
||||
|
||||
@@ -39,6 +39,12 @@ describe("configured plugin install release step", () => {
|
||||
touchedVersion: "2026.4.30",
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
shouldRunConfiguredPluginInstallReleaseStep({
|
||||
currentVersion: "2026.5.2-beta.1",
|
||||
touchedVersion: "2026.5.1",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldRunConfiguredPluginInstallReleaseStep({
|
||||
currentVersion: "2026.5.2",
|
||||
@@ -123,6 +129,37 @@ describe("configured plugin install release step", () => {
|
||||
expect(result.channelIds).toEqual(["wecom"]);
|
||||
});
|
||||
|
||||
it("collects Codex from the configured agent runtime even without integration discovery", async () => {
|
||||
const { collectReleaseConfiguredPluginIds } =
|
||||
await import("./release-configured-plugin-installs.js");
|
||||
const result = collectReleaseConfiguredPluginIds({
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.4",
|
||||
agentRuntime: { id: "codex" },
|
||||
},
|
||||
},
|
||||
},
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(mocks.detectPluginAutoEnableCandidates).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
config: expect.objectContaining({
|
||||
agents: expect.objectContaining({
|
||||
defaults: expect.objectContaining({
|
||||
model: "openai/gpt-5.4",
|
||||
agentRuntime: { id: "codex" },
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(result.pluginIds).toEqual(["codex"]);
|
||||
expect(result.channelIds).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not collect channel ids when the matching plugin id is blocked", async () => {
|
||||
const { collectReleaseConfiguredPluginIds } =
|
||||
await import("./release-configured-plugin-installs.js");
|
||||
@@ -178,26 +215,30 @@ describe("configured plugin install release step", () => {
|
||||
});
|
||||
|
||||
it("repairs used plugin installs and touches config only on success", async () => {
|
||||
mocks.detectPluginAutoEnableCandidates.mockReturnValue([
|
||||
{ pluginId: "matrix", kind: "channel-configured", channelId: "matrix" },
|
||||
]);
|
||||
mocks.repairMissingPluginInstallsForIds.mockResolvedValue({
|
||||
changes: ['Installed missing configured plugin "matrix".'],
|
||||
changes: ['Installed missing configured plugin "codex".'],
|
||||
warnings: [],
|
||||
});
|
||||
|
||||
const { maybeRunConfiguredPluginInstallReleaseStep } =
|
||||
await import("./release-configured-plugin-installs.js");
|
||||
const result = await maybeRunConfiguredPluginInstallReleaseStep({
|
||||
cfg: {},
|
||||
currentVersion: "2026.5.2",
|
||||
cfg: {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: "openai/gpt-5.4",
|
||||
agentRuntime: { id: "codex" },
|
||||
},
|
||||
},
|
||||
},
|
||||
currentVersion: "2026.5.2-beta.1",
|
||||
touchedVersion: "2026.5.1",
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(mocks.repairMissingPluginInstallsForIds).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pluginIds: ["matrix"],
|
||||
pluginIds: ["codex"],
|
||||
channelIds: [],
|
||||
env: {},
|
||||
}),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { collectConfiguredAgentHarnessRuntimes } from "../../../agents/harness-runtimes.js";
|
||||
import { listPotentialConfiguredChannelPresenceSignals } from "../../../channels/config-presence.js";
|
||||
import { normalizeChatChannelId } from "../../../channels/registry.js";
|
||||
import { isChannelConfigured } from "../../../config/channel-configured.js";
|
||||
@@ -9,7 +10,12 @@ import { VERSION } from "../../../version.js";
|
||||
import { repairMissingPluginInstallsForIds } from "./missing-configured-plugin-install.js";
|
||||
import { asObjectRecord } from "./object.js";
|
||||
|
||||
export const CONFIGURED_PLUGIN_INSTALL_RELEASE_VERSION = "2026.5.2";
|
||||
export const CONFIGURED_PLUGIN_INSTALL_RELEASE_VERSION = "2026.5.2-beta.1";
|
||||
|
||||
const AGENT_HARNESS_RUNTIME_PLUGIN_IDS: Readonly<Record<string, string>> = {
|
||||
// Codex can be selected as a harness for OpenAI models without a plugin entry.
|
||||
codex: "codex",
|
||||
};
|
||||
|
||||
type ReleaseConfiguredPluginIds = {
|
||||
pluginIds: string[];
|
||||
@@ -207,6 +213,16 @@ function collectProviderPluginIds(cfg: OpenClawConfig, env: NodeJS.ProcessEnv):
|
||||
return [...ids].toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function collectAgentHarnessRuntimePluginIds(
|
||||
cfg: OpenClawConfig,
|
||||
env: NodeJS.ProcessEnv,
|
||||
): string[] {
|
||||
return collectConfiguredAgentHarnessRuntimes(cfg, env)
|
||||
.map((runtime) => AGENT_HARNESS_RUNTIME_PLUGIN_IDS[runtime])
|
||||
.filter((pluginId): pluginId is string => Boolean(pluginId))
|
||||
.toSorted((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function addEligiblePluginId(cfg: OpenClawConfig, pluginIds: Set<string>, pluginId: string): void {
|
||||
const normalized = pluginId.trim();
|
||||
if (!normalized || isDenied(cfg, normalized) || isDisabled(cfg, normalized)) {
|
||||
@@ -258,6 +274,9 @@ export function collectReleaseConfiguredPluginIds(params: {
|
||||
for (const pluginId of collectProviderPluginIds(params.cfg, env)) {
|
||||
addEligiblePluginId(params.cfg, pluginIds, pluginId);
|
||||
}
|
||||
for (const pluginId of collectAgentHarnessRuntimePluginIds(params.cfg, env)) {
|
||||
addEligiblePluginId(params.cfg, pluginIds, pluginId);
|
||||
}
|
||||
for (const channelId of collectConfiguredChannelIds(params.cfg, env)) {
|
||||
if (
|
||||
!isChannelDisabled(params.cfg, channelId) &&
|
||||
|
||||
Reference in New Issue
Block a user