mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-29 20:48:44 +00:00
fix(cli): route plugin packaging recovery hints
Route invalid-config recovery output for source-only installed plugin packages to plugin packaging guidance instead of openclaw doctor --fix.
Validated with focused config/CLI/gateway/plugin tests, autoreview, Crabbox/Testbox E2E tbx_01ksgr80tnvvc13kv6t126yv78, and green PR CI on 3b3ce73d0f.
Thanks @brokemac79.
This commit is contained in:
@@ -19,6 +19,14 @@ const invalidConfigRecoveryHint = [
|
||||
'Run "openclaw doctor --fix" to repair, then retry.',
|
||||
"If startup is still blocked, inspect the adjacent .bak backup before restoring it manually.",
|
||||
].join("\n");
|
||||
const pluginPackagingRecoveryHints = [
|
||||
"This is a plugin packaging issue, not a local config problem.",
|
||||
"Update or reinstall the plugin after the publisher ships compiled JavaScript, or disable/uninstall the plugin until then.",
|
||||
];
|
||||
const pluginPackagingHintItems = pluginPackagingRecoveryHints.map((text) => ({
|
||||
kind: "generic",
|
||||
text,
|
||||
}));
|
||||
|
||||
function expectLatestRuntimeJson(payload: unknown) {
|
||||
const calls = defaultRuntime.writeJson.mock.calls;
|
||||
@@ -47,6 +55,8 @@ function setConfigSnapshot(params: {
|
||||
exists: boolean;
|
||||
valid: boolean;
|
||||
issues?: Array<{ path: string; message: string }>;
|
||||
warnings?: Array<{ path: string; message: string }>;
|
||||
legacyIssues?: Array<{ path: string; message: string }>;
|
||||
lastTouchedVersion?: string;
|
||||
}) {
|
||||
const config = params.lastTouchedVersion
|
||||
@@ -58,6 +68,28 @@ function setConfigSnapshot(params: {
|
||||
config,
|
||||
sourceConfig: config,
|
||||
issues: params.issues ?? [],
|
||||
warnings: params.warnings ?? [],
|
||||
legacyIssues: params.legacyIssues ?? [],
|
||||
});
|
||||
}
|
||||
|
||||
function setPluginPackagingInvalidSnapshot() {
|
||||
setConfigSnapshot({
|
||||
exists: true,
|
||||
valid: false,
|
||||
issues: [
|
||||
{
|
||||
path: "plugins.slots.memory",
|
||||
message: "plugin not found: source-only-pack",
|
||||
},
|
||||
],
|
||||
warnings: [
|
||||
{
|
||||
path: "plugins",
|
||||
message:
|
||||
"plugin source-only-pack: installed plugin package requires compiled runtime output for TypeScript entry index.ts: expected ./dist/index.js. This is a plugin packaging issue, not a local config problem.",
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -107,6 +139,22 @@ describe("runServiceRestart config pre-flight (#35862)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("points restart at plugin packaging recovery for packaging-only invalid config", async () => {
|
||||
setPluginPackagingInvalidSnapshot();
|
||||
|
||||
await expect(runServiceRestart(createServiceRunArgs())).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(service.restart).not.toHaveBeenCalled();
|
||||
expectLatestRuntimeJson({
|
||||
action: "restart",
|
||||
ok: false,
|
||||
error: "Gateway restart blocked: plugins.slots.memory: plugin not found: source-only-pack",
|
||||
hints: pluginPackagingRecoveryHints,
|
||||
hintItems: pluginPackagingHintItems,
|
||||
warnings: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks restart from an older binary when config was written by a newer one", async () => {
|
||||
setConfigSnapshot({ exists: true, valid: true, lastTouchedVersion: "9999.1.1" });
|
||||
|
||||
@@ -185,6 +233,22 @@ describe("runServiceStart config pre-flight (#35862)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("points start at plugin packaging recovery for packaging-only invalid config", async () => {
|
||||
setPluginPackagingInvalidSnapshot();
|
||||
|
||||
await expect(runServiceStart(createServiceRunArgs())).rejects.toThrow("__exit__:1");
|
||||
|
||||
expect(service.restart).not.toHaveBeenCalled();
|
||||
expectLatestRuntimeJson({
|
||||
action: "start",
|
||||
ok: false,
|
||||
error: "Gateway start blocked: plugins.slots.memory: plugin not found: source-only-pack",
|
||||
hints: pluginPackagingRecoveryHints,
|
||||
hintItems: pluginPackagingHintItems,
|
||||
warnings: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("aborts before not-loaded start recovery when config is invalid", async () => {
|
||||
const onNotLoaded = vi.fn(async () => ({
|
||||
result: "started" as const,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { readBestEffortConfig, readConfigFileSnapshot } from "../../config/confi
|
||||
import { resolveFutureConfigActionBlock } from "../../config/future-version-guard.js";
|
||||
import { formatConfigIssueLines } from "../../config/issue-format.js";
|
||||
import { resolveIsNixMode } from "../../config/paths.js";
|
||||
import { isPluginPackagingRuntimeOutputInvalidConfigSnapshot } from "../../config/recovery-policy.js";
|
||||
import { checkTokenDrift } from "../../daemon/service-audit.js";
|
||||
import type { GatewayServiceRestartResult } from "../../daemon/service-types.js";
|
||||
import type { GatewayServiceStartRepairIssue, GatewayServiceState } from "../../daemon/service.js";
|
||||
@@ -19,7 +20,10 @@ import {
|
||||
import { isWSL } from "../../infra/wsl.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { formatCliCommand } from "../command-format.js";
|
||||
import { formatInvalidConfigRecoveryHint } from "../config-recovery-hints.js";
|
||||
import {
|
||||
formatInvalidConfigRecoveryHint,
|
||||
formatPluginPackagingRuntimeOutputRecoveryHint,
|
||||
} from "../config-recovery-hints.js";
|
||||
import { resolveGatewayTokenForDriftCheck } from "./gateway-token-drift.js";
|
||||
import {
|
||||
buildDaemonServiceSnapshot,
|
||||
@@ -139,6 +143,10 @@ type ConfigActionPreflightFailure = {
|
||||
hints?: string[];
|
||||
};
|
||||
|
||||
function formatPluginPackagingRuntimeOutputRecoveryHints(): string[] {
|
||||
return formatPluginPackagingRuntimeOutputRecoveryHint().split("\n");
|
||||
}
|
||||
|
||||
async function getConfigActionPreflightFailure(
|
||||
action: string,
|
||||
): Promise<ConfigActionPreflightFailure | null> {
|
||||
@@ -146,11 +154,15 @@ async function getConfigActionPreflightFailure(
|
||||
try {
|
||||
snapshot = await readConfigFileSnapshot();
|
||||
if (snapshot.exists && !snapshot.valid) {
|
||||
const message =
|
||||
snapshot.issues.length > 0
|
||||
? formatConfigIssueLines(snapshot.issues, "", { normalizeRoot: true }).join("\n")
|
||||
: "Unknown validation issue.";
|
||||
return {
|
||||
message:
|
||||
snapshot.issues.length > 0
|
||||
? formatConfigIssueLines(snapshot.issues, "", { normalizeRoot: true }).join("\n")
|
||||
: "Unknown validation issue.",
|
||||
message,
|
||||
...(isPluginPackagingRuntimeOutputInvalidConfigSnapshot(snapshot)
|
||||
? { hints: formatPluginPackagingRuntimeOutputRecoveryHints() }
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user