Files
reverse-engineering-anything/src/application/Doctor.test.ts
T
08561e0eb7 feat(clients): register Grok Build and Grok Bot during setup (#1134)
* feat(clients): register Grok Build and Grok Bot during setup

Detect both clients and install the local REA MCP registration the same
way setup already handles the other agents. Grok Build edits only the
REA tables in its TOML config and sets startup_timeout_sec to 30. Grok
Bot writes the mcpServers.rea entry into mcp.json. That file is the
registration to add to the signed-in account; setup does not call the
account API.

Filed by Grok.

* fix(clients): clear a disabled Grok Build registration during setup

Grok Build keeps a server suppressed when its name stays in
disabled_mcp_servers, and setup was treating that file as already aligned.
Setup now removes only that name, doctor reports the registration stale until
it is gone, and uninstall keeps comments in an inline table whose only server
was REA. Split the lifecycle suite and reflow the get-started page so static
checks pass.

* fix(verify): check packed Hopper search as literal-only

The package verifier still called a regex probe that main deleted. The
native bridge refuses regex and joins string hits with typed records, so
the restored probe stubs that inventory and the check asserts the literal
matches and the regex refusal. The new README translations also name
Grok Build and Grok Bot.

* fix(clients): stop treating a Grok Bot data directory as registered

Grok Bot stores connectors in the signed-in account and runs them on its
hosted computer. Setup no longer writes mcp.json or reports that file as
aligned. Doctor and setup tell the caller to add the server from the
Grok Bot chat.

* fix(clients): drop multiline Grok disable-list entries

Decode disabled_mcp_servers strings with the document parser so a
multiline basic or literal string that loads as rea is removed.

---------

Co-authored-by: Grok <eugene.ray@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-09 06:08:29 +08:00

579 lines
17 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { SUPPORTED_NODE_VERSION_PROSE } from "../domain/runtimeVersion.js";
import type { ClientRegistrationStatus } from "./ClientRegistrationStatus.js";
import { createDoctorHostFixture as host } from "./Doctor.fixture.js";
import { runDoctor } from "./Doctor.js";
describe("doctor", () => {
it("returns exact recovery for every failed diagnostic", async () => {
const result = await runDoctor(
"/missing/app",
host({
nodeVersion: "20.0.0",
macosVersion: () => Promise.resolve("11.7"),
executable: () => Promise.resolve(false),
validTarget: () => Promise.resolve(false),
}),
);
expect(
Object.fromEntries(
result.checks.map(({ name, remediation }) => [name, remediation]),
),
).toEqual({
node: `Install ${SUPPORTED_NODE_VERSION_PROSE}.`,
host: "REA supports macOS 12+, Ubuntu 24.04+, Fedora 41+, 64-bit Arch Linux, CachyOS, and the experimental Windows x64 Ghidra P0 boundary.",
hopper: "Run rea setup to install Hopper, or set HOPPER_LAUNCHER_PATH.",
target: "Supply a readable local app or program path.",
});
});
it("reports Node 25 as unsupported", async () => {
const result = await runDoctor(undefined, host({ nodeVersion: "25.1.0" }));
expect(result.healthy).toBe(false);
expect(result.checks).toContainEqual(
expect.objectContaining({
name: "node",
ok: false,
detail: "25.1.0",
classification: "missing_dependency",
}),
);
});
it.each(["11.7", undefined])(
"rejects unsupported macOS version %s",
async (version) => {
const result = await runDoctor(
undefined,
host({ macosVersion: () => Promise.resolve(version) }),
);
expect(result.checks).toContainEqual(
expect.objectContaining({
name: "host",
ok: false,
classification: "unsupported_host",
}),
);
},
);
it("admits only the experimental Windows x64 host boundary", async () => {
const windows = await runDoctor(
undefined,
host({
platform: "win32",
architecture: "x64",
macosVersion: () => Promise.resolve(undefined),
executable: () => Promise.resolve(false),
}),
);
const arm = await runDoctor(
undefined,
host({
platform: "win32",
architecture: "arm64",
macosVersion: () => Promise.resolve(undefined),
executable: () => Promise.resolve(false),
}),
);
expect(windows.checks.find(({ name }) => name === "host")).toMatchObject({
ok: true,
detail: "win32 x64",
});
expect(arm.checks.find(({ name }) => name === "host")).toMatchObject({
ok: false,
detail: "win32 arm64",
});
});
it("detects a manual configured Hopper before Homebrew", async () => {
let brewProbes = 0;
const result = await runDoctor(
undefined,
host({
configuredHopperPath: "/manual/Hopper",
executable: (path) => Promise.resolve(path === "/manual/Hopper"),
brewHopperPath: () => {
brewProbes += 1;
return Promise.resolve(undefined);
},
}),
);
expect(result.hopperPath).toBe("/manual/Hopper");
expect(result.healthy).toBe(true);
expect(brewProbes).toBe(0);
});
it("reports optional BYO ilspycmd diagnostics only when configured", async () => {
const result = await runDoctor(
undefined,
host({
configuredIlspyCmdPath: "/tools/ilspycmd",
ilspyCmdVersion: (path) =>
Promise.resolve(
path === "/tools/ilspycmd" ? "ilspycmd: 9.1.0.7988" : undefined,
),
}),
);
expect(result.healthy).toBe(true);
expect(result.checks).toContainEqual({
name: "ilspycmd",
ok: true,
classification: "healthy",
detail: "/tools/ilspycmd (ilspycmd: 9.1.0.7988)",
});
});
it("reports configured ilspycmd drift without installing it", async () => {
const result = await runDoctor(
undefined,
host({
configuredIlspyCmdPath: "/missing/ilspycmd",
ilspyCmdVersion: () => Promise.resolve(undefined),
}),
);
expect(result.healthy).toBe(false);
expect(result.checks).toContainEqual({
name: "ilspycmd",
ok: false,
classification: "config_drift",
detail: "/missing/ilspycmd",
remediation:
"Unset REA_ILSPY_CMD_PATH or point it at a runnable ilspycmd executable.",
});
});
it("does not fall back when the configured Hopper launcher is invalid", async () => {
const configuredPath = "/invalid/custom/hopper";
const result = await runDoctor(
undefined,
host({ configuredHopperPath: configuredPath }),
);
expect(result.hopperPath).toBeUndefined();
expect(result.healthy).toBe(false);
expect(result.checks.find(({ name }) => name === "hopper")).toEqual({
name: "hopper",
ok: false,
classification: "config_drift",
detail: configuredPath,
remediation:
"Unset or update HOPPER_LAUNCHER_PATH to an executable Hopper launcher.",
});
});
});
describe("doctor installation identity", () => {
it("distinguishes stale skills, path shadowing, and unobservable live state", async () => {
const result = await runDoctor(
undefined,
host({
installationPaths: () =>
Promise.resolve(["/usr/local/bin/rea", "/home/user/bin/rea"]),
installedSkillIdentity: () =>
Promise.resolve({
version: "10",
toolCount: null,
catalogDigest: null,
canonical: false,
}),
}),
);
expect(result.identity).toMatchObject({
live_server: { state: "unknown" },
installations: { state: "multiple" },
skill: {
installed_version: "10",
state: "stale",
remediation: "Run rea setup to update the installed REA skill.",
},
});
});
it("reports a missing installed skill as unhealthy", async () => {
const result = await runDoctor(
undefined,
host({ installedSkillIdentity: () => Promise.resolve(undefined) }),
);
expect(result.healthy).toBe(false);
expect(result.identity?.skill).toMatchObject({
state: "missing",
remediation: "Run rea setup to update the installed REA skill.",
});
expect(result.checks).toContainEqual({
name: "skill:identity",
ok: false,
classification: "config_drift",
detail: "Installed REA skill identity is missing.",
remediation: "Run rea setup to update the installed REA skill.",
});
});
it("reports stale client registration paths without claiming live state", async () => {
const result = await runDoctor(
undefined,
host({
clientRegistrations: () =>
Promise.resolve([
{
client: "codex",
config_path: "/home/user/.codex/config.toml",
command: ["/old/rea", "mcp"],
state: "stale",
remediation:
"Run rea setup to refresh this registration, then restart the client.",
},
]),
}),
);
expect(result.healthy).toBe(false);
expect(result.identity).toMatchObject({
live_server: { state: "unknown" },
registrations: [{ client: "codex", state: "stale" }],
});
expect(result.checks).toContainEqual(
expect.objectContaining({
name: "registration:codex",
classification: "config_drift",
}),
);
});
});
describe("doctor scoped readiness", () => {
it("scopes effective readiness to selected clients without hiding drift", async () => {
const registrations: readonly ClientRegistrationStatus[] = [
{
client: "codex",
config_path: "/home/user/.codex/config.toml",
command: ["/current/rea", "mcp"],
state: "aligned",
remediation: null,
},
{
client: "cursor",
config_path: "/home/user/.cursor/mcp.json",
command: ["/old/rea", "mcp"],
state: "stale",
remediation: "Run rea setup to refresh this registration.",
},
];
const scoped = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve(registrations) }),
{ clients: ["codex"] },
);
expect(scoped).toMatchObject({
healthy: true,
environment_healthy: false,
scope: { mode: "explicit", clients: ["codex"] },
});
expect(scoped.scope_checks).toContainEqual(
expect.objectContaining({ name: "registration:codex", ok: true }),
);
expect(scoped.informational_checks).toContainEqual(
expect.objectContaining({ name: "registration:cursor", ok: false }),
);
const staleSelected = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve(registrations) }),
{ clients: ["cursor"] },
);
expect(staleSelected.healthy).toBe(false);
});
});
describe("Grok Bot readiness", () => {
it("does not treat a Grok Bot data directory as an aligned registration", async () => {
const registrations: readonly ClientRegistrationStatus[] = [
{
client: "grok_bot",
config_path: "/home/user/.grokbot",
command: [],
state: "manual",
remediation: "Add the connector from the Grok Bot chat.",
},
];
const audit = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve(registrations) }),
);
expect(audit.checks.map(({ name }) => name)).not.toContain(
"registration:grok_bot",
);
expect(audit.identity?.registrations).toEqual(registrations);
const scoped = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve(registrations) }),
{ clients: ["grok_bot"] },
);
expect(scoped.healthy).toBe(false);
expect(scoped.scope_checks).toContainEqual(
expect.objectContaining({
name: "registration:grok_bot",
ok: false,
remediation: "Add the connector from the Grok Bot chat.",
}),
);
const absent = await runDoctor(
undefined,
host({ clientRegistrations: () => Promise.resolve([]) }),
{ clients: ["grok_bot"] },
);
expect(absent.scope_checks).toContainEqual(
expect.objectContaining({
name: "registration:grok_bot",
ok: false,
remediation: expect.stringContaining("hosted computer"),
}),
);
});
});
describe("doctor scoped readiness", () => {
it("keeps agent readiness independent of Hopper host requirements", async () => {
const result = await runDoctor(
undefined,
host({
platform: "linux",
linuxDistribution: () =>
Promise.resolve({
id: "debian",
versionId: "13",
packageFamily: "deb",
supported: false,
}),
clientRegistrations: () =>
Promise.resolve([
{
client: "codex",
config_path: "/home/user/.codex/config.toml",
command: ["rea", "mcp"],
state: "aligned",
remediation: null,
},
]),
}),
{ clients: ["codex"] },
);
expect(result).toMatchObject({
healthy: true,
environment_healthy: false,
scope: { mode: "explicit", clients: ["codex"], providers: [] },
});
expect(result.scope_checks.map(({ name }) => name)).toEqual([
"node",
"registration:codex",
]);
expect(result.informational_checks).toContainEqual(
expect.objectContaining({ name: "host", ok: false }),
);
});
it("requires explicitly selected providers instead of any available provider", async () => {
const ghidra = {
id: "ghidra",
configured: true,
available: true,
providerVersion: "12.1.4",
registrationEnvironment: { GHIDRA_INSTALL_DIR: "/tools/ghidra" },
checks: [
{
name: "configuration",
ok: true,
code: null,
detail: "/tools/ghidra",
remediation: null,
classification: "config_drift" as const,
},
],
};
const noHopper = host({
executable: () => Promise.resolve(false),
providerInspections: () => Promise.resolve([ghidra]),
});
const hopper = await runDoctor(undefined, noHopper, {
providers: ["hopper"],
});
expect(hopper).toMatchObject({
healthy: false,
environment_healthy: true,
});
const selectedGhidra = await runDoctor(undefined, noHopper, {
providers: ["ghidra"],
});
expect(selectedGhidra.healthy).toBe(true);
});
});
describe("doctor Linux and Hopper discovery", () => {
it("accepts an officially supported Linux distribution", async () => {
const path = "/home/user/.local/share/rea/hopper/bin/Hopper";
const result = await runDoctor(
undefined,
host({
platform: "linux",
macosVersion: () => Promise.resolve(undefined),
linuxDistribution: () =>
Promise.resolve({
id: "ubuntu",
versionId: "24.04",
packageFamily: "deb",
supported: true,
}),
configuredHopperPath: path,
executable: (candidate) => Promise.resolve(candidate === path),
}),
);
expect(result.healthy).toBe(true);
expect(result.hopperPath).toBe(path);
});
it("reports missing Linux demo-session dependencies", async () => {
const result = await runDoctor(
undefined,
host({
platform: "linux",
macosVersion: () => Promise.resolve(undefined),
linuxDistribution: () =>
Promise.resolve({
id: "ubuntu",
versionId: "24.04",
packageFamily: "deb",
supported: true,
}),
linuxDemoRuntimeCheck: () =>
Promise.resolve({
name: "hopper-demo-runtime",
ok: false,
classification: "missing_dependency",
remediation: "Install Xvfb, xauth, Python 3, libX11, and libXtst.",
}),
}),
);
expect(result.healthy).toBe(false);
expect(
result.checks.find(({ name }) => name === "hopper-demo-runtime"),
).toMatchObject({
ok: false,
classification: "missing_dependency",
remediation: expect.stringContaining("xauth"),
});
});
it("explains how to recover from an unsupported configured launcher", async () => {
const path = "/custom/Hopper";
const result = await runDoctor(
undefined,
host({
platform: "linux",
configuredHopperPath: path,
linuxDistribution: () =>
Promise.resolve({
id: "ubuntu",
versionId: "24.04",
packageFamily: "deb",
supported: true,
}),
executable: (candidate) => Promise.resolve(candidate === path),
supportedLinuxHopper: () => Promise.resolve(false),
}),
);
expect(
result.checks.find(({ name }) => name === "hopper-version")?.remediation,
).toContain("Unset or update HOPPER_LAUNCHER_PATH");
});
it("detects a Homebrew cask installed outside /Applications", async () => {
const path =
"/opt/homebrew/Caskroom/hopper/Hopper Disassembler.app/Contents/MacOS/hopper";
const result = await runDoctor(
undefined,
host({
executable: (candidate) => Promise.resolve(candidate === path),
brewHopperPath: () => Promise.resolve(path),
}),
);
expect(result.hopperPath).toBe(path);
});
it("detects a manually installed Hopper application", async () => {
const path = "/Applications/Hopper v6.app/Contents/MacOS/hopper";
const result = await runDoctor(
undefined,
host({
executable: (candidate) => Promise.resolve(candidate === path),
manualHopperPaths: () => Promise.resolve([path]),
}),
);
expect(result.hopperPath).toBe(path);
});
it("uses shared app target validation when a target is supplied", async () => {
const result = await runDoctor(
"/Applications/Notes.app",
host({
validTarget: (path) =>
Promise.resolve(path === "/Applications/Notes.app"),
}),
);
expect(result.checks).toContainEqual(
expect.objectContaining({
name: "target",
ok: true,
classification: "healthy",
detail: "/Applications/Notes.app",
}),
);
});
it("rejects a readable Hopper launcher without execute permission", async () => {
const path = "/manual/Hopper";
const result = await runDoctor(
undefined,
host({
configuredHopperPath: path,
validTarget: () => Promise.resolve(true),
executable: () => Promise.resolve(false),
}),
);
expect(result.hopperPath).toBeUndefined();
expect(result.checks).toContainEqual(
expect.objectContaining({
name: "hopper",
ok: false,
classification: "config_drift",
}),
);
});
});
describe("doctor target admission diagnostics", () => {
it("preserves route-specific target diagnostics from the host", async () => {
const targetCheck = {
name: "target",
ok: false,
classification: "unsupported_target" as const,
detail: "/fixture/javascript-app",
remediation: "Use analyze_javascript_application with input_path.",
};
const result = await runDoctor(
targetCheck.detail,
host({
inspectTarget: () => Promise.resolve(targetCheck),
validTarget: () => {
throw new Error("the detailed target check should be used");
},
}),
);
expect(result.checks).toContainEqual(targetCheck);
});
});