mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 10:30:44 +00:00
fix(doctor): keep plugin runtime deps repair explicit (#75603)
* fix(doctor): keep plugin runtime deps repair explicit * fix(doctor): keep plugin runtime deps repair explicit * fix(doctor): keep plugin runtime deps repair explicit --------- Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
This commit is contained in:
@@ -53,6 +53,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/agent: reject strict `openclaw agent --deliver` requests with missing delivery targets before starting the agent run, so users do not wait for a completed turn that cannot send anywhere. Thanks @vincentkoc.
|
||||
- Setup/import: honor non-interactive `--import-from` onboarding flags by running the migration import path instead of silently completing normal setup without importing anything. Thanks @vincentkoc.
|
||||
- Discord/voice: run voice-channel turns under a voice-output policy that hides the agent `tts` tool and asks for spoken reply text, so `/vc join` sessions synthesize and play agent replies instead of ending with `NO_REPLY`. Fixes #61536. Thanks @aounakram.
|
||||
- Doctor/plugins: keep plain `doctor --non-interactive` from installing bundled plugin runtime dependencies, so headless health checks report missing deps while `doctor --fix` remains the explicit repair path. Thanks @vincentkoc.
|
||||
- Plugins/runtime-deps: include packaged OpenClaw identity in bundled plugin loader cache keys, so same-path package upgrades stop reusing stale versioned runtime-deps mirrors. Fixes #75045. Thanks @sahilsatralkar.
|
||||
- Plugin SDK: restore reply-prefix and reply-pipeline helpers on the deprecated root/compat SDK surface so external plugins still using `openclaw/plugin-sdk` do not fail message dispatch after update. Fixes #75171. Thanks @zhangxiliang.
|
||||
- Plugins/runtime-deps: prune inactive same-package versioned runtime-deps roots after bundled dependency repair, so upgrades do not leave old `openclaw-<version>-<hash>` package caches behind after doctor runs. Thanks @vincentkoc.
|
||||
|
||||
@@ -121,27 +121,44 @@ function expectNoLegacyRuntimeDepsManifest(installRoot: string): void {
|
||||
expect(fs.existsSync(path.join(installRoot, ".openclaw-runtime-deps.json"))).toBe(false);
|
||||
}
|
||||
|
||||
function createNonInteractivePrompter(
|
||||
options: { updateInProgress?: boolean } = {},
|
||||
function createNonInteractiveDoctorPrompter(
|
||||
options: {
|
||||
repair?: boolean;
|
||||
updateInProgress?: boolean;
|
||||
confirmAutoFix?: DoctorPrompter["confirmAutoFix"];
|
||||
} = {},
|
||||
): DoctorPrompter {
|
||||
const shouldRepair = options.repair ?? false;
|
||||
return {
|
||||
shouldRepair: false,
|
||||
shouldRepair,
|
||||
shouldForce: false,
|
||||
repairMode: {
|
||||
shouldRepair: false,
|
||||
shouldRepair,
|
||||
shouldForce: false,
|
||||
nonInteractive: true,
|
||||
canPrompt: false,
|
||||
updateInProgress: options.updateInProgress ?? false,
|
||||
},
|
||||
confirm: async () => false,
|
||||
confirmAutoFix: async () => false,
|
||||
confirmAutoFix: options.confirmAutoFix ?? (async () => false),
|
||||
confirmAggressiveAutoFix: async () => false,
|
||||
confirmRuntimeRepair: async () => false,
|
||||
select: async (_params: unknown, fallback: unknown) => fallback,
|
||||
} as DoctorPrompter;
|
||||
}
|
||||
|
||||
function createPlainNonInteractivePrompter(
|
||||
options: { confirmAutoFix?: DoctorPrompter["confirmAutoFix"] } = {},
|
||||
): DoctorPrompter {
|
||||
return createNonInteractiveDoctorPrompter(options);
|
||||
}
|
||||
|
||||
function createNonInteractiveRepairPrompter(
|
||||
options: { updateInProgress?: boolean } = {},
|
||||
): DoctorPrompter {
|
||||
return createNonInteractiveDoctorPrompter({ ...options, repair: true });
|
||||
}
|
||||
|
||||
function createRuntime(options: { logs?: string[]; errors?: string[] } = {}): RuntimeEnv {
|
||||
return {
|
||||
log: (message: unknown) => {
|
||||
@@ -487,7 +504,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -501,6 +518,36 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
expect(installed).toEqual([]);
|
||||
});
|
||||
|
||||
it("does not repair missing runtime deps during plain non-interactive doctor", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
|
||||
writeJson(path.join(root, "package.json"), { name: "openclaw" });
|
||||
writeBundledProviderPlugin(root, "bedrock", ["bedrock"], {
|
||||
"bedrock-only": "1.0.0",
|
||||
});
|
||||
const installed = createInstalledRuntimeDeps();
|
||||
const confirmAutoFix = vi.fn(async () => true);
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createPlainNonInteractivePrompter({ confirmAutoFix }),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: {
|
||||
enabled: true,
|
||||
allow: ["bedrock"],
|
||||
entries: { bedrock: { enabled: true } },
|
||||
},
|
||||
},
|
||||
installDeps: (params) => {
|
||||
installed.push(params);
|
||||
materializeRuntimeDeps(params);
|
||||
},
|
||||
});
|
||||
|
||||
expect(installed).toEqual([]);
|
||||
expect(confirmAutoFix).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("repairs explicitly enabled provider deps", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
|
||||
writeJson(path.join(root, "package.json"), { name: "openclaw" });
|
||||
@@ -519,7 +566,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: {
|
||||
@@ -553,7 +600,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -588,7 +635,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -624,7 +671,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("repairs missing deps during non-interactive doctor", async () => {
|
||||
it("repairs missing deps during doctor --fix --non-interactive", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
|
||||
writeJson(path.join(root, "package.json"), { name: "openclaw" });
|
||||
writeBundledChannelPlugin(root, "telegram", { grammy: "1.37.0" });
|
||||
@@ -632,7 +679,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -659,7 +706,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
expectNoLegacyRuntimeDepsManifest(installRoot);
|
||||
});
|
||||
|
||||
it("repairs a previous incomplete runtime deps install during non-interactive doctor", async () => {
|
||||
it("repairs a previous incomplete runtime deps install during doctor --fix --non-interactive", async () => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-doctor-bundled-"));
|
||||
writeJson(path.join(root, "package.json"), { name: "openclaw" });
|
||||
writeBundledChannelPlugin(root, "telegram", { grammy: "1.37.0" });
|
||||
@@ -672,7 +719,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -701,7 +748,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime({ logs }),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -728,7 +775,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
const repair = maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime({ logs }),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -760,7 +807,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
const repair = maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: { error: () => {}, log: () => {} } as never,
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -790,7 +837,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -820,7 +867,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: {
|
||||
@@ -851,7 +898,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
await expect(
|
||||
maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime({ errors }),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
config: {
|
||||
plugins: { enabled: true },
|
||||
@@ -876,7 +923,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter({ updateInProgress: true }),
|
||||
prompter: createNonInteractiveRepairPrompter({ updateInProgress: true }),
|
||||
packageRoot: root,
|
||||
includeConfiguredChannels: true,
|
||||
config: {
|
||||
@@ -909,7 +956,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
env,
|
||||
packageRoot: root,
|
||||
config: {
|
||||
@@ -963,7 +1010,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
env,
|
||||
packageRoot: root,
|
||||
config: {
|
||||
@@ -999,7 +1046,7 @@ describe("doctor bundled plugin runtime deps", () => {
|
||||
|
||||
await maybeRepairBundledPluginRuntimeDeps({
|
||||
runtime: createRuntime(),
|
||||
prompter: createNonInteractivePrompter(),
|
||||
prompter: createNonInteractiveRepairPrompter(),
|
||||
packageRoot: root,
|
||||
includeConfiguredChannels: true,
|
||||
config: {
|
||||
|
||||
@@ -92,11 +92,11 @@ export async function maybeRepairBundledPluginRuntimeDeps(params: {
|
||||
|
||||
const shouldRepair =
|
||||
params.prompter.shouldRepair ||
|
||||
params.prompter.repairMode.nonInteractive ||
|
||||
(await params.prompter.confirmAutoFix({
|
||||
message: "Install missing bundled plugin runtime deps now?",
|
||||
initialValue: true,
|
||||
}));
|
||||
(!params.prompter.repairMode.nonInteractive &&
|
||||
(await params.prompter.confirmAutoFix({
|
||||
message: "Install missing bundled plugin runtime deps now?",
|
||||
initialValue: true,
|
||||
})));
|
||||
if (!shouldRepair) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user