fix(clients): preserve large TOML documents without argument spreads
Fixes #4856
This commit is contained in:
1 parent
3460ceac3f
commit
c43bc0bf66
3 files changed
+139
-3
No files matched your search
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
parseClientConfiguration,
|
||||
serializeClientConfiguration,
|
||||
withClientServers,
|
||||
} from "./ClientConfigurationDocument.js";
|
||||
|
||||
const registration = { command: "rea", args: ["mcp"] };
|
||||
|
||||
describe("TOML configuration source capacity", () => {
|
||||
it("retains a large multiline foreign value inside an inline server table", () => {
|
||||
const note = `first\n${"x\n".repeat(500_000)}last`;
|
||||
const original = `mcp_servers = { other = {command="other",env={NOTE="""${note}"""}}}\n`;
|
||||
const parsed = parseClientConfiguration(original, "toml");
|
||||
const desired = withClientServers(parsed, {
|
||||
...parsed.servers,
|
||||
rea: registration,
|
||||
});
|
||||
const configured = serializeClientConfiguration(desired, "toml", original);
|
||||
expect(configured).toContain(`NOTE="""${note}"""`);
|
||||
expect(parseClientConfiguration(configured, "toml").document).toEqual(
|
||||
desired,
|
||||
);
|
||||
const removed = serializeClientConfiguration(
|
||||
parsed.document,
|
||||
"toml",
|
||||
configured,
|
||||
);
|
||||
expect(removed).toContain(`NOTE="""${note}"""`);
|
||||
expect(parseClientConfiguration(removed, "toml").document).toEqual(
|
||||
parsed.document,
|
||||
);
|
||||
});
|
||||
|
||||
it("removes only REA from a large multiline Grok disabled-server list", () => {
|
||||
const otherNames = '"other",\n'.repeat(500_000);
|
||||
const original =
|
||||
`disabled_mcp_servers = [\n"rea",\n${otherNames}]\n` +
|
||||
'[mcp_servers.other]\ncommand = "other"\n';
|
||||
const parsed = parseClientConfiguration(original, "grok");
|
||||
const desired = withClientServers(parsed, {
|
||||
...parsed.servers,
|
||||
rea: registration,
|
||||
});
|
||||
const configured = serializeClientConfiguration(desired, "grok", original);
|
||||
const result = parseClientConfiguration(configured, "grok");
|
||||
expect(result.document.disabled_mcp_servers).toEqual(
|
||||
Array<string>(500_000).fill("other"),
|
||||
);
|
||||
expect(result.servers).toEqual({
|
||||
other: { command: "other" },
|
||||
rea: registration,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -967,8 +967,12 @@ const upsertTomlServerSection = (
|
||||
const discardPending = (): void => {
|
||||
pending.length = 0;
|
||||
};
|
||||
// Document line counts must not become engine-limited argument counts.
|
||||
const appendLines = (lines: readonly string[]): void => {
|
||||
for (const line of lines) kept.push(line);
|
||||
};
|
||||
const flushPending = (): void => {
|
||||
kept.push(...pending);
|
||||
appendLines(pending);
|
||||
pending.length = 0;
|
||||
};
|
||||
const lineIndexAt = (offset: number): number => {
|
||||
@@ -1039,7 +1043,7 @@ const upsertTomlServerSection = (
|
||||
: undefined;
|
||||
if (disabledLines !== undefined) {
|
||||
flushPending();
|
||||
kept.push(...disabledLines);
|
||||
appendLines(disabledLines);
|
||||
lineIndex = endLine + 1;
|
||||
mode = "none";
|
||||
continue;
|
||||
@@ -1064,7 +1068,7 @@ const upsertTomlServerSection = (
|
||||
edited === undefined
|
||||
? text.slice(lineStart, statementEnd)
|
||||
: `${prefix}${edited}${suffix}`;
|
||||
kept.push(...replacement.split("\n"));
|
||||
appendLines(replacement.split("\n"));
|
||||
inlineServerTable = true;
|
||||
} else discardPending();
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, join } from "node:path";
|
||||
import { parse as parseToml } from "smol-toml";
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
for (const scenario of [
|
||||
{ client: "codex", path: ".codex/config.toml", line: "\n" },
|
||||
{ client: "grok_build", path: ".grok/config.toml", line: "# retained\n" },
|
||||
] as const)
|
||||
cliTest(
|
||||
`${scenario.client} setup and uninstall preserve large valid TOML documents`,
|
||||
async ({ cli }) => {
|
||||
const home = await createTestTempDirectory("rea-toml-capacity-");
|
||||
const config = join(home, scenario.path);
|
||||
const original =
|
||||
'model = "local-control"\n' +
|
||||
scenario.line.repeat(140_000) +
|
||||
'[mcp_servers.other]\ncommand = "other-local-control"\n';
|
||||
await mkdir(dirname(config), { recursive: true });
|
||||
await writeFile(config, original);
|
||||
const environment = {
|
||||
HOME: home,
|
||||
USERPROFILE: home,
|
||||
CODEX_HOME: join(home, ".codex"),
|
||||
GROK_HOME: join(home, ".grok"),
|
||||
XDG_CONFIG_HOME: join(home, ".config"),
|
||||
TMPDIR: home,
|
||||
};
|
||||
const setupArguments = [
|
||||
"setup",
|
||||
"--client",
|
||||
scenario.client,
|
||||
"--skill=false",
|
||||
"--json",
|
||||
];
|
||||
const run = (arguments_: readonly string[]) =>
|
||||
cli.run({ arguments: arguments_, cwd: home, environment });
|
||||
|
||||
const preview = await run([...setupArguments, "--dry-run"]);
|
||||
expect(preview.exitCode, preview.stdout).toBe(0);
|
||||
expect(preview.json).toMatchObject({ status: "planned" });
|
||||
expect(await readFile(config, "utf8")).toBe(original);
|
||||
|
||||
const applied = await run([...setupArguments, "--yes"]);
|
||||
expect(applied.exitCode, applied.stdout).toBe(0);
|
||||
expect(applied.json).toMatchObject({
|
||||
status: "ready",
|
||||
clients: { [scenario.client]: { status: "configured" } },
|
||||
});
|
||||
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
||||
const configured = await readFile(config, "utf8");
|
||||
expect(configured.startsWith(original.trimEnd())).toBe(true);
|
||||
expect(parseToml(configured)).toMatchObject({
|
||||
mcp_servers: { other: { command: "other-local-control" }, rea: {} },
|
||||
});
|
||||
|
||||
const repeated = await run([...setupArguments, "--yes"]);
|
||||
expect(repeated.exitCode, repeated.stdout).toBe(0);
|
||||
expect(repeated.json).toMatchObject({
|
||||
status: "ready",
|
||||
appliedActions: [],
|
||||
});
|
||||
expect(await readFile(config, "utf8")).toBe(configured);
|
||||
|
||||
const removed = await run(["uninstall", "--json"]);
|
||||
expect(removed.exitCode, removed.stdout).toBe(0);
|
||||
expect(removed.json).toMatchObject({ status: "complete" });
|
||||
const remaining = await readFile(config, "utf8");
|
||||
expect(remaining.startsWith(original.trimEnd())).toBe(true);
|
||||
expect(parseToml(remaining)).toEqual(parseToml(original));
|
||||
expect(await readFile(`${config}.rea.backup`, "utf8")).toBe(original);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user