























@@ -27,8 +27,10 @@ vi.mock("../plugins/bundled-sources.js", () => ({
2727}));
28282929const installPluginFromNpmSpec = vi.hoisted(() => vi.fn());
30+const installPluginFromNpmPackArchive = vi.hoisted(() => vi.fn());
3031vi.mock("../plugins/install.js", () => ({
3132 installPluginFromNpmSpec,
33+ installPluginFromNpmPackArchive,
3234}));
33353436const installPluginFromClawHub = vi.hoisted(() => vi.fn());
@@ -86,6 +88,8 @@ function requireCapturedPrompt<T>(captured: T | undefined): T {
8688describe("ensureOnboardingPluginInstalled", () => {
8789beforeEach(() => {
8890vi.clearAllMocks();
91+delete process.env.OPENCLAW_ALLOW_PLUGIN_INSTALL_OVERRIDES;
92+delete process.env.OPENCLAW_PLUGIN_INSTALL_OVERRIDES;
8993withTimeout.mockImplementation(async <T>(promise: Promise<T>) => await promise);
9094refreshPluginRegistryAfterConfigMutation.mockResolvedValue(undefined);
9195});
@@ -125,6 +129,123 @@ describe("ensureOnboardingPluginInstalled", () => {
125129expect(enablePluginInConfig).not.toHaveBeenCalled();
126130});
127131132+it("uses a guarded npm-pack install override for the matching plugin id", async () => {
133+const archivePath = path.resolve("tmp/demo-plugin.tgz");
134+process.env.OPENCLAW_ALLOW_PLUGIN_INSTALL_OVERRIDES = "1";
135+process.env.OPENCLAW_PLUGIN_INSTALL_OVERRIDES = JSON.stringify({
136+"other-plugin": "npm:@demo/other@1.0.0",
137+"demo-plugin": `npm-pack:${archivePath}`,
138+});
139+installPluginFromNpmPackArchive.mockResolvedValue({
140+ok: true,
141+pluginId: "demo-plugin",
142+targetDir: "/tmp/openclaw/extensions/demo-plugin",
143+version: "1.2.3",
144+manifestName: "@demo/plugin",
145+npmTarballName: "demo-plugin-1.2.3.tgz",
146+npmResolution: {
147+name: "@demo/plugin",
148+version: "1.2.3",
149+resolvedSpec: "file:demo-plugin-1.2.3.tgz",
150+integrity: "sha512-demo",
151+shasum: "abc123",
152+resolvedAt: "2026-05-09T00:00:00.000Z",
153+},
154+});
155+156+const select = vi.fn(async () => "npm");
157+const result = await ensureOnboardingPluginInstalled({
158+cfg: {},
159+entry: {
160+pluginId: "demo-plugin",
161+label: "Demo Plugin",
162+install: {
163+npmSpec: "@demo/plugin@1.2.3",
164+},
165+trustedSourceLinkedOfficialInstall: true,
166+},
167+prompter: {
168+ select,
169+note: vi.fn(),
170+progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
171+} as never,
172+runtime: { log: vi.fn() } as never,
173+});
174+175+expect(select).not.toHaveBeenCalled();
176+expect(installPluginFromNpmSpec).not.toHaveBeenCalled();
177+expect(installPluginFromNpmPackArchive).toHaveBeenCalledWith(
178+expect.objectContaining({
179+ archivePath,
180+expectedPluginId: "demo-plugin",
181+}),
182+);
183+expect(installPluginFromNpmPackArchive.mock.calls[0]?.[0]).not.toHaveProperty(
184+"trustedSourceLinkedOfficialInstall",
185+);
186+expect(recordPluginInstall).toHaveBeenCalledWith(expect.anything(), {
187+pluginId: "demo-plugin",
188+source: "npm",
189+spec: "file:demo-plugin-1.2.3.tgz",
190+sourcePath: archivePath,
191+installPath: "/tmp/openclaw/extensions/demo-plugin",
192+version: "1.2.3",
193+artifactKind: "npm-pack",
194+artifactFormat: "tgz",
195+npmIntegrity: "sha512-demo",
196+npmShasum: "abc123",
197+npmTarballName: "demo-plugin-1.2.3.tgz",
198+});
199+expect(result.status).toBe("installed");
200+});
201+202+it("uses a guarded npm install override without official-trust flags", async () => {
203+process.env.OPENCLAW_ALLOW_PLUGIN_INSTALL_OVERRIDES = "1";
204+process.env.OPENCLAW_PLUGIN_INSTALL_OVERRIDES = JSON.stringify({
205+codex: "npm:@openclaw/codex@2026.5.8",
206+"other-plugin": "npm-pack:/tmp/other.tgz",
207+});
208+installPluginFromNpmSpec.mockResolvedValue({
209+ok: true,
210+pluginId: "codex",
211+targetDir: "/tmp/openclaw/extensions/codex",
212+version: "2026.5.8",
213+npmResolution: {
214+name: "@openclaw/codex",
215+version: "2026.5.8",
216+resolvedSpec: "@openclaw/codex@2026.5.8",
217+},
218+});
219+220+await ensureOnboardingPluginInstalled({
221+cfg: {},
222+entry: {
223+pluginId: "codex",
224+label: "Codex",
225+install: {
226+npmSpec: "@openclaw/codex",
227+},
228+trustedSourceLinkedOfficialInstall: true,
229+},
230+prompter: {
231+select: vi.fn(async () => "npm"),
232+note: vi.fn(),
233+progress: vi.fn(() => ({ update: vi.fn(), stop: vi.fn() })),
234+} as never,
235+runtime: { log: vi.fn() } as never,
236+});
237+238+expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
239+expect.not.objectContaining({ trustedSourceLinkedOfficialInstall: true }),
240+);
241+expect(installPluginFromNpmSpec).toHaveBeenCalledWith(
242+expect.objectContaining({
243+spec: "@openclaw/codex@2026.5.8",
244+expectedPluginId: "codex",
245+}),
246+);
247+});
248+128249it("installs and records ClawHub provider plugins with source facts", async () => {
129250installPluginFromClawHub.mockImplementation(async (params) => {
130251params.logger?.info?.("Downloading demo-plugin from ClawHub…");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。