Files
rea/tests/boundary/filesystem/clientConfigurationDialects.test.ts
T
Haoran Xu c592ea6bac fix(opencode): override inherited disabled flags (#3469)
Set disabled to false in generated OpenCode V2 registrations so a
lower-priority disabled entry cannot keep approved setup stale.

Cover layered JSON/JSONC refreshes, idempotent setup and doctor readiness,
exact backups, preserved lower settings, malformed-config refusal, and
the exclusion of disabled registrations from automatic maintenance.
Update the native V2 registration expectations.

Fixes #3318
2026-10-23 02:33:38 +08:00

877 lines
30 KiB
TypeScript

import { mkdir, readFile, symlink, writeFile } from "node:fs/promises";
import { join, relative, resolve } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
findNodeAtLocation,
parse as parseJsonc,
parseTree,
type ParseError,
} from "jsonc-parser";
import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js";
import { configureClientConfiguration } from "../../../src/application/SetupClientConfiguration.js";
import { systemUninstallHost } from "../../../src/application/Uninstall.js";
import { supportedClients } from "../../../src/application/SupportedClients.js";
import { PRODUCT_IDENTITY } from "../../../src/identity.js";
import { classifyClientRegistrationCommand } from "../../../src/application/ClientRegistrationIdentity.js";
import {
CURRENT_ENTRYPOINT_PATH,
normalizeEntrypointPath,
} from "../../../src/process/CurrentEntrypointPath.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import {
clearClientLocationEnvironment,
NPX_REGISTRATION_COMMAND,
} from "../../fixtures/clientEnvironment.js";
const command = NPX_REGISTRATION_COMMAND;
beforeEach(clearClientLocationEnvironment);
afterEach(() => vi.unstubAllEnvs());
const getClients = (home: string) =>
supportedClients(home).filter(({ name }) =>
[
"opencode",
"antigravity",
"copilot_cli",
"commandcode",
"vscode",
"devin",
"omp",
"pi",
].includes(name),
);
describe("additional client configuration dialects", () => {
it("does not uninstall a Node registration whose relative script only matches this process cwd", async () => {
if (CURRENT_ENTRYPOINT_PATH === undefined)
throw new Error("test runner entrypoint is unavailable");
const home = await createTestTempDirectory("rea-relative-uninstall-");
const cursor = supportedClients(home).find(({ name }) => name === "cursor");
if (cursor === undefined) throw new Error("missing Cursor client");
await mkdir(cursor.markerPath ?? home, { recursive: true });
const relativeScript = relative(process.cwd(), CURRENT_ENTRYPOINT_PATH);
const registration = {
command: process.execPath,
args: [relativeScript, "mcp"],
};
await writeFile(
cursor.configPath,
JSON.stringify({ mcpServers: { rea: registration } }),
);
expect(
await readClientRegistrationStatuses(home, CURRENT_ENTRYPOINT_PATH),
).toContainEqual(
expect.objectContaining({
client: "cursor",
command: [process.execPath, relativeScript, "mcp"],
state: "unknown",
}),
);
await systemUninstallHost(home).removeClient(cursor);
const after = parseJsonc(await readFile(cursor.configPath, "utf8")) as {
readonly mcpServers: Readonly<Record<string, unknown>>;
};
expect(after.mcpServers.rea).toEqual(registration);
});
it("treats an empty XDG config home as unset for OpenCode setup", async () => {
const home = await createTestTempDirectory("rea-opencode-empty-xdg-");
const client = supportedClients(home, "linux", {
XDG_CONFIG_HOME: "",
}).find(({ name }) => name === "opencode");
const expectedPath = join(home, ".config/opencode/opencode.json");
if (client === undefined) throw new Error("missing opencode client");
expect(client.configPath).toBe(expectedPath);
expect(client.configPaths).toContain(expectedPath);
expect(
await configureClientConfiguration(client, {}, command),
).toMatchObject({ status: "configured" });
expect(await readFile(expectedPath, "utf8")).toContain('"mcp"');
});
it.each([
"opencode",
"antigravity",
"copilot_cli",
"commandcode",
"vscode",
"devin",
"omp",
"pi",
] as const)("registers, reads back, and uninstalls %s", async (name) => {
const home = await createTestTempDirectory("rea-client-dialect-");
const client = getClients(home).find(
(candidate) => candidate.name === name,
);
if (client === undefined) throw new Error(`missing ${name} client`);
await mkdir(client.markerPath ?? home, { recursive: true });
const configured = await configureClientConfiguration(client, {}, command);
expect(configured.status).toBe("configured");
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "unchanged",
});
const text = await readFile(client.configPath, "utf8");
const document = parseJsonc(text) as Record<string, unknown>;
const serversKey =
name === "opencode"
? "mcp"
: name === "vscode"
? "servers"
: "mcpServers";
const servers = document[serversKey] as Record<
string,
Record<string, unknown>
>;
const registration = servers[PRODUCT_IDENTITY.mcpServerKey];
expect(registration).toBeDefined();
if (name === "opencode")
expect(registration).toMatchObject({
type: "local",
command,
enabled: true,
});
if (name === "vscode" || name === "omp" || name === "pi")
expect(registration).toMatchObject({
type: "stdio",
command: "npx",
args: command.slice(1),
});
if (name === "copilot_cli")
expect(registration).toMatchObject({ type: "stdio", tools: ["*"] });
if (name === "commandcode")
expect(registration).toMatchObject({
transport: "stdio",
enabled: true,
command: "npx",
args: command.slice(1),
});
const statuses = await readClientRegistrationStatuses(home);
expect(statuses).toEqual(
expect.arrayContaining([
expect.objectContaining({ client: name, state: "aligned", command }),
]),
);
const removed = await systemUninstallHost(home).removeClient(client);
expect(removed.status).toBe("removed");
const afterRemoval = parseJsonc(
await readFile(client.configPath, "utf8"),
) as Record<string, unknown>;
expect(
(afterRemoval[serversKey] as Record<string, unknown>)[
PRODUCT_IDENTITY.mcpServerKey
],
).toBeUndefined();
});
});
describe("OpenCode JSONC registration edits", () => {
it("preserves OpenCode JSONC comments and sibling registrations during update and removal", async () => {
const home = await createTestTempDirectory("rea-opencode-jsonc-");
const client = getClients(home).find(({ name }) => name === "opencode");
if (client === undefined) throw new Error("missing OpenCode client");
await mkdir(client.markerPath ?? home, { recursive: true });
const original = `{
// Client-wide preferences stay intact.
"model": "provider/model",
"mcp": {
// An independently managed server.
"other": { "type": "local", "command": ["node", "other.js"] },
},
}\n`;
await writeFile(client.configPath, original);
expect(
await configureClientConfiguration(client, {}, command),
).toMatchObject({
status: "configured",
backupPath: `${client.configPath}.rea.backup`,
});
const updated = await readFile(client.configPath, "utf8");
const errors: ParseError[] = [];
const parsed = parseJsonc(updated, errors, {
allowTrailingComma: true,
}) as Record<string, unknown>;
expect(errors).toEqual([]);
expect(updated).toContain("// Client-wide preferences stay intact.");
expect(updated).toContain("// An independently managed server.");
expect(parsed.model).toBe("provider/model");
expect((parsed.mcp as Record<string, unknown>).other).toBeDefined();
expect(await readFile(`${client.configPath}.rea.backup`, "utf8")).toBe(
original,
);
expect((await systemUninstallHost(home).removeClient(client)).status).toBe(
"removed",
);
const removedText = await readFile(client.configPath, "utf8");
const removedErrors: ParseError[] = [];
const removed = parseJsonc(removedText, removedErrors, {
allowTrailingComma: true,
}) as Record<string, unknown>;
expect(removedErrors).toEqual([]);
expect(removedText).toContain("// Client-wide preferences stay intact.");
expect(removedText).toContain("// An independently managed server.");
expect((removed.mcp as Record<string, unknown>).rea).toBeUndefined();
expect((removed.mcp as Record<string, unknown>).other).toBeDefined();
});
it("rejects malformed OpenCode JSONC without creating a backup or changing bytes", async () => {
const home = await createTestTempDirectory("rea-opencode-invalid-");
const client = getClients(home).find(({ name }) => name === "opencode");
if (client === undefined) throw new Error("missing OpenCode client");
await mkdir(client.markerPath ?? home, { recursive: true });
const invalid = '{ "mcp": { /* broken */ "rea": [ }';
await writeFile(client.configPath, invalid);
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "failed",
reason: "readback",
});
expect(await readFile(client.configPath, "utf8")).toBe(invalid);
});
});
it.each([
{
name: "a sole registration without a trailing comma",
hasBeforeSibling: false,
hasAfterSibling: false,
hasGapComment: false,
afterRegistration: "",
expectedComment: undefined,
},
{
name: "a sole registration with a trailing comma",
hasBeforeSibling: false,
hasAfterSibling: false,
hasGapComment: false,
afterRegistration: ",",
expectedComment: undefined,
},
{
name: "a sole registration with a block comment before its trailing comma",
hasBeforeSibling: false,
hasAfterSibling: false,
hasGapComment: false,
afterRegistration: " /* Keep this note. */,",
expectedComment: "/* Keep this note. */",
},
{
name: "a sole registration with a line comment before its trailing comma",
hasBeforeSibling: false,
hasAfterSibling: false,
hasGapComment: false,
afterRegistration: " // Keep this note.\n ,",
expectedComment: "// Keep this note.",
},
{
name: "a registration with a surviving sibling and block comment before its comma",
hasBeforeSibling: true,
hasAfterSibling: true,
hasGapComment: true,
afterRegistration:
' /* Keep this note. */,\n "after": { "command": "after" }',
expectedComment: "/* Keep this note. */",
},
{
name: "a registration with a surviving sibling and line comment before its comma",
hasBeforeSibling: true,
hasAfterSibling: true,
hasGapComment: true,
afterRegistration:
' // Keep this note.\n ,\n "after": { "command": "after" }',
expectedComment: "// Keep this note.",
},
{
name: "a first registration with a surviving sibling and block comment before its comma",
hasBeforeSibling: false,
hasAfterSibling: true,
hasGapComment: false,
afterRegistration:
' /* Keep this note. */ ,\n "after": { "command": "after" }',
expectedComment: "/* Keep this note. */",
},
] as const)(
"Cursor removes $name and preserves surrounding configuration",
async ({
hasBeforeSibling,
hasAfterSibling,
hasGapComment,
afterRegistration,
expectedComment,
}) => {
const home = await createTestTempDirectory("rea-cursor-jsonc-remove-");
const client = supportedClients(home).find(({ name }) => name === "cursor");
if (client === undefined) throw new Error("missing Cursor client");
await mkdir(client.markerPath ?? home, { recursive: true });
const original = `{
// Keep unrelated client preferences.
"theme": "dark",
"mcpServers": {
${hasBeforeSibling ? ' "before": { "command": "before" },\n' : ""} }
}\n`;
await writeFile(client.configPath, original);
expect(
await configureClientConfiguration(client, {}, command),
).toMatchObject({
status: "configured",
backupPath: `${client.configPath}.rea.backup`,
});
const configured = await readFile(client.configPath, "utf8");
let edited = configured;
if (hasGapComment) {
const root = parseTree(edited, [], { allowTrailingComma: true });
const registration =
root === undefined
? undefined
: findNodeAtLocation(root, [
"mcpServers",
PRODUCT_IDENTITY.mcpServerKey,
]);
const property = registration?.parent;
if (property === undefined)
throw new Error("configured Cursor registration property is missing");
edited =
edited.slice(0, property.offset) +
"// Keep the comment before the registration.\n " +
edited.slice(property.offset);
}
const root = parseTree(edited, [], { allowTrailingComma: true });
const registration =
root === undefined
? undefined
: findNodeAtLocation(root, [
"mcpServers",
PRODUCT_IDENTITY.mcpServerKey,
]);
if (registration === undefined)
throw new Error("configured Cursor registration is missing");
const registrationEnd = registration.offset + registration.length;
edited =
edited.slice(0, registrationEnd) +
afterRegistration +
edited.slice(registrationEnd);
await writeFile(client.configPath, edited);
const backup = await readFile(`${client.configPath}.rea.backup`, "utf8");
const removed = await systemUninstallHost(home).removeClient(client);
expect(removed.status).toBe("removed");
const removedText = await readFile(client.configPath, "utf8");
const errors: ParseError[] = [];
const document = parseJsonc(removedText, errors, {
allowTrailingComma: true,
}) as Record<string, unknown>;
expect(errors).toEqual([]);
expect(document.theme).toBe("dark");
expect(removedText).toContain("// Keep unrelated client preferences.");
if (expectedComment !== undefined)
expect(removedText).toContain(expectedComment);
if (hasGapComment)
expect(removedText).toContain(
"// Keep the comment before the registration.",
);
expect(
(document.mcpServers as Record<string, unknown>).rea,
).toBeUndefined();
if (hasBeforeSibling)
expect(document.mcpServers).toMatchObject({
before: { command: "before" },
});
if (hasAfterSibling)
expect(document.mcpServers).toMatchObject({
after: { command: "after" },
});
if (!hasBeforeSibling && !hasAfterSibling)
expect(document.mcpServers).toEqual({});
expect(await readFile(`${client.configPath}.rea.backup`, "utf8")).toBe(
backup,
);
const repeated = await systemUninstallHost(home).removeClient(client);
expect(repeated.status).toBe("skipped");
expect(await readFile(client.configPath, "utf8")).toBe(removedText);
expect(await readFile(`${client.configPath}.rea.backup`, "utf8")).toBe(
backup,
);
},
);
const parseOpenCode = (text: string): Record<string, unknown> => {
const errors: ParseError[] = [];
const document = parseJsonc(text, errors, {
allowTrailingComma: true,
}) as Record<string, unknown>;
expect(errors).toEqual([]);
return document;
};
const openCodeClient = async (prefix: string) => {
const home = await createTestTempDirectory(prefix);
const client = getClients(home).find(({ name }) => name === "opencode");
if (client === undefined) throw new Error("missing OpenCode client");
await mkdir(client.markerPath ?? home, { recursive: true });
return { home, client };
};
const legacyEntry = JSON.stringify({ type: "local", command, enabled: true });
describe("OpenCode V2 native server table", () => {
it("registers in mcp.servers, replaces REA's V1 entry, and uninstalls", async () => {
const { home, client } = await openCodeClient("rea-opencode-v2-");
await writeFile(
client.configPath,
`{
"mcp": {
// Default timeouts are not a server.
"timeout": { "catalog": 30000 },
"servers": {
// An independently managed server.
"other": { "type": "local", "command": ["node", "other.js"] },
},
"rea": ${legacyEntry},
},
}\n`,
);
expect(
(await configureClientConfiguration(client, {}, command)).status,
).toBe("configured");
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "unchanged",
});
const text = await readFile(client.configPath, "utf8");
expect(text).toContain("// Default timeouts are not a server.");
expect(text).toContain("// An independently managed server.");
expect(parseOpenCode(text).mcp).toEqual({
timeout: { catalog: 30000 },
servers: {
other: { type: "local", command: ["node", "other.js"] },
rea: { type: "local", command: [...command], disabled: false },
},
});
expect(await readClientRegistrationStatuses(home)).toEqual(
expect.arrayContaining([
expect.objectContaining({
client: "opencode",
state: "aligned",
command,
}),
]),
);
expect((await systemUninstallHost(home).removeClient(client)).status).toBe(
"removed",
);
expect(
parseOpenCode(await readFile(client.configPath, "utf8")).mcp,
).toEqual({
timeout: { catalog: 30000 },
servers: { other: { type: "local", command: ["node", "other.js"] } },
});
});
it("updates and removes REA's entry after OpenCode migrates it to mcp.servers", async () => {
const { home, client } = await openCodeClient("rea-opencode-migrated-");
const stale = ["npx", "-y", `${PRODUCT_IDENTITY.packageName}@3.2.0`, "mcp"];
await writeFile(
client.configPath,
`${JSON.stringify({ mcp: { servers: { rea: { type: "local", command: stale } } } })}\n`,
);
expect(await readClientRegistrationStatuses(home)).toEqual(
expect.arrayContaining([
expect.objectContaining({ client: "opencode", command: stale }),
]),
);
expect(
(await configureClientConfiguration(client, {}, command)).status,
).toBe("configured");
expect(
parseOpenCode(await readFile(client.configPath, "utf8")).mcp,
).toEqual({
servers: {
rea: { type: "local", command: [...command], disabled: false },
},
});
expect((await systemUninstallHost(home).removeClient(client)).status).toBe(
"removed",
);
expect(
parseOpenCode(await readFile(client.configPath, "utf8")).mcp,
).toEqual({
servers: {},
});
});
it("reports and removes a V1 entry that OpenCode still loads beside mcp.servers", async () => {
const { home, client } = await openCodeClient("rea-opencode-mixed-");
await writeFile(
client.configPath,
`{ "mcp": { "servers": {}, "rea": ${legacyEntry} } }\n`,
);
expect(await readClientRegistrationStatuses(home)).toEqual(
expect.arrayContaining([
expect.objectContaining({
client: "opencode",
state: "aligned",
command,
}),
]),
);
expect((await systemUninstallHost(home).removeClient(client)).status).toBe(
"removed",
);
expect(
parseOpenCode(await readFile(client.configPath, "utf8")).mcp,
).toEqual({
servers: {},
});
});
it("treats a V2 table holding a server named type as native", async () => {
const { home, client } = await openCodeClient("rea-opencode-type-server-");
const typeServer = { type: "local", command: ["node", "type.js"] };
await writeFile(
client.configPath,
`${JSON.stringify({ mcp: { servers: { type: typeServer, rea: { type: "local", command } } } })}\n`,
);
expect(await readClientRegistrationStatuses(home)).toEqual(
expect.arrayContaining([
expect.objectContaining({
client: "opencode",
state: "aligned",
command,
}),
]),
);
expect((await systemUninstallHost(home).removeClient(client)).status).toBe(
"removed",
);
expect(
parseOpenCode(await readFile(client.configPath, "utf8")).mcp,
).toEqual({
servers: { type: typeServer },
});
});
it("keeps a V1 server named servers in the V1 table", async () => {
const { client } = await openCodeClient("rea-opencode-v1-servers-");
const servers = { type: "local", command: ["node", "servers.js"] };
await writeFile(
client.configPath,
`${JSON.stringify({ mcp: { servers } })}\n`,
);
expect(
(await configureClientConfiguration(client, {}, command)).status,
).toBe("configured");
expect(
parseOpenCode(await readFile(client.configPath, "utf8")).mcp,
).toEqual({
servers,
rea: { type: "local", command: [...command], enabled: true },
});
});
});
describe("platform-aware client config paths", () => {
it.each([
[
"darwin",
"/home/a",
{},
"/home/a/Library/Application Support/Code/User/mcp.json",
],
["linux", "/home/a", {}, "/home/a/.config/Code/User/mcp.json"],
[
"win32",
"C:/Users/a",
{},
"C:/Users/a/AppData/Roaming/Code/User/mcp.json",
],
[
"linux",
"/home/a",
{ XDG_CONFIG_HOME: "/config" },
"/config/Code/User/mcp.json",
],
] as const)(
"resolves VS Code user MCP path for %s",
(platform, home, env, expected) => {
expect(
supportedClients(home, platform, env).find(
({ name }) => name === "vscode",
)?.configPath,
).toBe(expected);
},
);
it("honors Grok Build and Grok Bot configuration roots", () => {
expect(
supportedClients("/home/a", "linux", {
GROK_HOME: "/custom/grok",
SAND_DATA_ROOT: "/custom/grokbot",
}).filter(({ name }) => name === "grok_build" || name === "grok_bot"),
).toEqual([
expect.objectContaining({
name: "grok_build",
displayName: "Grok Build",
configPath: "/custom/grok/config.toml",
markerPath: "/custom/grok",
format: "grok",
}),
expect.objectContaining({
name: "grok_bot",
displayName: "Grok Bot",
configPath: "/custom/grokbot",
markerPath: "/custom/grokbot",
format: "unsupported",
}),
]);
expect(
supportedClients("/home/a", "linux", {
SAND_DATA_ROOT: "relative/sand",
}).find(({ name }) => name === "grok_bot")?.configPath,
).toBe("/home/a/.grokbot");
expect(
supportedClients("/home/a", "darwin", {}).find(
({ name }) => name === "grok_build",
)?.configPath,
).toBe("/home/a/.grok/config.toml");
});
it("honors Copilot CLI and Devin platform configuration roots", () => {
expect(
supportedClients("/home/a", "linux", {
COPILOT_HOME: "/custom/copilot",
XDG_CONFIG_HOME: "/custom/config",
}).find(({ name }) => name === "copilot_cli")?.configPath,
).toBe("/custom/copilot/mcp-config.json");
expect(
supportedClients("C:/Users/a", "win32", {
APPDATA: "D:/Profile/Roaming",
}).find(({ name }) => name === "devin")?.configPath,
).toBe("D:/Profile/Roaming/devin/mcp_config.json");
});
it("prefers an existing OpenCode JSONC file and honors explicit harness roots", async () => {
const home = await createTestTempDirectory("rea-config-overrides-");
const opencodeDirectory = join(home, ".config/opencode");
await mkdir(opencodeDirectory, { recursive: true });
await writeFile(join(opencodeDirectory, "opencode.jsonc"), "{}\n");
const clients = supportedClients(home, "linux", {});
expect(clients.find(({ name }) => name === "opencode")?.configPath).toBe(
join(opencodeDirectory, "opencode.jsonc"),
);
expect(
supportedClients(home, "linux", {
OPENCODE_CONFIG: "/custom/open-code.jsonc",
CLAUDE_CONFIG_DIR: "/custom/claude",
CODEX_HOME: "/custom/codex",
}).filter(({ name }) =>
["opencode", "claude_code", "codex"].includes(name),
),
).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "opencode",
configPath: "/custom/open-code.jsonc",
}),
expect.objectContaining({
name: "claude_code",
configPath: "/custom/claude/.claude.json",
}),
expect.objectContaining({
name: "codex",
configPath: "/custom/codex/config.toml",
}),
]),
);
});
});
describe("client registration command ownership", () => {
it.skipIf(process.platform === "win32").each([
["rea", "rea-agents"],
["rea-agents", "rea"],
])(
"recognizes %s through its %s launcher alias",
async (registered, current) => {
const root = await createTestTempDirectory("rea-launcher-alias-");
const script = join(root, "entry.mjs");
await writeFile(script, "// owned dispatcher\n");
await symlink(script, join(root, registered));
await symlink(script, join(root, current));
for (const command of [
[join(root, registered), "mcp"],
[process.execPath, join(root, registered), "mcp"],
]) {
expect(
classifyClientRegistrationCommand(command, join(root, current)),
).toBe("owned");
}
},
);
it.skipIf(process.platform === "win32")(
"distinguishes a confirmed different script from an unresolved alias",
async () => {
const root = await createTestTempDirectory("rea-launcher-foreign-");
const owned = join(root, "entry.mjs");
const foreign = join(root, "other.mjs");
await writeFile(owned, "// owned\n");
await writeFile(foreign, "// foreign\n");
await symlink(owned, join(root, "rea"));
await symlink(foreign, join(root, "rea-agents"));
await symlink(join(root, "missing.mjs"), join(root, "missing"));
for (const candidate of ["rea-agents", "missing"]) {
const command = [join(root, candidate), "mcp"];
expect(
classifyClientRegistrationCommand(command, join(root, "rea")),
).toBe(candidate === "missing" ? "unknown" : "foreign");
}
},
);
it
.skipIf(process.platform === "win32")
.each(["registered", "current"] as const)(
"preserves unknown identity when the %s alias cannot resolve",
async (unresolved) => {
const root = await createTestTempDirectory("rea-launcher-unresolved-");
const script = join(root, "entry.mjs");
await writeFile(script, "// current dispatcher\n");
await symlink(join(root, "removed-release.mjs"), join(root, "dangling"));
await symlink(join(root, "cycle-b"), join(root, "cycle-a"));
await symlink(join(root, "cycle-a"), join(root, "cycle-b"));
for (const alias of ["dangling", "cycle-a"]) {
const registered =
unresolved === "registered" ? join(root, alias) : script;
const current = unresolved === "current" ? join(root, alias) : script;
for (const command of [
[registered, "mcp"],
[process.execPath, registered, "mcp"],
])
expect(classifyClientRegistrationCommand(command, current)).toBe(
"unknown",
);
}
},
);
});
describe("client launcher traversal semantics", () => {
it.skipIf(process.platform === "win32")(
"distinguishes native executable traversal from Node script argument selection",
async () => {
const root = await createTestTempDirectory(
"rea-launcher-parent-segment-",
);
const current = join(root, "entry.mjs");
const foreign = join(root, "other", "entry.mjs");
await mkdir(join(root, "other", "child"), { recursive: true });
await writeFile(current, "// current dispatcher\n");
await writeFile(foreign, "// foreign dispatcher\n");
await symlink(join(root, "other", "child"), join(root, "link"));
const candidate = `${root}/link/../entry.mjs`;
expect(await readFile(candidate, "utf8")).toBe("// foreign dispatcher\n");
expect(
classifyClientRegistrationCommand([candidate, "mcp"], current),
).toBe("unknown");
expect(
classifyClientRegistrationCommand([candidate, "mcp"], foreign),
).toBe("unknown");
expect(
classifyClientRegistrationCommand(
[process.execPath, candidate, "mcp"],
current,
),
).toBe("owned");
const unresolvable = `${root}/missing/../entry.mjs`;
expect(
classifyClientRegistrationCommand([unresolvable, "mcp"], current),
).toBe("unknown");
expect(
classifyClientRegistrationCommand([current, "mcp"], unresolvable),
).toBe("unknown");
expect(
classifyClientRegistrationCommand(
[process.execPath, unresolvable, "mcp"],
current,
),
).toBe("owned");
const nodeAlias = join(root, "node-alias");
await symlink(process.execPath, nodeAlias);
expect(
classifyClientRegistrationCommand([nodeAlias, current, "mcp"], current),
).toBe("owned");
},
);
it("recognizes prior exact REA releases while rejecting foreign package commands", () => {
expect(classifyClientRegistrationCommand(["rea", "mcp"], undefined)).toBe(
"owned",
);
const cwdMatchingEntry = resolve("./rea");
expect(
classifyClientRegistrationCommand(["./rea", "mcp"], cwdMatchingEntry),
).toBe("unknown");
expect(
classifyClientRegistrationCommand(
["npx", "-y", `${PRODUCT_IDENTITY.packageName}@3.2.0`, "mcp"],
undefined,
),
).toBe("owned");
expect(
classifyClientRegistrationCommand(
["npx", "-y", "another-package", "mcp"],
undefined,
),
).toBe("foreign");
expect(
classifyClientRegistrationCommand(
["bunx", `${PRODUCT_IDENTITY.packageName}@3.2.0`, "mcp"],
undefined,
),
).toBe("owned");
expect(
classifyClientRegistrationCommand(
["bunx", "another-package", "mcp"],
undefined,
),
).toBe("foreign");
expect(
classifyClientRegistrationCommand(
[process.execPath, "/packaged/rea.mjs", "mcp"],
"/packaged/rea.mjs",
),
).toBe("owned");
expect(
classifyClientRegistrationCommand(
[process.execPath, resolve("unknown"), "mcp"],
normalizeEntrypointPath(""),
),
).toBe("unknown");
expect(
classifyClientRegistrationCommand(
["node", "/packaged/other-agent.mjs", "mcp"],
"/packaged/rea.mjs",
),
).toBe("unknown");
});
});