fix(doctor): read each client's own activation key (#6331)
* fix(doctor): read each client's own activation key Doctor treated both `disabled: true` and `enabled: false` as a disabled registration for every client except Grok. The installed clients each read only one of them: Codex and Kimi Code honor `enabled` and still load a server marked `disabled`, while Devin honors `disabled` and still loads a server marked `enabled: false`. Doctor reported those loaded registrations as stale, so `rea doctor --client codex` failed with config_drift and setup offered to "repair" a working entry. Skip the key each of these clients ignores. Other clients keep the existing rule. * fix(tests): resolve the Devin config root per platform in activation cases The client activation cases wrote Devin's mcp_config.json under the XDG root on every platform, but devinDirectory resolves APPDATA (or ~/AppData/Roaming) on Windows. Follow the client's per-platform root so the cases exercise the file doctor actually reads there.
This commit is contained in:
1 parent
26bc77ce56
commit
4b4babf1e7
2 files changed
+83
-2
No files matched your search
@@ -356,13 +356,21 @@ export const readClientRegistrationInspections = async (
|
||||
);
|
||||
};
|
||||
|
||||
// Codex and Kimi Code load a server marked `disabled: true` and honor only
|
||||
// `enabled`; Devin loads one marked `enabled: false` and honors only `disabled`.
|
||||
const ignoresDisabledKey = new Set(["codex", "kimi"]);
|
||||
const ignoresEnabledKey = new Set(["devin"]);
|
||||
|
||||
const registrationActivation = (
|
||||
registration: z.output<typeof registrationSchema>,
|
||||
client: SetupClient,
|
||||
parsed: ClientConfigurationDocument,
|
||||
): "enabled" | "disabled" =>
|
||||
(client.format !== "grok" && registration.disabled === true) ||
|
||||
(client.format !== "grok" &&
|
||||
!ignoresDisabledKey.has(client.name) &&
|
||||
registration.disabled === true) ||
|
||||
(registration.enabled === false &&
|
||||
!ignoresEnabledKey.has(client.name) &&
|
||||
!clientServerForcedEnabled(parsed, PRODUCT_IDENTITY.mcpServerKey)) ||
|
||||
((parsed.dialect === "zcode" || parsed.dialect === "zcode_fallback") &&
|
||||
registration.enable === false) ||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { chmod, mkdir, symlink, writeFile } from "node:fs/promises";
|
||||
import { join, relative, resolve } from "node:path";
|
||||
import { dirname, join, relative, resolve } from "node:path";
|
||||
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
@@ -312,6 +312,79 @@ describe("commandcode registration status", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("client activation keys", () => {
|
||||
it.each([
|
||||
["disabled = true", "aligned"],
|
||||
["enabled = false", "stale"],
|
||||
] as const)(
|
||||
"reads only the activation key Codex honors (%s)",
|
||||
async (activation, state) => {
|
||||
const home = await createTestTempDirectory("rea-codex-activation-");
|
||||
await mkdir(join(home, ".codex"));
|
||||
const entry = resolve("scripts/rea.mjs");
|
||||
await writeFile(
|
||||
join(home, ".codex/config.toml"),
|
||||
`[mcp_servers.rea]\ncommand = ${JSON.stringify(process.execPath)}\nargs = [${JSON.stringify(entry)}, "mcp"]\nstartup_timeout_sec = 30\ntool_timeout_sec = 450\n${activation}\n`,
|
||||
);
|
||||
expect(
|
||||
await readClientRegistrationStatuses(home, entry, { environment: {} }),
|
||||
).toEqual([expect.objectContaining({ client: "codex", state })]);
|
||||
},
|
||||
);
|
||||
|
||||
// Each JSON client loads rea under the key it ignores and skips it under the key it honors.
|
||||
it.each([
|
||||
["kimi", { disabled: true }, "aligned"],
|
||||
["kimi", { enabled: false }, "stale"],
|
||||
["devin", { enabled: false }, "aligned"],
|
||||
["devin", { disabled: true }, "stale"],
|
||||
] as const)(
|
||||
"reads only the activation key %s honors (%j)",
|
||||
async (client, activation, state) => {
|
||||
const home = await createTestTempDirectory("rea-json-activation-");
|
||||
const entry = resolve("scripts/rea.mjs");
|
||||
const [config, fields] =
|
||||
client === "kimi"
|
||||
? [
|
||||
join(home, ".kimi-code/mcp.json"),
|
||||
{
|
||||
env: { REA_MCP_INPUT_SCHEMA_PROFILE: "compact" },
|
||||
startupTimeoutMs: 30_000,
|
||||
toolTimeoutMs: 450_000,
|
||||
},
|
||||
]
|
||||
: [
|
||||
join(
|
||||
process.platform === "win32"
|
||||
? join(home, "AppData", "Roaming", "devin")
|
||||
: join(home, ".config", "devin"),
|
||||
"mcp_config.json",
|
||||
),
|
||||
{},
|
||||
];
|
||||
await mkdir(dirname(config), { recursive: true });
|
||||
await writeFile(
|
||||
config,
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
rea: {
|
||||
command: process.execPath,
|
||||
args: [entry, "mcp"],
|
||||
...fields,
|
||||
...activation,
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
expect(
|
||||
await readClientRegistrationStatuses(home, entry, {
|
||||
environment: { XDG_CONFIG_HOME: join(home, ".config") },
|
||||
}),
|
||||
).toEqual([expect.objectContaining({ client, state })]);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("Node-wrapped registration policy", () => {
|
||||
it.each([1, 30])(
|
||||
"accepts Codex caller-selected startup timeout %d independently of launcher",
|
||||
|
||||
Reference in new issue
Block a user