




















@@ -30,13 +30,22 @@ const resolveGatewayAuthMock = vi.hoisted(() =>
3030);
3131const resolveSecretRefValuesMock = vi.hoisted(() => vi.fn());
3232const randomTokenMock = vi.hoisted(() => vi.fn(() => "generated-token"));
33-const buildGatewayInstallPlanMock = vi.hoisted(() =>
34-vi.fn(async () => ({
35-programArguments: ["openclaw", "gateway", "run"],
36-workingDirectory: "/tmp",
37-environment: {},
38-})),
39-);
33+const createInstallPlanFixture = vi.hoisted(() => {
34+return async (params?: { wrapperPath?: string; env?: Record<string, string | undefined> }) => {
35+const environment: Record<string, string | undefined> = {};
36+if (params?.wrapperPath || params?.env?.OPENCLAW_WRAPPER) {
37+environment.OPENCLAW_WRAPPER = params.wrapperPath ?? params.env?.OPENCLAW_WRAPPER;
38+}
39+return {
40+programArguments: params?.wrapperPath
41+ ? [params.wrapperPath, "gateway", "run"]
42+ : ["openclaw", "gateway", "run"],
43+workingDirectory: "/tmp",
44+ environment,
45+};
46+};
47+});
48+const buildGatewayInstallPlanMock = vi.hoisted(() => vi.fn(createInstallPlanFixture));
4049const parsePortMock = vi.hoisted(() => vi.fn(() => null));
4150const isGatewayDaemonRuntimeMock = vi.hoisted(() => vi.fn(() => true));
4251const installDaemonServiceAndEmitMock = vi.hoisted(() => vi.fn(async () => {}));
@@ -108,6 +117,11 @@ vi.mock("../../commands/daemon-install-helpers.js", () => ({
108117buildGatewayInstallPlan: buildGatewayInstallPlanMock,
109118}));
110119120+vi.mock("../../daemon/program-args.js", () => ({
121+OPENCLAW_WRAPPER_ENV_KEY: "OPENCLAW_WRAPPER",
122+resolveOpenClawWrapperPath: async (value: string | undefined) => value?.trim() || undefined,
123+}));
124+111125vi.mock("./shared.js", () => ({
112126parsePort: parsePortMock,
113127createDaemonInstallActionContext: (jsonFlag: unknown) => {
@@ -188,6 +202,7 @@ describe("runDaemonInstall", () => {
188202installDaemonServiceAndEmitMock.mockReset();
189203service.isLoaded.mockReset();
190204service.stage.mockReset();
205+service.readCommand.mockReset();
191206resetRuntimeCapture();
192207actionState.warnings.length = 0;
193208actionState.emitted.length = 0;
@@ -211,11 +226,7 @@ describe("runDaemonInstall", () => {
211226});
212227resolveSecretRefValuesMock.mockResolvedValue(new Map());
213228randomTokenMock.mockReturnValue("generated-token");
214-buildGatewayInstallPlanMock.mockResolvedValue({
215-programArguments: ["openclaw", "gateway", "run"],
216-workingDirectory: "/tmp",
217-environment: {},
218-});
229+buildGatewayInstallPlanMock.mockImplementation(createInstallPlanFixture);
219230parsePortMock.mockReturnValue(null);
220231isGatewayDaemonRuntimeMock.mockReturnValue(true);
221232installDaemonServiceAndEmitMock.mockResolvedValue(undefined);
@@ -402,6 +413,50 @@ describe("runDaemonInstall", () => {
402413expect(actionState.emitted.at(-1)).toMatchObject({ result: "already-installed" });
403414});
404415416+it("preserves wrapper env from an installed but unloaded service during forced reinstall", async () => {
417+service.isLoaded.mockResolvedValue(false);
418+service.readCommand.mockResolvedValue({
419+programArguments: ["/usr/local/bin/openclaw-doppler", "gateway", "run"],
420+environment: {
421+OPENCLAW_WRAPPER: "/usr/local/bin/openclaw-doppler",
422+},
423+} as never);
424+425+await runDaemonInstall({ json: true, force: true });
426+427+expect(service.readCommand).toHaveBeenCalledTimes(1);
428+expect(buildGatewayInstallPlanMock).toHaveBeenCalledWith(
429+expect.objectContaining({
430+wrapperPath: "/usr/local/bin/openclaw-doppler",
431+existingEnvironment: expect.objectContaining({
432+OPENCLAW_WRAPPER: "/usr/local/bin/openclaw-doppler",
433+}),
434+env: expect.objectContaining({
435+OPENCLAW_WRAPPER: "/usr/local/bin/openclaw-doppler",
436+}),
437+}),
438+);
439+expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1);
440+});
441+442+it("reinstalls when wrapper command matches but wrapper env is missing", async () => {
443+service.isLoaded.mockResolvedValue(true);
444+service.readCommand.mockResolvedValue({
445+programArguments: ["/usr/local/bin/openclaw-doppler", "gateway", "run"],
446+environment: {},
447+} as never);
448+449+await runDaemonInstall({
450+json: true,
451+wrapper: "/usr/local/bin/openclaw-doppler",
452+});
453+454+expect(installDaemonServiceAndEmitMock).toHaveBeenCalledTimes(1);
455+expect(actionState.warnings).toContain(
456+"Gateway service OPENCLAW_WRAPPER differs from the current wrapper install plan; refreshing the install.",
457+);
458+});
459+405460it("reinstalls when the embedded gateway token differs from the install plan", async () => {
406461service.isLoaded.mockResolvedValue(true);
407462service.readCommand.mockResolvedValue({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。