mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(setup): report the effective ZCode fallback registration
Resolve native-versus-fallback precedence once at the parsed configuration owner. Status and maintenance consume the selected dialect and path, including invalid and disabled fallback registrations. Validation: 28 ZCode boundary and 26 maintenance tests pass. No configuration writes introduced. Based on #3123. Co-authored-by: IvenKooLab <276572622@qq.com>
This commit is contained in:
@@ -172,3 +172,47 @@ export const effectiveClientConfiguration = (
|
||||
}
|
||||
return { ...last, servers, legacyServers: {} };
|
||||
};
|
||||
|
||||
/** Read the configuration ZCode will load, preserving its same-scope fallback rule. */
|
||||
export const readEffectiveClientRegistrationConfiguration = async (
|
||||
client: SetupClient,
|
||||
resources?: ArtifactResourceScope,
|
||||
): Promise<
|
||||
Result<
|
||||
{
|
||||
readonly document: ClientConfigurationDocument | undefined;
|
||||
readonly configPath: string;
|
||||
},
|
||||
ClientConfigurationFileError
|
||||
>
|
||||
> => {
|
||||
const nativeSources = await readClientConfigurationFiles(client, resources);
|
||||
if (!nativeSources.ok) return nativeSources;
|
||||
const nativeDocument = effectiveClientConfiguration(nativeSources.value);
|
||||
const fallbackPath =
|
||||
client.format === "zcode" ? client.zcodeFallbackPath : undefined;
|
||||
if (
|
||||
fallbackPath === undefined ||
|
||||
(nativeDocument !== undefined &&
|
||||
Object.keys(nativeDocument.servers).length > 0)
|
||||
)
|
||||
return ok({ document: nativeDocument, configPath: client.configPath });
|
||||
|
||||
const fallbackClient: SetupClient = {
|
||||
...client,
|
||||
configPath: fallbackPath,
|
||||
configPaths: [fallbackPath],
|
||||
format: "zcode_fallback",
|
||||
};
|
||||
const fallbackSources = await readClientConfigurationFiles(
|
||||
fallbackClient,
|
||||
resources,
|
||||
);
|
||||
if (!fallbackSources.ok) return fallbackSources;
|
||||
const fallbackDocument = effectiveClientConfiguration(fallbackSources.value);
|
||||
return ok(
|
||||
fallbackDocument === undefined
|
||||
? { document: nativeDocument, configPath: client.configPath }
|
||||
: { document: fallbackDocument, configPath: fallbackPath },
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { readClientPolicyBlock } from "./GeminiClientSettings.js";
|
||||
import {
|
||||
effectiveClientConfiguration,
|
||||
readClientConfigurationFiles,
|
||||
} from "./ClientConfigurationFiles.js";
|
||||
import { readEffectiveClientRegistrationConfiguration } from "./ClientConfigurationFiles.js";
|
||||
import {
|
||||
clientServerForcedEnabled,
|
||||
clientServerListedDisabled,
|
||||
@@ -144,24 +141,26 @@ export const readClientRegistrationInspections = async (
|
||||
});
|
||||
continue;
|
||||
}
|
||||
let selectedConfigPath = client.configPath;
|
||||
try {
|
||||
const files = await readClientConfigurationFiles(
|
||||
const configuration = await readEffectiveClientRegistrationConfiguration(
|
||||
client,
|
||||
options.resources,
|
||||
);
|
||||
if (!files.ok) {
|
||||
if (!configuration.ok) {
|
||||
record({
|
||||
client: client.name,
|
||||
config_path: client.configPath,
|
||||
config_path: configuration.error.path,
|
||||
command: [],
|
||||
state: "invalid",
|
||||
remediation: files.error.detail,
|
||||
remediation: configuration.error.detail,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
const parsed = effectiveClientConfiguration(files.value);
|
||||
const { document: parsed, configPath } = configuration.value;
|
||||
selectedConfigPath = configPath;
|
||||
if (parsed === undefined) {
|
||||
record(unavailableStatus(client, "missing"));
|
||||
record(unavailableStatus(client, "missing", configPath));
|
||||
continue;
|
||||
}
|
||||
const policy = await readClientPolicyBlock(
|
||||
@@ -173,7 +172,7 @@ export const readClientRegistrationInspections = async (
|
||||
if (!policy.ok) {
|
||||
record({
|
||||
client: client.name,
|
||||
config_path: client.configPath,
|
||||
config_path: configPath,
|
||||
command: [],
|
||||
state: "invalid",
|
||||
remediation: policy.error.detail,
|
||||
@@ -190,17 +189,26 @@ export const readClientRegistrationInspections = async (
|
||||
if (editabilityError !== undefined) {
|
||||
record({
|
||||
client: client.name,
|
||||
config_path: client.configPath,
|
||||
config_path: configPath,
|
||||
command: [],
|
||||
state: "invalid",
|
||||
remediation: editabilityError.message,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
record(unavailableStatus(client, "missing"));
|
||||
record(unavailableStatus(client, "missing", configPath));
|
||||
continue;
|
||||
}
|
||||
const registration = parseRegistration(raw, client);
|
||||
const effectiveClient: SetupClient =
|
||||
parsed.dialect === "zcode_fallback"
|
||||
? {
|
||||
...client,
|
||||
configPath,
|
||||
configPaths: [configPath],
|
||||
format: "zcode_fallback",
|
||||
}
|
||||
: client;
|
||||
const registration = parseRegistration(raw, effectiveClient);
|
||||
const command: RegistrationCommand = [
|
||||
registration.command,
|
||||
...registration.args,
|
||||
@@ -213,13 +221,13 @@ export const readClientRegistrationInspections = async (
|
||||
packageVersion !== PRODUCT_IDENTITY.packageVersion;
|
||||
record(
|
||||
configuredStatus(
|
||||
client,
|
||||
effectiveClient,
|
||||
command,
|
||||
activation === "enabled" &&
|
||||
!packageVersionDrift &&
|
||||
registrationAligned(
|
||||
registration,
|
||||
client,
|
||||
effectiveClient,
|
||||
currentCommandPath,
|
||||
options.platform ?? process.platform,
|
||||
) &&
|
||||
@@ -237,7 +245,11 @@ export const readClientRegistrationInspections = async (
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
record(
|
||||
unavailableStatus(client, isMissing(cause) ? "missing" : "invalid"),
|
||||
unavailableStatus(
|
||||
client,
|
||||
isMissing(cause) ? "missing" : "invalid",
|
||||
selectedConfigPath,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -254,7 +266,8 @@ const registrationActivation = (
|
||||
(client.format !== "grok" && registration.disabled === true) ||
|
||||
(registration.enabled === false &&
|
||||
!clientServerForcedEnabled(parsed, PRODUCT_IDENTITY.mcpServerKey)) ||
|
||||
(client.format === "zcode" && registration.enable === false) ||
|
||||
((parsed.dialect === "zcode" || parsed.dialect === "zcode_fallback") &&
|
||||
registration.enable === false) ||
|
||||
clientServerListedDisabled(parsed, PRODUCT_IDENTITY.mcpServerKey)
|
||||
? "disabled"
|
||||
: "enabled";
|
||||
@@ -435,9 +448,10 @@ const packageVersionRemediation = (
|
||||
const unavailableStatus = (
|
||||
client: SetupClient,
|
||||
state: "missing" | "invalid",
|
||||
configPath: string = client.configPath,
|
||||
): ClientRegistrationStatus => ({
|
||||
client: client.name,
|
||||
config_path: client.configPath,
|
||||
config_path: configPath,
|
||||
command: [],
|
||||
state,
|
||||
remediation: remediation(client),
|
||||
|
||||
@@ -457,6 +457,115 @@ describe("ZCode disabled registrations", () => {
|
||||
).toBe("configured");
|
||||
expect(await statusFor()).toMatchObject({ state: "aligned", command });
|
||||
});
|
||||
|
||||
it("reports the effective same-scope fallback registration and source path", async () => {
|
||||
const { home, client } = await zcodeClient();
|
||||
const fallback = client.zcodeFallbackPath;
|
||||
if (fallback === undefined) throw new Error("missing ZCode fallback path");
|
||||
await mkdir(join(home, ".agents"), { recursive: true });
|
||||
await writeFile(
|
||||
fallback,
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
rea: { command: command[0], args: command.slice(1) },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
(
|
||||
await readClientRegistrationStatuses(home, undefined, {
|
||||
platform: process.platform,
|
||||
environment: {},
|
||||
})
|
||||
).find(({ client }) => client === "zcode"),
|
||||
).toMatchObject({
|
||||
config_path: fallback,
|
||||
command,
|
||||
state: "aligned",
|
||||
});
|
||||
});
|
||||
|
||||
it("reports a selected fallback parse failure as invalid at its source path", async () => {
|
||||
const { home, client } = await zcodeClient();
|
||||
const fallback = client.zcodeFallbackPath;
|
||||
if (fallback === undefined) throw new Error("missing ZCode fallback path");
|
||||
await mkdir(join(home, ".agents"), { recursive: true });
|
||||
await writeFile(fallback, "{ malformed");
|
||||
|
||||
expect(
|
||||
(
|
||||
await readClientRegistrationStatuses(home, undefined, {
|
||||
platform: process.platform,
|
||||
environment: {},
|
||||
})
|
||||
).find(({ client }) => client === "zcode"),
|
||||
).toMatchObject({ config_path: fallback, state: "invalid" });
|
||||
});
|
||||
|
||||
it("reports disabled fallback registrations as stale at the fallback path", async () => {
|
||||
const { home, client } = await zcodeClient();
|
||||
const fallback = client.zcodeFallbackPath;
|
||||
if (fallback === undefined) throw new Error("missing ZCode fallback path");
|
||||
await mkdir(join(home, ".agents"), { recursive: true });
|
||||
await writeFile(
|
||||
fallback,
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
rea: { command: command[0], args: command.slice(1), enable: false },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
(
|
||||
await readClientRegistrationStatuses(home, undefined, {
|
||||
platform: process.platform,
|
||||
environment: {},
|
||||
})
|
||||
).find(({ client }) => client === "zcode"),
|
||||
).toMatchObject({
|
||||
config_path: fallback,
|
||||
command,
|
||||
state: "stale",
|
||||
});
|
||||
});
|
||||
|
||||
it("prefers a nonempty native server table over the fallback", async () => {
|
||||
const { home, client } = await zcodeClient();
|
||||
const fallback = client.zcodeFallbackPath;
|
||||
if (fallback === undefined) throw new Error("missing ZCode fallback path");
|
||||
await mkdir(join(home, ".agents"), { recursive: true });
|
||||
await writeFile(
|
||||
fallback,
|
||||
JSON.stringify({
|
||||
mcpServers: {
|
||||
rea: { command: "fallback-command", args: ["mcp"] },
|
||||
},
|
||||
}),
|
||||
);
|
||||
await writeFile(
|
||||
client.configPath,
|
||||
JSON.stringify({
|
||||
mcp: {
|
||||
servers: { rea: { command: command[0], args: command.slice(1) } },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(
|
||||
(
|
||||
await readClientRegistrationStatuses(home, undefined, {
|
||||
platform: process.platform,
|
||||
environment: {},
|
||||
})
|
||||
).find(({ client }) => client === "zcode"),
|
||||
).toMatchObject({
|
||||
config_path: client.configPath,
|
||||
command,
|
||||
state: "aligned",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("ZCode strict native JSON", () => {
|
||||
|
||||
@@ -146,6 +146,22 @@ describe("maintenance activation and layered configuration", () => {
|
||||
expect(await readFile(configPath, "utf8")).toBe(original);
|
||||
});
|
||||
|
||||
it("selects a ZCode registration loaded from the same-scope fallback", async () => {
|
||||
await writeClient("zcode", JSON.stringify({ mcp: { servers: {} } }));
|
||||
const fallbackPath = join(home, ".agents", "mcp.json");
|
||||
await mkdir(join(home, ".agents"), { recursive: true });
|
||||
await writeFile(
|
||||
fallbackPath,
|
||||
JSON.stringify({
|
||||
mcpServers: { rea: { command: entryPoint, args: ["mcp"] } },
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
existingMaintenanceScope(home, entryPoint, process.env),
|
||||
).resolves.toMatchObject({ clients: ["zcode"] });
|
||||
});
|
||||
|
||||
it("selects an enabled registration inherited through layered OpenCode files", async () => {
|
||||
const directory = join(home, ".config", "opencode");
|
||||
await mkdir(directory, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user