mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:40:44 +00:00
fix(doctor): preserve catalog repair specs
This commit is contained in:
@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Plugins/externalization: pin beta-only official launch packages for ACPX, Google Chat, and LINE to explicit npm beta specs so catalog-driven installs do not trip the prerelease safety guard while npm `latest` still points at beta. Thanks @vincentkoc.
|
||||
- CLI/doctor: keep missing-plugin repair from overriding official catalog metadata with runtime fallbacks, so ACPX repairs preserve the beta npm spec during the externalization rollout. Thanks @vincentkoc.
|
||||
- Control UI/Talk: fix Talk (OpenAI Realtime WebRTC) CORS failure by stripping server-side-only attribution headers (`originator`, `version`, `User-Agent`) from browser offer headers; `api.openai.com/v1/realtime/calls` only allows `authorization` and `content-type` in its CORS preflight, so forwarding these headers caused the browser SDP exchange to fail. Fixes #76435. Thanks @hclsys.
|
||||
- CLI/logs: auto-reconnect `openclaw logs --follow` on transient gateway disconnects (WebSocket close, timeout, connection drop) with bounded exponential backoff (up to 8 retries, capped at 30 s) and stderr retry warnings, while still exiting immediately on non-recoverable auth or configuration errors. Fixes #74782. (#75059) Thanks @shashank-poola.
|
||||
- Plugins/onboarding: trust optional official plugin and web-search installs selected from the official catalog so npm security scanning treats them like other source-linked official install paths. Thanks @vincentkoc.
|
||||
|
||||
@@ -404,6 +404,54 @@ describe("repairMissingConfiguredPluginInstalls", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not let runtime fallback metadata override official catalog install specs", async () => {
|
||||
mocks.installPluginFromNpmSpec.mockResolvedValueOnce({
|
||||
ok: true,
|
||||
pluginId: "acpx",
|
||||
targetDir: "/tmp/openclaw-plugins/acpx",
|
||||
version: "2026.5.2-beta.2",
|
||||
npmResolution: {
|
||||
name: "@openclaw/acpx",
|
||||
version: "2026.5.2-beta.2",
|
||||
resolvedSpec: "@openclaw/acpx@2026.5.2-beta.2",
|
||||
integrity: "sha512-acpx",
|
||||
resolvedAt: "2026-05-01T00:00:00.000Z",
|
||||
},
|
||||
});
|
||||
mocks.listOfficialExternalPluginCatalogEntries.mockReturnValue([
|
||||
{
|
||||
id: "acpx",
|
||||
label: "ACPX Runtime",
|
||||
install: {
|
||||
npmSpec: "@openclaw/acpx@beta",
|
||||
defaultChoice: "npm",
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const { repairMissingConfiguredPluginInstalls } =
|
||||
await import("./missing-configured-plugin-install.js");
|
||||
const result = await repairMissingConfiguredPluginInstalls({
|
||||
cfg: {
|
||||
acp: {
|
||||
backend: "acpx",
|
||||
},
|
||||
},
|
||||
env: {},
|
||||
});
|
||||
|
||||
expect(mocks.installPluginFromNpmSpec).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
spec: "@openclaw/acpx@beta",
|
||||
expectedPluginId: "acpx",
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
}),
|
||||
);
|
||||
expect(result.changes).toEqual([
|
||||
'Installed missing configured plugin "acpx" from @openclaw/acpx@beta.',
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not install disabled configured plugin entries", async () => {
|
||||
mocks.listOfficialExternalPluginCatalogEntries.mockReturnValue([
|
||||
{
|
||||
|
||||
@@ -44,7 +44,7 @@ const RUNTIME_PLUGIN_INSTALL_CANDIDATES: readonly DownloadableInstallCandidate[]
|
||||
{
|
||||
pluginId: "acpx",
|
||||
label: "ACPX Runtime",
|
||||
npmSpec: "@openclaw/acpx",
|
||||
npmSpec: "@openclaw/acpx@beta",
|
||||
trustedSourceLinkedOfficialInstall: true,
|
||||
},
|
||||
// Runtime-only configs do not have a provider/channel integration catalog entry.
|
||||
@@ -275,7 +275,9 @@ function collectDownloadableInstallCandidates(params: {
|
||||
if (params.blockedPluginIds?.has(entry.pluginId)) {
|
||||
continue;
|
||||
}
|
||||
candidates.set(entry.pluginId, entry);
|
||||
if (!candidates.has(entry.pluginId)) {
|
||||
candidates.set(entry.pluginId, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return [...candidates.values()].toSorted((left, right) =>
|
||||
|
||||
Reference in New Issue
Block a user