fix(config): parse quoted bracket paths

This commit is contained in:
Peter Steinberger
2026-04-22 22:10:25 +01:00
parent a971884104
commit 0588dfe15d
3 changed files with 61 additions and 1 deletions

View File

@@ -82,6 +82,12 @@ install method aligned:
The Gateway core auto-updater (when enabled via config) reuses this same update path.
For package-manager installs, `openclaw update` resolves the target package
version before invoking the package manager. If the installed version exactly
matches the target and no update-channel change needs to be persisted, the
command exits as skipped before package install, plugin sync, completion refresh,
or gateway restart work.
## Git checkout flow
Channels:

View File

@@ -273,6 +273,41 @@ describe("config cli", () => {
});
});
it("dry-runs nested plugin install updates without dropping sibling fields", async () => {
const resolved = {
plugins: {
installs: {
"openclaw-web-search": {
source: "npm",
spec: "@ollama/openclaw-web-search",
installPath: "/tmp/openclaw-web-search",
version: "0.2.2",
resolvedName: "@ollama/openclaw-web-search",
resolvedVersion: "0.2.2",
resolvedSpec: "@ollama/openclaw-web-search@0.2.2",
integrity: "sha512-test",
resolvedAt: "2026-04-22T10:33:58.083Z",
installedAt: "2026-04-22T10:33:58.240Z",
},
},
},
} as unknown as OpenClawConfig;
setSnapshot(resolved, resolved);
await runConfigCommand([
"config",
"set",
'plugins.installs["openclaw-web-search"].spec',
'"@ollama/openclaw-web-search@0.2.2"',
"--strict-json",
"--dry-run",
]);
expect(mockWriteConfigFile).not.toHaveBeenCalled();
expect(mockError).not.toHaveBeenCalled();
expect(mockLog).toHaveBeenCalledWith(expect.stringContaining("Dry run successful"));
});
it("writes agents.defaults.llm.idleTimeoutSeconds without disturbing sibling defaults", async () => {
const resolved: OpenClawConfig = {
agents: {

View File

@@ -108,6 +108,25 @@ function isIndexSegment(raw: string): boolean {
return /^[0-9]+$/.test(raw);
}
function parseBracketPathSegment(raw: string, fullPath: string): string {
const trimmed = raw.trim();
if (!trimmed) {
throw new Error(`Invalid path (empty "[]"): ${fullPath}`);
}
if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
try {
const parsed = JSON5.parse(trimmed) as unknown;
if (typeof parsed === "string" && parsed.trim()) {
return parsed;
}
} catch (err) {
throw new Error(`Invalid path bracket string (${trimmed}): ${fullPath}`, { cause: err });
}
throw new Error(`Invalid path bracket string (${trimmed}): ${fullPath}`);
}
return trimmed;
}
function parsePath(raw: string): PathSegment[] {
const trimmed = raw.trim();
if (!trimmed) {
@@ -147,7 +166,7 @@ function parsePath(raw: string): PathSegment[] {
if (!inside) {
throw new Error(`Invalid path (empty "[]"): ${raw}`);
}
parts.push(inside);
parts.push(parseBracketPathSegment(inside, raw));
i = close + 1;
continue;
}