fix(crabbox): sync full sparse lease runs

This commit is contained in:
Vincent Koc
2026-05-25 15:39:13 +02:00
parent 3f363e0450
commit e97e831c12
3 changed files with 76 additions and 41 deletions

View File

@@ -52,6 +52,7 @@ Docs: https://docs.openclaw.ai
- Docker E2E: avoid rebuilding the Control UI twice while preparing the shared OpenClaw package tarball for package-backed scenario runs.
- Tests: avoid rebuilding the Control UI twice during the installer Docker smoke now that `pnpm build` includes `ui:build`.
- Tests: give QA config mutation RPCs enough native Windows budget to finish gateway config writes and restart settle after hot scenario runs.
- Crabbox: sync clean sparse worktrees through a temporary full checkout even when reusing an existing lease so tracked build-time files are not omitted.
- Build: route `scripts/ui.js` through the shared pnpm runner and keep Control UI chunking helpers in sparse-included source so native Windows Corepack builds can produce `dist/control-ui`.
- Tests: give the memory fallback QA scenario enough turn budget to exercise native Windows gateway runs instead of failing on the client timeout while the mock agent is still dispatching.
- Tests: collect QA gateway CPU/RSS metrics on native Windows and give the channel baseline enough turn budget to report slow gateway runs instead of timing out before proof.

View File

@@ -679,7 +679,7 @@ function shouldUseFullCheckoutForCleanSparseRemoteSync(commandArgs, providerName
if (commandArgs[0] !== "run" || isLocalContainerProvider(providerName)) {
return false;
}
if (hasOption(commandArgs, "--no-sync") || hasOption(commandArgs, "--id")) {
if (hasOption(commandArgs, "--no-sync")) {
return false;
}

View File

@@ -63,7 +63,11 @@ function makeFakeGit(
"process.exit(response.status ?? 0);",
].join("\n");
writeFileSync(gitPath, `${script}\n`, "utf8");
writeFileSync(`${gitPath}.cmd`, `@echo off\r\n"${process.execPath}" "%~dp0git" %*\r\n`, "utf8");
writeFileSync(
`${gitPath}.cmd`,
`@echo off\r\n"${process.execPath}" "%~dp0git" %*\r\n`,
"utf8",
);
chmodSync(gitPath, 0o755);
return binDir;
}
@@ -283,35 +287,40 @@ describe("scripts/crabbox-wrapper", () => {
]);
});
it("finds a Crabbox checkout next to the Git common dir in linked worktrees", () => {
const fakeWorkspaceParent = mkdtempSync(path.join(tmpdir(), "openclaw-linked-worktree-"));
tempDirs.push(fakeWorkspaceParent);
const gitCommonDir = path.join(fakeWorkspaceParent, "openclaw", ".git");
const crabboxBinDir = path.join(fakeWorkspaceParent, "crabbox", "bin");
mkdirSync(gitCommonDir, { recursive: true });
writeFakeCrabbox(crabboxBinDir, "provider: aws\n");
const gitResponses = {
["rev-parse\u0000--git-common-dir"]: { stdout: `${gitCommonDir}\n` },
};
const gitBinDir = makeFakeGit(gitResponses);
const itWithPosixLinkedWorktreeFixture = process.platform === "win32" ? it.skip : it;
const result = spawnSync(
process.execPath,
["scripts/crabbox-wrapper.mjs", "run", "--provider", "aws", "--", "echo ok"],
{
cwd: repoRoot,
encoding: "utf8",
env: {
...process.env,
OPENCLAW_FAKE_GIT_RESPONSES: JSON.stringify(gitResponses),
PATH: [gitBinDir, path.dirname(process.execPath)].join(path.delimiter),
itWithPosixLinkedWorktreeFixture(
"finds a Crabbox checkout next to the Git common dir in linked worktrees",
() => {
const fakeWorkspaceParent = mkdtempSync(path.join(tmpdir(), "openclaw-linked-worktree-"));
tempDirs.push(fakeWorkspaceParent);
const gitCommonDir = path.join(fakeWorkspaceParent, "openclaw", ".git");
const crabboxBinDir = path.join(fakeWorkspaceParent, "crabbox", "bin");
mkdirSync(gitCommonDir, { recursive: true });
writeFakeCrabbox(crabboxBinDir, "provider: aws\n");
const gitResponses = {
["rev-parse\u0000--git-common-dir"]: { stdout: `${gitCommonDir}\n` },
};
const gitBinDir = makeFakeGit(gitResponses);
const result = spawnSync(
process.execPath,
["scripts/crabbox-wrapper.mjs", "run", "--provider", "aws", "--", "echo ok"],
{
cwd: repoRoot,
encoding: "utf8",
env: {
...process.env,
OPENCLAW_FAKE_GIT_RESPONSES: JSON.stringify(gitResponses),
PATH: [gitBinDir, path.dirname(process.execPath)].join(path.delimiter),
},
},
},
);
);
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toContain("aws");
});
expect(result.status).toBe(0);
expect(parseFakeCrabboxOutput(result).args).toContain("aws");
},
);
it("accepts advertised providers from wrapped Crabbox help", () => {
const result = runWrapper(
@@ -355,18 +364,14 @@ describe("scripts/crabbox-wrapper", () => {
}
it("falls back to normal sync decisions when git is missing from PATH", () => {
const binDir = makeFakeCrabbox(
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
);
const result = spawnSync(
process.execPath,
["scripts/crabbox-wrapper.mjs", "run", "--provider", "aws", "--", "echo ok"],
["run", "--provider", "aws", "--", "echo ok"],
{
cwd: repoRoot,
encoding: "utf8",
env: {
...process.env,
PATH: [binDir, path.dirname(process.execPath)].join(path.delimiter),
gitResponses: {
["rev-parse\u0000--git-common-dir"]: { status: 1 },
["config\u0000--bool\u0000core.sparseCheckout"]: { status: 1 },
["sparse-checkout\u0000list"]: { status: 1 },
},
},
);
@@ -493,6 +498,35 @@ describe("scripts/crabbox-wrapper", () => {
expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
});
it("uses a temporary full checkout when clean sparse AWS syncs reuse a lease", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
[
"run",
"--provider",
"aws",
"--target",
"windows",
"--id",
"cbx_existing",
"--",
"corepack",
"pnpm",
"build",
],
{
gitResponses: {
["config\u0000--bool\u0000core.sparseCheckout"]: { stdout: "true\n" },
["status\u0000--porcelain=v1"]: { stdout: "" },
},
},
);
expect(result.status).toBe(0);
expect(result.stderr).toContain("syncing from temporary full checkout");
expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
});
it("bootstraps Git metadata for sparse changed gates on remote raw syncs", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
@@ -626,7 +660,7 @@ describe("scripts/crabbox-wrapper", () => {
expect(parseFakeCrabboxOutput(result).cwd).toBe(repoRoot);
});
it("keeps existing AWS leases on the original sparse checkout", () => {
it("uses a temporary full checkout when existing AWS leases sync clean sparse worktrees", () => {
const result = runWrapper(
"provider: hetzner, aws, local-container, blacksmith-testbox, or cloudflare\n",
["run", "--provider", "aws", "--id", "cbx_existing", "--", "echo ok"],
@@ -639,8 +673,8 @@ describe("scripts/crabbox-wrapper", () => {
);
expect(result.status).toBe(0);
expect(result.stderr).not.toContain("syncing from temporary full checkout");
expect(parseFakeCrabboxOutput(result).cwd).toBe(repoRoot);
expect(result.stderr).toContain("syncing from temporary full checkout");
expect(parseFakeCrabboxOutput(result).cwd).toContain("openclaw-crabbox-sync-");
});
it("uses a temporary full checkout when clean sparse branches differ from the Blacksmith ref", () => {