fix(scripts): keep watch proof asset copies out of idle window

This commit is contained in:
Vincent Koc
2026-06-02 13:54:42 +02:00
parent cd0af35e5c
commit 4115f0c82f
5 changed files with 99 additions and 7 deletions
+1
View File
@@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai
- Gateway: return the post-expiration pending-work revision from node drains so reconnecting nodes do not observe stale queue revisions after expired items are pruned.
- Release/CI/E2E: keep temporary full-sync checkouts alive while slow Crabbox leases boot, so sparse worktree runs do not lose their sync source before file-list generation.
- Release/CI/E2E: normalize inherited Linux `C.UTF-8` locale settings before raw AWS macOS Crabbox bootstrap commands, avoiding macOS locale warnings during package-manager hydration.
- Release/CI/E2E: keep gateway watch regression checks from copying large static plugin assets inside the measured idle window.
- Agents/providers: keep streaming tool-call argument parsing record-shaped when providers emit valid non-object JSON such as `null` or arrays.
- Release/CI/E2E: reset incremental log readers when watched log files rotate without shrinking, so same-size replacements do not hide new readiness or RPC lines.
- Talk: preserve explicit `null` payloads on controller-created turn and output-audio lifecycle events.
@@ -39,6 +39,7 @@ const WATCH_GATEWAY_SKIP_ENV = {
OPENCLAW_SKIP_CHANNELS: "1",
OPENCLAW_SKIP_CRON: "1",
OPENCLAW_SKIP_GMAIL_WATCHER: "1",
OPENCLAW_RUNTIME_POSTBUILD_STATIC_ASSETS: "0",
OPENCLAW_TEST_MINIMAL_GATEWAY: "1",
NODE_ENV: "test",
};
+13 -1
View File
@@ -473,10 +473,22 @@ const listRequiredOpenClawExtensionAliasOutputs = (deps) => {
};
const listRequiredStaticExtensionAssetOutputs = (deps) => {
if (deps.env.OPENCLAW_RUNTIME_POSTBUILD_STATIC_ASSETS === "0") {
return [];
}
const distRoot = resolveRuntimePostBuildDistRoot(deps);
const runtimeRoot = resolveRuntimePostBuildRuntimeRoot(deps);
const runtimeExtensionsRoot = path.join(runtimeRoot, "extensions");
const hasRuntimeOverlay = deps.fs.existsSync(runtimeExtensionsRoot);
return discoverStaticExtensionAssets({ rootDir: deps.cwd, fs: deps.fs })
.filter((asset) => deps.fs.existsSync(path.join(deps.cwd, asset.src)))
.map((asset) => path.join(distRoot, normalizePath(asset.dest).replace(/^dist\//u, "")))
.flatMap((asset) => {
const relativeOutput = normalizePath(asset.dest).replace(/^dist\//u, "");
return [
path.join(distRoot, relativeOutput),
...(hasRuntimeOverlay ? [path.join(runtimeRoot, relativeOutput)] : []),
];
})
.toSorted((left, right) => left.localeCompare(right));
};
+76
View File
@@ -2307,6 +2307,82 @@ describe("run-node script", () => {
});
});
it("reports missing static runtime overlay asset outputs when runtime stamps match HEAD", async () => {
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
await setupTrackedProject(tmp, {
files: {
[ROOT_SRC]: "export const value = 1;\n",
[DIFFS_PACKAGE]:
'{"openclaw":{"build":{"staticAssets":[{"source":"./assets/viewer-runtime.js","output":"assets/viewer-runtime.js"}]}}}\n',
[DIFFS_VIEWER_RUNTIME_SOURCE]: "export {};\n",
[DIST_DIFFS_VIEWER_RUNTIME]: "export {};\n",
[DIST_RUNTIME_DIFFS_VIEWER_RUNTIME]: "export {};\n",
[RUNTIME_POSTBUILD_STAMP]: '{"head":"abc123"}\n',
},
buildPaths: [
ROOT_SRC,
DIFFS_PACKAGE,
DIFFS_VIEWER_RUNTIME_SOURCE,
DIST_DIFFS_VIEWER_RUNTIME,
DIST_RUNTIME_DIFFS_VIEWER_RUNTIME,
DIST_ENTRY,
BUILD_STAMP,
RUNTIME_POSTBUILD_STAMP,
],
});
await fs.rm(resolvePath(tmp, DIST_RUNTIME_DIFFS_VIEWER_RUNTIME));
const requirement = resolveRuntimePostBuildRequirement(
createBuildRequirementDeps(tmp, {
gitHead: "abc123\n",
gitStatus: "",
}),
);
expect(requirement).toEqual({
shouldSync: true,
reason: "missing_runtime_postbuild_output",
});
});
});
it("does not require static asset outputs when runtime static assets are disabled", async () => {
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
await setupTrackedProject(tmp, {
files: {
[ROOT_SRC]: "export const value = 1;\n",
[DIFFS_PACKAGE]:
'{"openclaw":{"build":{"staticAssets":[{"source":"./assets/viewer-runtime.js","output":"assets/viewer-runtime.js"}]}}}\n',
[DIFFS_VIEWER_RUNTIME_SOURCE]: "export {};\n",
[DIST_RUNTIME_EXTENSION_PACKAGE]: '{"openclaw":{"extensions":["./index.js"]}}\n',
[RUNTIME_POSTBUILD_STAMP]: '{"head":"abc123"}\n',
},
buildPaths: [
ROOT_SRC,
DIFFS_PACKAGE,
DIFFS_VIEWER_RUNTIME_SOURCE,
DIST_RUNTIME_EXTENSION_PACKAGE,
DIST_ENTRY,
BUILD_STAMP,
RUNTIME_POSTBUILD_STAMP,
],
});
const requirement = resolveRuntimePostBuildRequirement(
createBuildRequirementDeps(tmp, {
env: { OPENCLAW_RUNTIME_POSTBUILD_STATIC_ASSETS: "0" },
gitHead: "abc123\n",
gitStatus: "",
}),
);
expect(requirement).toEqual({
shouldSync: false,
reason: "clean",
});
});
});
it("does not require static asset outputs when the declared source is absent", async () => {
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
await setupTrackedProject(tmp, {
@@ -150,6 +150,12 @@ describe("check-gateway-watch-regression", () => {
}),
);
const waitForGatewayReady = vi.fn(async () => false);
const spawn = vi.fn(() => {
process.nextTick(() => {
child.emit("error", new Error("spawn failed"));
});
return child;
});
try {
const result = await runTimedWatch(
@@ -162,12 +168,7 @@ describe("check-gateway-watch-regression", () => {
outputDir,
{
allocateLoopbackPort: async () => 19042,
spawn: () => {
process.nextTick(() => {
child.emit("error", new Error("spawn failed"));
});
return child;
},
spawn,
sleep,
stopTimedWatchChild: stopChild,
waitForGatewayReady,
@@ -180,6 +181,7 @@ describe("check-gateway-watch-regression", () => {
expect(result.spawnError).toBe("spawn failed");
expect(fs.existsSync(isolatedHomeDir)).toBe(false);
expect(fs.existsSync(path.join(outputDir, "watch.home.txt"))).toBe(true);
expect(spawn.mock.calls[0]?.[2]?.env?.OPENCLAW_RUNTIME_POSTBUILD_STATIC_ASSETS).toBe("0");
expect(waitForGatewayReady).not.toHaveBeenCalled();
expect(stopChild).not.toHaveBeenCalled();
} finally {