

























@@ -170,13 +170,14 @@ function createClawHubInstallConfig(params: {
170170clawhubPackage: string;
171171clawhubFamily: "bundle-plugin" | "code-plugin";
172172clawhubChannel: "community" | "official" | "private";
173+spec?: string;
173174}): OpenClawConfig {
174175return {
175176plugins: {
176177installs: {
177178[params.pluginId]: {
178179source: "clawhub" as const,
179-spec: `clawhub:${params.clawhubPackage}`,
180+spec: params.spec ?? `clawhub:${params.clawhubPackage}`,
180181installPath: params.installPath,
181182clawhubUrl: params.clawhubUrl,
182183clawhubPackage: params.clawhubPackage,
@@ -1029,6 +1030,115 @@ describe("updateNpmInstalledPlugins", () => {
10291030},
10301031);
103110321033+it("tries npm beta for default npm specs on beta channel without persisting the beta tag", async () => {
1034+installPluginFromNpmSpecMock.mockResolvedValue(
1035+createSuccessfulNpmUpdateResult({
1036+pluginId: "openclaw-codex-app-server",
1037+targetDir: "/tmp/openclaw-codex-app-server",
1038+version: "0.2.0-beta.4",
1039+npmResolution: {
1040+name: "openclaw-codex-app-server",
1041+version: "0.2.0-beta.4",
1042+resolvedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
1043+},
1044+}),
1045+);
1046+1047+const result = await updateNpmInstalledPlugins({
1048+config: createCodexAppServerInstallConfig({
1049+spec: "openclaw-codex-app-server",
1050+}),
1051+pluginIds: ["openclaw-codex-app-server"],
1052+updateChannel: "beta",
1053+});
1054+1055+expectNpmUpdateCall({
1056+spec: "openclaw-codex-app-server@beta",
1057+expectedPluginId: "openclaw-codex-app-server",
1058+});
1059+expectCodexAppServerInstallState({
1060+ result,
1061+spec: "openclaw-codex-app-server",
1062+version: "0.2.0-beta.4",
1063+resolvedSpec: "openclaw-codex-app-server@0.2.0-beta.4",
1064+});
1065+});
1066+1067+it("falls back to the default npm spec when a beta tag is unavailable", async () => {
1068+installPluginFromNpmSpecMock
1069+.mockResolvedValueOnce({
1070+ok: false,
1071+error:
1072+"npm ERR! code ETARGET\nnpm ERR! No matching version found for openclaw-codex-app-server@beta.",
1073+})
1074+.mockResolvedValueOnce(
1075+createSuccessfulNpmUpdateResult({
1076+pluginId: "openclaw-codex-app-server",
1077+targetDir: "/tmp/openclaw-codex-app-server",
1078+version: "0.2.6",
1079+npmResolution: {
1080+name: "openclaw-codex-app-server",
1081+version: "0.2.6",
1082+resolvedSpec: "openclaw-codex-app-server@0.2.6",
1083+},
1084+}),
1085+);
1086+1087+const warnMessages: string[] = [];
1088+const result = await updateNpmInstalledPlugins({
1089+config: createCodexAppServerInstallConfig({
1090+spec: "openclaw-codex-app-server",
1091+}),
1092+pluginIds: ["openclaw-codex-app-server"],
1093+updateChannel: "beta",
1094+logger: { warn: (msg) => warnMessages.push(msg) },
1095+});
1096+1097+expect(installPluginFromNpmSpecMock).toHaveBeenNthCalledWith(
1098+1,
1099+expect.objectContaining({
1100+spec: "openclaw-codex-app-server@beta",
1101+}),
1102+);
1103+expect(installPluginFromNpmSpecMock).toHaveBeenNthCalledWith(
1104+2,
1105+expect.objectContaining({
1106+spec: "openclaw-codex-app-server",
1107+}),
1108+);
1109+expect(warnMessages).toEqual([expect.stringContaining("has no beta npm release")]);
1110+expectCodexAppServerInstallState({
1111+ result,
1112+spec: "openclaw-codex-app-server",
1113+version: "0.2.6",
1114+resolvedSpec: "openclaw-codex-app-server@0.2.6",
1115+});
1116+});
1117+1118+it("preserves explicit npm tags when updating on the beta channel", async () => {
1119+installPluginFromNpmSpecMock.mockResolvedValue(
1120+createSuccessfulNpmUpdateResult({
1121+pluginId: "openclaw-codex-app-server",
1122+targetDir: "/tmp/openclaw-codex-app-server",
1123+version: "0.2.0-rc.1",
1124+}),
1125+);
1126+1127+await updateNpmInstalledPlugins({
1128+config: createCodexAppServerInstallConfig({
1129+spec: "openclaw-codex-app-server@rc",
1130+}),
1131+pluginIds: ["openclaw-codex-app-server"],
1132+updateChannel: "beta",
1133+dryRun: true,
1134+});
1135+1136+expectNpmUpdateCall({
1137+spec: "openclaw-codex-app-server@rc",
1138+expectedPluginId: "openclaw-codex-app-server",
1139+});
1140+});
1141+10321142it("updates ClawHub-installed plugins via recorded package metadata", async () => {
10331143installPluginFromClawHubMock.mockResolvedValue({
10341144ok: true,
@@ -1098,6 +1208,130 @@ describe("updateNpmInstalledPlugins", () => {
10981208});
10991209});
110012101211+it("tries ClawHub beta for default ClawHub specs on beta channel without persisting the beta tag", async () => {
1212+installPluginFromClawHubMock.mockResolvedValue(
1213+createSuccessfulClawHubUpdateResult({
1214+pluginId: "demo",
1215+targetDir: "/tmp/demo",
1216+version: "1.3.0-beta.1",
1217+clawhubPackage: "demo",
1218+}),
1219+);
1220+1221+const result = await updateNpmInstalledPlugins({
1222+config: createClawHubInstallConfig({
1223+pluginId: "demo",
1224+installPath: "/tmp/demo",
1225+clawhubUrl: "https://clawhub.ai",
1226+clawhubPackage: "demo",
1227+clawhubFamily: "code-plugin",
1228+clawhubChannel: "official",
1229+}),
1230+pluginIds: ["demo"],
1231+updateChannel: "beta",
1232+});
1233+1234+expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
1235+expect.objectContaining({
1236+spec: "clawhub:demo@beta",
1237+baseUrl: "https://clawhub.ai",
1238+expectedPluginId: "demo",
1239+}),
1240+);
1241+expect(result.config.plugins?.installs?.demo).toMatchObject({
1242+source: "clawhub",
1243+spec: "clawhub:demo",
1244+installPath: "/tmp/demo",
1245+version: "1.3.0-beta.1",
1246+clawhubPackage: "demo",
1247+});
1248+});
1249+1250+it("falls back to the default ClawHub spec when a beta release is unavailable", async () => {
1251+installPluginFromClawHubMock
1252+.mockResolvedValueOnce({
1253+ok: false,
1254+code: "version_not_found",
1255+error: "version not found: beta",
1256+})
1257+.mockResolvedValueOnce(
1258+createSuccessfulClawHubUpdateResult({
1259+pluginId: "demo",
1260+targetDir: "/tmp/demo",
1261+version: "1.2.4",
1262+clawhubPackage: "demo",
1263+}),
1264+);
1265+1266+const warnMessages: string[] = [];
1267+const result = await updateNpmInstalledPlugins({
1268+config: createClawHubInstallConfig({
1269+pluginId: "demo",
1270+installPath: "/tmp/demo",
1271+clawhubUrl: "https://clawhub.ai",
1272+clawhubPackage: "demo",
1273+clawhubFamily: "code-plugin",
1274+clawhubChannel: "official",
1275+}),
1276+pluginIds: ["demo"],
1277+updateChannel: "beta",
1278+logger: { warn: (msg) => warnMessages.push(msg) },
1279+});
1280+1281+expect(installPluginFromClawHubMock).toHaveBeenNthCalledWith(
1282+1,
1283+expect.objectContaining({
1284+spec: "clawhub:demo@beta",
1285+}),
1286+);
1287+expect(installPluginFromClawHubMock).toHaveBeenNthCalledWith(
1288+2,
1289+expect.objectContaining({
1290+spec: "clawhub:demo",
1291+}),
1292+);
1293+expect(warnMessages).toEqual([expect.stringContaining("has no beta ClawHub release")]);
1294+expect(result.config.plugins?.installs?.demo).toMatchObject({
1295+source: "clawhub",
1296+spec: "clawhub:demo",
1297+installPath: "/tmp/demo",
1298+version: "1.2.4",
1299+clawhubPackage: "demo",
1300+});
1301+});
1302+1303+it("preserves explicit ClawHub tags when updating on the beta channel", async () => {
1304+installPluginFromClawHubMock.mockResolvedValue(
1305+createSuccessfulClawHubUpdateResult({
1306+pluginId: "demo",
1307+targetDir: "/tmp/demo",
1308+version: "1.3.0-rc.1",
1309+clawhubPackage: "demo",
1310+}),
1311+);
1312+1313+await updateNpmInstalledPlugins({
1314+config: createClawHubInstallConfig({
1315+pluginId: "demo",
1316+installPath: "/tmp/demo",
1317+clawhubUrl: "https://clawhub.ai",
1318+clawhubPackage: "demo",
1319+clawhubFamily: "code-plugin",
1320+clawhubChannel: "official",
1321+spec: "clawhub:demo@rc",
1322+}),
1323+pluginIds: ["demo"],
1324+updateChannel: "beta",
1325+dryRun: true,
1326+});
1327+1328+expect(installPluginFromClawHubMock).toHaveBeenCalledWith(
1329+expect.objectContaining({
1330+spec: "clawhub:demo@rc",
1331+}),
1332+);
1333+});
1334+11011335it("skips ClawHub plugin update when bundled version is newer", async () => {
11021336resolveBundledPluginSourcesMock.mockReturnValue(
11031337new Map([
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。