Skip to content

Commit fccb2b8

Browse files
committed
fix: launch Windows startup gateway directly
1 parent c197b3f commit fccb2b8

4 files changed

Lines changed: 57 additions & 25 deletions

File tree

scripts/e2e/parallels-macos-smoke.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ BUILD_LOCK_DIR="${TMPDIR:-/tmp}/openclaw-parallels-build.lock"
4747
TIMEOUT_INSTALL_SITE_S=420
4848
TIMEOUT_INSTALL_TGZ_S=420
4949
TIMEOUT_INSTALL_REGISTRY_S=420
50-
TIMEOUT_UPDATE_DEV_S=300
50+
TIMEOUT_UPDATE_DEV_S="${OPENCLAW_PARALLELS_MACOS_UPDATE_DEV_TIMEOUT_S:-600}"
5151
TIMEOUT_VERIFY_S=60
5252
TIMEOUT_ONBOARD_S=180
5353
TIMEOUT_GATEWAY_S=180

scripts/e2e/parallels-windows-smoke.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ BUILD_LOCK_DIR="${TMPDIR:-/tmp}/openclaw-parallels-build.lock"
4141
TIMEOUT_SNAPSHOT_S=240
4242
TIMEOUT_GIT_SETUP_S=1200
4343
TIMEOUT_INSTALL_S=420
44-
TIMEOUT_UPDATE_S=300
44+
TIMEOUT_UPDATE_S="${OPENCLAW_PARALLELS_WINDOWS_UPDATE_TIMEOUT_S:-1800}"
4545
TIMEOUT_UPDATE_POLL_GRACE_S=60
4646
TIMEOUT_VERIFY_S=120
4747
TIMEOUT_ONBOARD_S=600

src/daemon/schtasks.startup-fallback.test.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import fs from "node:fs/promises";
22
import path from "node:path";
33
import { PassThrough } from "node:stream";
44
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5-
import { quoteCmdScriptArg } from "./cmd-argv.js";
65
import "./test-helpers/schtasks-base-mocks.js";
76
import {
87
inspectPortUsage,
@@ -91,11 +90,25 @@ async function writeStartupFallbackEntry(env: Record<string, string>) {
9190
return startupEntryPath;
9291
}
9392

94-
function expectStartupFallbackSpawn(env: Record<string, string>) {
95-
expect(spawn).toHaveBeenCalledWith(
96-
"cmd.exe",
97-
["/d", "/s", "/c", quoteCmdScriptArg(resolveTaskScriptPath(env))],
98-
expect.objectContaining({ detached: true, stdio: "ignore", windowsHide: true }),
93+
function expectStartupFallbackSpawn() {
94+
expect(spawn).toHaveBeenCalled();
95+
const calls = spawn.mock.calls as unknown as Array<
96+
[string, readonly string[], Record<string, unknown>]
97+
>;
98+
const lastCall = calls[calls.length - 1];
99+
if (!lastCall) {
100+
throw new Error("expected gateway launch spawn call");
101+
}
102+
const [executable, args, options] = lastCall;
103+
expect(executable).not.toBe("cmd.exe");
104+
expect(args).toEqual(expect.arrayContaining(["--port", "18789"]));
105+
expect(options).toEqual(
106+
expect.objectContaining({
107+
detached: true,
108+
env: expect.objectContaining({ OPENCLAW_GATEWAY_PORT: "18789" }),
109+
stdio: "ignore",
110+
windowsHide: true,
111+
}),
99112
);
100113
}
101114

@@ -197,11 +210,7 @@ describe("Windows startup fallback", () => {
197210
expect(result.scriptPath).toBe(resolveTaskScriptPath(env));
198211
expect(startupScript).toContain('start "" /min cmd.exe /d /c');
199212
expect(startupScript).toContain("gateway.cmd");
200-
expect(spawn).toHaveBeenCalledWith(
201-
"cmd.exe",
202-
["/d", "/s", "/c", quoteCmdScriptArg(resolveTaskScriptPath(env))],
203-
expect.objectContaining({ detached: true, stdio: "ignore", windowsHide: true }),
204-
);
213+
expectStartupFallbackSpawn();
205214
expect(childUnref).toHaveBeenCalled();
206215
expect(printed).toContain("Installed Windows login item");
207216
});
@@ -216,7 +225,7 @@ describe("Windows startup fallback", () => {
216225
await installGatewayScheduledTask(env);
217226

218227
await expect(fs.access(resolveStartupEntryPath(env))).resolves.toBeUndefined();
219-
expectStartupFallbackSpawn(env);
228+
expectStartupFallbackSpawn();
220229
});
221230
});
222231

@@ -231,18 +240,18 @@ describe("Windows startup fallback", () => {
231240
await installGatewayScheduledTask(env);
232241

233242
await expect(fs.access(resolveStartupEntryPath(env))).resolves.toBeUndefined();
234-
expectStartupFallbackSpawn(env);
243+
expectStartupFallbackSpawn();
235244
});
236245
});
237246

238-
it("launches the task script directly when schtasks /Run is accepted but never starts the task", async () => {
247+
it("launches through the Startup-style launcher when schtasks /Run is accepted but never starts the task", async () => {
239248
await withWindowsEnv("openclaw-win-startup-", async ({ env }) => {
240249
fastForwardTaskStartWait();
241250
addAcceptedRunNeverStartsResponses();
242251

243252
await installGatewayScheduledTask(env);
244253

245-
expectStartupFallbackSpawn(env);
254+
expectStartupFallbackSpawn();
246255
});
247256
});
248257

@@ -388,6 +397,7 @@ describe("Windows startup fallback", () => {
388397
{ code: 0, stdout: "", stderr: "" },
389398
{ code: 1, stdout: "", stderr: "not found" },
390399
]);
400+
await writeGatewayScript(env);
391401
await writeStartupFallbackEntry(env);
392402
inspectPortUsage.mockResolvedValue({
393403
port: 18789,
@@ -401,7 +411,7 @@ describe("Windows startup fallback", () => {
401411
outcome: "completed",
402412
});
403413
expectGatewayTermination(5151);
404-
expectStartupFallbackSpawn(env);
414+
expectStartupFallbackSpawn();
405415
});
406416
});
407417

@@ -432,7 +442,7 @@ describe("Windows startup fallback", () => {
432442
outcome: "completed",
433443
});
434444

435-
expectStartupFallbackSpawn(env);
445+
expectStartupFallbackSpawn();
436446
});
437447
});
438448

src/daemon/schtasks.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,14 +270,18 @@ function buildTaskScript({
270270
return `${lines.join("\r\n")}\r\n`;
271271
}
272272

273+
function renderStartupLaunchCommand(scriptPath: string): string {
274+
return `start "" /min cmd.exe /d /c ${quoteCmdScriptArg(scriptPath)}`;
275+
}
276+
273277
function buildStartupLauncherScript(params: { description?: string; scriptPath: string }): string {
274278
const lines = ["@echo off"];
275279
const trimmedDescription = params.description?.trim();
276280
if (trimmedDescription) {
277281
assertNoCmdLineBreak(trimmedDescription, "Startup launcher description");
278282
lines.push(`rem ${trimmedDescription}`);
279283
}
280-
lines.push(`start "" /min cmd.exe /d /c ${quoteCmdScriptArg(params.scriptPath)}`);
284+
lines.push(renderStartupLaunchCommand(params.scriptPath));
281285
return `${lines.join("\r\n")}\r\n`;
282286
}
283287

@@ -309,8 +313,26 @@ async function isRegisteredScheduledTask(env: GatewayServiceEnv): Promise<boolea
309313
return res.code === 0;
310314
}
311315

312-
function launchFallbackTaskScript(scriptPath: string): void {
313-
const child = spawn("cmd.exe", ["/d", "/s", "/c", quoteCmdScriptArg(scriptPath)], {
316+
async function launchFallbackTaskScript(env: GatewayServiceEnv): Promise<void> {
317+
const scriptPath = resolveTaskScriptPath(env);
318+
const command = await readScheduledTaskCommand(env);
319+
if (command?.programArguments.length) {
320+
const [executable, ...args] = command.programArguments;
321+
const child = spawn(executable, args, {
322+
cwd: command.workingDirectory || undefined,
323+
detached: true,
324+
env: {
325+
...process.env,
326+
...command.environment,
327+
},
328+
stdio: "ignore",
329+
windowsHide: true,
330+
});
331+
child.unref();
332+
return;
333+
}
334+
335+
const child = spawn("cmd.exe", ["/d", "/c", scriptPath], {
314336
detached: true,
315337
stdio: "ignore",
316338
windowsHide: true,
@@ -563,7 +585,7 @@ async function restartStartupEntry(
563585
if (typeof runtime.pid === "number" && runtime.pid > 0) {
564586
await terminateGatewayProcessTree(runtime.pid, 300);
565587
}
566-
launchFallbackTaskScript(resolveTaskScriptPath(env));
588+
await launchFallbackTaskScript(env);
567589
stdout.write(`${formatLine("Restarted Windows login item", resolveTaskName(env))}\n`);
568590
return { outcome: "completed" };
569591
}
@@ -784,7 +806,7 @@ async function runScheduledTaskOrThrow(params: {
784806
) {
785807
return;
786808
}
787-
launchFallbackTaskScript(params.scriptPath);
809+
await launchFallbackTaskScript(params.env);
788810
}
789811

790812
async function activateScheduledTask(params: {
@@ -831,7 +853,7 @@ async function activateScheduledTask(params: {
831853
scriptPath: params.scriptPath,
832854
});
833855
await fs.writeFile(startupEntryPath, launcher, "utf8");
834-
launchFallbackTaskScript(params.scriptPath);
856+
await launchFallbackTaskScript(params.env);
835857
writeFormattedLines(
836858
params.stdout,
837859
[

0 commit comments

Comments
 (0)