





















@@ -19,6 +19,14 @@ const invalidConfigRecoveryHint = [
1919'Run "openclaw doctor --fix" to repair, then retry.',
2020"If startup is still blocked, inspect the adjacent .bak backup before restoring it manually.",
2121].join("\n");
22+const pluginPackagingRecoveryHints = [
23+"This is a plugin packaging issue, not a local config problem.",
24+"Update or reinstall the plugin after the publisher ships compiled JavaScript, or disable/uninstall the plugin until then.",
25+];
26+const pluginPackagingHintItems = pluginPackagingRecoveryHints.map((text) => ({
27+kind: "generic",
28+ text,
29+}));
22302331function expectLatestRuntimeJson(payload: unknown) {
2432const calls = defaultRuntime.writeJson.mock.calls;
@@ -47,6 +55,8 @@ function setConfigSnapshot(params: {
4755exists: boolean;
4856valid: boolean;
4957issues?: Array<{ path: string; message: string }>;
58+warnings?: Array<{ path: string; message: string }>;
59+legacyIssues?: Array<{ path: string; message: string }>;
5060lastTouchedVersion?: string;
5161}) {
5262const config = params.lastTouchedVersion
@@ -58,6 +68,28 @@ function setConfigSnapshot(params: {
5868 config,
5969sourceConfig: config,
6070issues: params.issues ?? [],
71+warnings: params.warnings ?? [],
72+legacyIssues: params.legacyIssues ?? [],
73+});
74+}
75+76+function setPluginPackagingInvalidSnapshot() {
77+setConfigSnapshot({
78+exists: true,
79+valid: false,
80+issues: [
81+{
82+path: "plugins.slots.memory",
83+message: "plugin not found: source-only-pack",
84+},
85+],
86+warnings: [
87+{
88+path: "plugins",
89+message:
90+"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.",
91+},
92+],
6193});
6294}
6395@@ -107,6 +139,22 @@ describe("runServiceRestart config pre-flight (#35862)", () => {
107139});
108140});
109141142+it("points restart at plugin packaging recovery for packaging-only invalid config", async () => {
143+setPluginPackagingInvalidSnapshot();
144+145+await expect(runServiceRestart(createServiceRunArgs())).rejects.toThrow("__exit__:1");
146+147+expect(service.restart).not.toHaveBeenCalled();
148+expectLatestRuntimeJson({
149+action: "restart",
150+ok: false,
151+error: "Gateway restart blocked: plugins.slots.memory: plugin not found: source-only-pack",
152+hints: pluginPackagingRecoveryHints,
153+hintItems: pluginPackagingHintItems,
154+warnings: undefined,
155+});
156+});
157+110158it("blocks restart from an older binary when config was written by a newer one", async () => {
111159setConfigSnapshot({ exists: true, valid: true, lastTouchedVersion: "9999.1.1" });
112160@@ -185,6 +233,22 @@ describe("runServiceStart config pre-flight (#35862)", () => {
185233});
186234});
187235236+it("points start at plugin packaging recovery for packaging-only invalid config", async () => {
237+setPluginPackagingInvalidSnapshot();
238+239+await expect(runServiceStart(createServiceRunArgs())).rejects.toThrow("__exit__:1");
240+241+expect(service.restart).not.toHaveBeenCalled();
242+expectLatestRuntimeJson({
243+action: "start",
244+ok: false,
245+error: "Gateway start blocked: plugins.slots.memory: plugin not found: source-only-pack",
246+hints: pluginPackagingRecoveryHints,
247+hintItems: pluginPackagingHintItems,
248+warnings: undefined,
249+});
250+});
251+188252it("aborts before not-loaded start recovery when config is invalid", async () => {
189253const onNotLoaded = vi.fn(async () => ({
190254result: "started" as const,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。