mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:40:44 +00:00
Run doctor after global gateway updates
This commit is contained in:
committed by
Peter Steinberger
parent
74f9a2aedd
commit
fa533101d8
@@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Gateway/update: run `doctor --non-interactive --fix` after Control UI global package updates before reporting success, so legacy config is migrated before the gateway restart. Thanks @stevenchouai.
|
||||
- Active Memory: apply `setupGraceTimeoutMs` to the embedded recall runner as well as the outer prompt-build watchdog, so very-cold first recalls keep the configured setup grace end-to-end. (#74480) Thanks @volcano303.
|
||||
- CLI/config: keep JSON dry-run patches validating touched channel configuration against bundled channel schemas even when the patch only contains SecretRef objects.
|
||||
- Plugins/tools: keep disabled bundled tool plugins out of explicit runtime allowlist ownership and fall back from loaded-but-empty channel registries to tool-bearing plugin registries, so Active Memory can use bundled `memory-core` search/get tools even when `memory-lancedb` is disabled. Fixes #76603. Thanks @jwong-art.
|
||||
|
||||
@@ -265,6 +265,14 @@ describe("runGatewayUpdate", () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function writeGatewayEntrypoint(pkgRoot: string) {
|
||||
const entrypoint = path.join(pkgRoot, "dist", "index.js");
|
||||
await fs.mkdir(path.dirname(entrypoint), { recursive: true });
|
||||
await fs.writeFile(entrypoint, "export {};\n", "utf-8");
|
||||
await writePackageDistInventory(pkgRoot);
|
||||
return entrypoint;
|
||||
}
|
||||
|
||||
async function createGlobalPackageFixture(rootDir: string) {
|
||||
const nodeModules = path.join(rootDir, "node_modules");
|
||||
const pkgRoot = path.join(nodeModules, "openclaw");
|
||||
@@ -1456,6 +1464,87 @@ describe("runGatewayUpdate", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("runs doctor after global npm updates before reporting success", async () => {
|
||||
const nodeModules = path.join(tempDir, "node_modules");
|
||||
const pkgRoot = path.join(nodeModules, "openclaw");
|
||||
await seedGlobalPackageRoot(pkgRoot);
|
||||
|
||||
let doctorEnv: NodeJS.ProcessEnv | undefined;
|
||||
const { calls, runCommand } = createGlobalInstallHarness({
|
||||
pkgRoot,
|
||||
npmRootOutput: nodeModules,
|
||||
installCommand: "npm i -g openclaw@latest --no-fund --no-audit --loglevel=error",
|
||||
onInstall: async () => {
|
||||
await writeGlobalPackageVersion(pkgRoot);
|
||||
await writeGatewayEntrypoint(pkgRoot);
|
||||
},
|
||||
});
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
const doctorCommand = `${doctorNodePath} ${path.join(
|
||||
pkgRoot,
|
||||
"dist",
|
||||
"index.js",
|
||||
)} doctor --non-interactive --fix`;
|
||||
const runCommandWithDoctor = async (argv: string[], options?: { env?: NodeJS.ProcessEnv }) => {
|
||||
const key = argv.join(" ");
|
||||
if (key === doctorCommand) {
|
||||
calls.push(key);
|
||||
doctorEnv = options?.env;
|
||||
return { stdout: "doctor repaired config", stderr: "", code: 0 };
|
||||
}
|
||||
return runCommand(argv, options);
|
||||
};
|
||||
|
||||
const result = await runWithCommand(runCommandWithDoctor, { cwd: pkgRoot });
|
||||
|
||||
expect(result.status).toBe("ok");
|
||||
expect(calls).toContain(doctorCommand);
|
||||
expect(result.steps.map((step) => step.name)).toContain("openclaw doctor");
|
||||
expect(doctorEnv?.OPENCLAW_UPDATE_IN_PROGRESS).toBe("1");
|
||||
expect(doctorEnv?.OPENCLAW_UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE).toBe("1");
|
||||
});
|
||||
|
||||
it("fails global npm updates when post-update doctor fails", async () => {
|
||||
const nodeModules = path.join(tempDir, "node_modules");
|
||||
const pkgRoot = path.join(nodeModules, "openclaw");
|
||||
await seedGlobalPackageRoot(pkgRoot);
|
||||
|
||||
const { calls, runCommand } = createGlobalInstallHarness({
|
||||
pkgRoot,
|
||||
npmRootOutput: nodeModules,
|
||||
installCommand: "npm i -g openclaw@latest --no-fund --no-audit --loglevel=error",
|
||||
onInstall: async () => {
|
||||
await writeGlobalPackageVersion(pkgRoot);
|
||||
await writeGatewayEntrypoint(pkgRoot);
|
||||
},
|
||||
});
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
const doctorCommand = `${doctorNodePath} ${path.join(
|
||||
pkgRoot,
|
||||
"dist",
|
||||
"index.js",
|
||||
)} doctor --non-interactive --fix`;
|
||||
const runCommandWithDoctor = async (argv: string[], options?: { env?: NodeJS.ProcessEnv }) => {
|
||||
const key = argv.join(" ");
|
||||
if (key === doctorCommand) {
|
||||
calls.push(key);
|
||||
return { stdout: "", stderr: "doctor refused migration", code: 1 };
|
||||
}
|
||||
return runCommand(argv, options);
|
||||
};
|
||||
|
||||
const result = await runWithCommand(runCommandWithDoctor, { cwd: pkgRoot });
|
||||
|
||||
expect(result.status).toBe("error");
|
||||
expect(result.reason).toBe("doctor-failed");
|
||||
expect(calls).toContain(doctorCommand);
|
||||
expect(result.steps.at(-1)).toMatchObject({
|
||||
name: "openclaw doctor",
|
||||
exitCode: 1,
|
||||
stderrTail: "doctor refused migration",
|
||||
});
|
||||
});
|
||||
|
||||
it("falls back to global npm update when git is missing from PATH", async () => {
|
||||
const { nodeModules, pkgRoot } = await createGlobalPackageFixture(tempDir);
|
||||
const { calls, runCommand } = createGlobalInstallHarness({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { resolveGatewayInstallEntrypoint } from "../daemon/gateway-entrypoint.js";
|
||||
import { type CommandOptions, runCommandWithTimeout } from "../process/exec.js";
|
||||
import {
|
||||
resolveControlUiDistIndexHealth,
|
||||
@@ -1443,6 +1444,27 @@ export async function runGatewayUpdate(opts: UpdateRunnerOptions = {}): Promise<
|
||||
stepIndex: 0,
|
||||
totalSteps: 1,
|
||||
}),
|
||||
postVerifyStep: async (verifiedPackageRoot) => {
|
||||
const doctorEntry = await resolveGatewayInstallEntrypoint(verifiedPackageRoot);
|
||||
if (!doctorEntry) {
|
||||
return null;
|
||||
}
|
||||
const doctorNodePath = await resolveStableNodePath(process.execPath);
|
||||
return await runStep({
|
||||
runCommand,
|
||||
name: "openclaw doctor",
|
||||
argv: [doctorNodePath, doctorEntry, "doctor", "--non-interactive", "--fix"],
|
||||
cwd: verifiedPackageRoot,
|
||||
timeoutMs,
|
||||
env: {
|
||||
OPENCLAW_UPDATE_IN_PROGRESS: "1",
|
||||
[UPDATE_PARENT_SUPPORTS_DOCTOR_CONFIG_WRITE_ENV]: "1",
|
||||
},
|
||||
progress,
|
||||
stepIndex: 0,
|
||||
totalSteps: 1,
|
||||
});
|
||||
},
|
||||
});
|
||||
return {
|
||||
status: packageUpdate.failedStep ? "error" : "ok",
|
||||
|
||||
Reference in New Issue
Block a user