fix(gateway): sync dirty plugin metadata in watch mode

This commit is contained in:
Peter Steinberger
2026-05-01 10:58:28 +01:00
parent 90554ea048
commit 6e3fd67084
4 changed files with 67 additions and 3 deletions

View File

@@ -17,6 +17,17 @@ const manifest = JSON.parse(
}>;
};
const packageJson = JSON.parse(
readFileSync(new URL("./package.json", import.meta.url), "utf8"),
) as {
dependencies?: Record<string, string>;
openclaw?: {
bundle?: {
stageRuntimeDependencies?: boolean;
};
};
};
function manifestComparableWizardFields(choice: {
choiceId?: string;
choiceLabel?: string;
@@ -53,6 +64,12 @@ function providerWizardByKey() {
}
describe("OpenAI plugin manifest", () => {
it("opts into staging bundled runtime dependencies", () => {
expect(packageJson.dependencies?.["@mariozechner/pi-ai"]).toBe("0.70.6");
expect(packageJson.dependencies?.ws).toBe("^8.20.0");
expect(packageJson.openclaw?.bundle?.stageRuntimeDependencies).toBe(true);
});
it("keeps removed Codex CLI import auth choice as a deprecated browser-login alias", () => {
const codexBrowserLogin = manifest.providerAuthChoices?.find(
(choice) => choice.choiceId === "openai-codex",

View File

@@ -12,6 +12,9 @@
"@openclaw/plugin-sdk": "workspace:*"
},
"openclaw": {
"bundle": {
"stageRuntimeDependencies": true
},
"extensions": [
"./index.ts"
]

View File

@@ -798,7 +798,10 @@ const writeBuildStamp = (deps) => {
}
};
const shouldSkipCleanWatchRuntimeSync = (deps) => deps.env.OPENCLAW_WATCH_MODE === "1";
const shouldSkipWatchRuntimeSync = (deps, requirement) =>
deps.env.OPENCLAW_WATCH_MODE === "1" &&
requirement.reason === "missing_runtime_postbuild_stamp" &&
hasDirtyRuntimePostBuildInputs(deps) !== true;
const isGatewayClientCommand = (args) =>
args[0] === "gateway" && (args[1] === "call" || args[1] === "status");
@@ -885,9 +888,12 @@ export async function runNodeMain(params = {}) {
return await closeRunNodeOutputTee(deps, exitCode);
}
if (!buildRequirement.shouldBuild) {
if (!useExistingGatewayClientDist && !shouldSkipCleanWatchRuntimeSync(deps)) {
if (!useExistingGatewayClientDist) {
const runtimePostBuildRequirement = resolveRuntimePostBuildRequirement(deps);
if (runtimePostBuildRequirement.shouldSync) {
if (
runtimePostBuildRequirement.shouldSync &&
!shouldSkipWatchRuntimeSync(deps, runtimePostBuildRequirement)
) {
const synced = await withRunNodeBuildLock(deps, async () => {
const lockedRuntimePostBuildRequirement = resolveRuntimePostBuildRequirement(deps);
if (!lockedRuntimePostBuildRequirement.shouldSync) {

View File

@@ -697,6 +697,44 @@ describe("run-node script", () => {
});
});
it("restages dirty runtime metadata in watch mode when dist is already current", async () => {
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
await setupTrackedProject(tmp, {
files: {
[ROOT_SRC]: "export const value = 1;\n",
[EXTENSION_PACKAGE]: '{"openclaw":{"bundle":{"stageRuntimeDependencies":true}}}\n',
[RUNTIME_POSTBUILD_STAMP]: '{"head":"abc123"}\n',
},
buildPaths: [
ROOT_SRC,
EXTENSION_PACKAGE,
ROOT_TSCONFIG,
ROOT_PACKAGE,
DIST_ENTRY,
BUILD_STAMP,
RUNTIME_POSTBUILD_STAMP,
],
});
const runRuntimePostBuild = vi.fn();
const { spawnCalls, spawn, spawnSync } = createSpawnRecorder({
gitHead: "abc123\n",
gitStatus: ` M ${EXTENSION_PACKAGE}\n`,
});
const exitCode = await runStatusCommand({
tmp,
spawn,
spawnSync,
env: { OPENCLAW_WATCH_MODE: "1" },
runRuntimePostBuild,
});
expect(exitCode).toBe(0);
expect(spawnCalls).toEqual([statusCommandSpawn()]);
expect(runRuntimePostBuild).toHaveBeenCalledOnce();
});
});
it("runs QA parity report from source without rebuilding private QA dist", async () => {
await withTempDir({ prefix: "openclaw-run-node-" }, async (tmp) => {
await setupTrackedProject(tmp, {