mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
refactor(setup): take the platform in client configuration readers and the setup write path (#11120)
* refactor(setup): take the platform in client configuration readers and the setup write path ClientRegistrationEnvironment, ClientConfigurationFiles and SupportedClients take a required platform (before defaulted parameters). Setup's configure, inspect and alignment entry points take the setup host's platform and pass it through every layer read, ownership check and the Cordis serializer, which now aliases Windows environment names by the selected platform instead of the running host. Host file operations in ClientConfigPath, ClientConfigurationPublication, JsonFiles and rea mcp add read HOST_PLATFORM explicitly. * fix(setup): keep private-profile modes on the host when setup selects another platform
This commit is contained in:
1 parent
bb445d8691
commit
3e4e178ccc
37 files changed
+548
-212
No files matched your search
@@ -109,6 +109,8 @@ export const configureClientConfiguration = async (
|
||||
environment: SetupProviderEnvironment = {},
|
||||
command: readonly string[] = currentClientRegistrationCommand(),
|
||||
resolveProviderRequestBudget?: ProviderRequestBudgetResolver,
|
||||
// Setup writes the running host's client files unless its host selects one.
|
||||
platform: NodeJS.Platform = HOST_PLATFORM.platform,
|
||||
): Promise<ClientConfigurationResult> => {
|
||||
if (command.length === 0)
|
||||
return {
|
||||
@@ -127,7 +129,7 @@ export const configureClientConfiguration = async (
|
||||
try {
|
||||
return await withClientConfigurationTransaction(transactionPath, () =>
|
||||
configureClientDocument(
|
||||
{ client, transactionPath },
|
||||
{ client, transactionPath, platform },
|
||||
environment,
|
||||
command,
|
||||
resolveProviderRequestBudget,
|
||||
@@ -148,7 +150,12 @@ const configureClientDocument = async (
|
||||
{
|
||||
client,
|
||||
transactionPath,
|
||||
}: { readonly client: SetupClient; readonly transactionPath: string },
|
||||
platform,
|
||||
}: {
|
||||
readonly client: SetupClient;
|
||||
readonly transactionPath: string;
|
||||
readonly platform: NodeJS.Platform;
|
||||
},
|
||||
environment: SetupProviderEnvironment,
|
||||
command: readonly string[],
|
||||
resolveProviderRequestBudget?: ProviderRequestBudgetResolver,
|
||||
@@ -159,7 +166,7 @@ const configureClientDocument = async (
|
||||
const files = await readClientConfigurationFiles(
|
||||
client,
|
||||
undefined,
|
||||
undefined,
|
||||
platform,
|
||||
environment,
|
||||
);
|
||||
if (!files.ok) {
|
||||
@@ -204,6 +211,7 @@ const configureClientDocument = async (
|
||||
parsed,
|
||||
original ?? "",
|
||||
files.value,
|
||||
platform,
|
||||
);
|
||||
if (ownershipBlocker !== undefined)
|
||||
return {
|
||||
@@ -220,6 +228,7 @@ const configureClientDocument = async (
|
||||
command,
|
||||
parsed,
|
||||
files.value,
|
||||
platform,
|
||||
resolveProviderRequestBudget,
|
||||
),
|
||||
);
|
||||
@@ -235,7 +244,7 @@ const configureClientDocument = async (
|
||||
void cause;
|
||||
return { status: "failed", reason: "readback" };
|
||||
}
|
||||
const fallback = await readZcodeFallbackServers(client, parsed);
|
||||
const fallback = await readZcodeFallbackServers(client, platform, parsed);
|
||||
if (!fallback.ok) {
|
||||
if (fallback.error.kind !== "registration_ownership")
|
||||
throwCleanupFailure(fallback.error);
|
||||
@@ -256,11 +265,17 @@ const configureClientDocument = async (
|
||||
servers: { ...fallback.value, ...parsed.servers },
|
||||
},
|
||||
files.value,
|
||||
platform,
|
||||
resolveProviderRequestBudget,
|
||||
);
|
||||
// The settings written now can differ from the ones inspection admitted,
|
||||
// for example a Hopper path set by an installation earlier in this setup.
|
||||
const override = inlineOverrideBlocker(client, files.value, desired);
|
||||
const override = inlineOverrideBlocker(
|
||||
client,
|
||||
files.value,
|
||||
desired,
|
||||
platform,
|
||||
);
|
||||
if (override !== undefined)
|
||||
return { status: "failed", reason: "read_only_override", detail: override };
|
||||
if (
|
||||
@@ -277,6 +292,7 @@ const configureClientDocument = async (
|
||||
parsed,
|
||||
desired,
|
||||
fallbackServers: fallback.value,
|
||||
platform,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -291,6 +307,7 @@ interface ClientConfigurationUpdate {
|
||||
readonly parsed: ClientConfigurationDocument;
|
||||
readonly desired: unknown;
|
||||
readonly fallbackServers: Readonly<Record<string, unknown>>;
|
||||
readonly platform: NodeJS.Platform;
|
||||
}
|
||||
|
||||
const persistClientConfiguration = async ({
|
||||
@@ -302,6 +319,7 @@ const persistClientConfiguration = async ({
|
||||
parsed,
|
||||
desired,
|
||||
fallbackServers,
|
||||
platform,
|
||||
}: ClientConfigurationUpdate): Promise<ClientConfigurationResult> => {
|
||||
// The plan chose this file; an MSIX package may have switched to its
|
||||
// private copy since, for example when it quit.
|
||||
@@ -335,6 +353,7 @@ const persistClientConfiguration = async ({
|
||||
...parsed.servers,
|
||||
[PRODUCT_IDENTITY.mcpServerKey]: desired,
|
||||
},
|
||||
platform,
|
||||
PRODUCT_IDENTITY.mcpServerKey,
|
||||
);
|
||||
// OMP's denylist would hide the registration; keep every other listed name.
|
||||
@@ -409,6 +428,7 @@ const persistClientConfiguration = async ({
|
||||
? { mode: PRIVATE_PROFILE_MODES.directory }
|
||||
: {}),
|
||||
});
|
||||
// File modes are this host's, whatever platform the registration targets.
|
||||
if (
|
||||
client.privateProfile !== undefined &&
|
||||
HOST_PLATFORM.platform !== "win32"
|
||||
@@ -519,20 +539,22 @@ const readClientRefresh = async (
|
||||
providerEnvironment: SetupProviderEnvironment,
|
||||
command: readonly string[],
|
||||
{
|
||||
platform,
|
||||
resolveProviderRequestBudget,
|
||||
resources,
|
||||
currentCommandPath,
|
||||
}: {
|
||||
readonly platform: NodeJS.Platform;
|
||||
readonly resolveProviderRequestBudget?: ProviderRequestBudgetResolver;
|
||||
readonly resources?: ArtifactResourceScope;
|
||||
/** The entry point whose registrations setup owns; update names the new one. */
|
||||
readonly currentCommandPath?: string;
|
||||
} = {},
|
||||
},
|
||||
): Promise<Result<ClientRefresh, ClientConfigurationFileError>> => {
|
||||
const files = await readClientConfigurationFiles(
|
||||
client,
|
||||
resources,
|
||||
undefined,
|
||||
platform,
|
||||
providerEnvironment,
|
||||
);
|
||||
if (!files.ok) {
|
||||
@@ -561,6 +583,7 @@ const readClientRefresh = async (
|
||||
command,
|
||||
parsed,
|
||||
files.value,
|
||||
platform,
|
||||
resolveProviderRequestBudget,
|
||||
);
|
||||
const target =
|
||||
@@ -570,6 +593,7 @@ const readClientRefresh = async (
|
||||
files.value,
|
||||
parsed,
|
||||
original,
|
||||
platform,
|
||||
currentCommandPath,
|
||||
);
|
||||
if (target !== undefined)
|
||||
@@ -584,7 +608,7 @@ const readClientRefresh = async (
|
||||
client,
|
||||
files.value,
|
||||
desired,
|
||||
HOST_PLATFORM.platform,
|
||||
platform,
|
||||
);
|
||||
if (overrideOrigin !== undefined)
|
||||
return ok({
|
||||
@@ -626,6 +650,8 @@ export const clientConfigurationAligned = async (
|
||||
providerEnvironment: SetupProviderEnvironment,
|
||||
command: readonly string[],
|
||||
resolveProviderRequestBudget?: ProviderRequestBudgetResolver,
|
||||
// Setup reads the running host's client files unless its host selects one.
|
||||
platform: NodeJS.Platform = HOST_PLATFORM.platform,
|
||||
): Promise<boolean> => {
|
||||
if (client.configPathError !== undefined) return false;
|
||||
try {
|
||||
@@ -634,8 +660,8 @@ export const clientConfigurationAligned = async (
|
||||
providerEnvironment,
|
||||
command,
|
||||
resolveProviderRequestBudget === undefined
|
||||
? {}
|
||||
: { resolveProviderRequestBudget },
|
||||
? { platform }
|
||||
: { platform, resolveProviderRequestBudget },
|
||||
);
|
||||
return (
|
||||
refresh.ok &&
|
||||
@@ -698,6 +724,7 @@ export const clientRefreshRefusal = async (
|
||||
providerEnvironment,
|
||||
command,
|
||||
{
|
||||
platform,
|
||||
currentCommandPath,
|
||||
...(resources === undefined ? {} : { resources }),
|
||||
},
|
||||
@@ -782,6 +809,7 @@ const discoveredInlineSettings = async (
|
||||
command,
|
||||
refresh.parsed,
|
||||
refresh.sources,
|
||||
platform,
|
||||
);
|
||||
return inlineRegistrationOverride(
|
||||
client,
|
||||
@@ -799,6 +827,8 @@ export const inspectClientConfiguration = async (
|
||||
providerEnvironment: SetupProviderEnvironment,
|
||||
command: readonly string[],
|
||||
resolveProviderRequestBudget?: ProviderRequestBudgetResolver,
|
||||
// Setup reads the running host's client files unless its host selects one.
|
||||
platform: NodeJS.Platform = HOST_PLATFORM.platform,
|
||||
): Promise<ClientConfigurationInspection> => {
|
||||
let inspection: ClientConfigurationInspection;
|
||||
try {
|
||||
@@ -806,6 +836,7 @@ export const inspectClientConfiguration = async (
|
||||
client,
|
||||
providerEnvironment,
|
||||
command,
|
||||
platform,
|
||||
resolveProviderRequestBudget,
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
@@ -828,6 +859,7 @@ const inspectClientDocument = async (
|
||||
client: SetupClient,
|
||||
providerEnvironment: SetupProviderEnvironment,
|
||||
command: readonly string[],
|
||||
platform: NodeJS.Platform,
|
||||
resolveProviderRequestBudget?: ProviderRequestBudgetResolver,
|
||||
): Promise<ClientConfigurationInspection> => {
|
||||
if (client.configPathError !== undefined)
|
||||
@@ -836,7 +868,7 @@ const inspectClientDocument = async (
|
||||
const files = await readClientConfigurationFiles(
|
||||
client,
|
||||
undefined,
|
||||
undefined,
|
||||
platform,
|
||||
providerEnvironment,
|
||||
);
|
||||
if (!files.ok) {
|
||||
@@ -869,6 +901,7 @@ const inspectClientDocument = async (
|
||||
files.value,
|
||||
undefined,
|
||||
undefined,
|
||||
platform,
|
||||
);
|
||||
if (ownershipBlocker !== undefined)
|
||||
return { status: "invalid", remediation: ownershipBlocker };
|
||||
@@ -878,9 +911,15 @@ const inspectClientDocument = async (
|
||||
command,
|
||||
undefined,
|
||||
files.value,
|
||||
platform,
|
||||
resolveProviderRequestBudget,
|
||||
);
|
||||
const override = inlineOverrideBlocker(client, files.value, desired);
|
||||
const override = inlineOverrideBlocker(
|
||||
client,
|
||||
files.value,
|
||||
desired,
|
||||
platform,
|
||||
);
|
||||
if (override !== undefined)
|
||||
return { status: "invalid", remediation: override };
|
||||
// This read establishes absence; do not reuse an earlier user-file policy.
|
||||
@@ -894,7 +933,7 @@ const inspectClientDocument = async (
|
||||
}
|
||||
if (policy.value !== undefined)
|
||||
return { status: "invalid", remediation: policy.value };
|
||||
const fallback = await readZcodeFallbackServers(client);
|
||||
const fallback = await readZcodeFallbackServers(client, platform);
|
||||
if (!fallback.ok) {
|
||||
if (fallback.error.kind !== "registration_ownership")
|
||||
throwCleanupFailure(fallback.error);
|
||||
@@ -917,6 +956,7 @@ const inspectClientDocument = async (
|
||||
files.value,
|
||||
parsed,
|
||||
original,
|
||||
platform,
|
||||
);
|
||||
if (ownershipBlocker !== undefined)
|
||||
return { status: "invalid", remediation: ownershipBlocker };
|
||||
@@ -926,9 +966,15 @@ const inspectClientDocument = async (
|
||||
command,
|
||||
parsed,
|
||||
files.value,
|
||||
platform,
|
||||
resolveProviderRequestBudget,
|
||||
);
|
||||
const override = inlineOverrideBlocker(client, files.value, desired);
|
||||
const override = inlineOverrideBlocker(
|
||||
client,
|
||||
files.value,
|
||||
desired,
|
||||
platform,
|
||||
);
|
||||
if (override !== undefined)
|
||||
return { status: "invalid", remediation: override };
|
||||
const policy = await readClientPolicyBlock(client, parsed, desired);
|
||||
@@ -946,7 +992,7 @@ const inspectClientDocument = async (
|
||||
(await privateProfileCurrent(client, original))
|
||||
)
|
||||
return { status: "already_current" };
|
||||
const fallback = await readZcodeFallbackServers(client, parsed);
|
||||
const fallback = await readZcodeFallbackServers(client, platform, parsed);
|
||||
if (!fallback.ok) {
|
||||
if (fallback.error.kind !== "registration_ownership")
|
||||
throwCleanupFailure(fallback.error);
|
||||
@@ -993,14 +1039,9 @@ const inlineOverrideBlocker = (
|
||||
client: SetupClient,
|
||||
sources: readonly ClientConfigurationSource[],
|
||||
desired: unknown,
|
||||
platform: NodeJS.Platform,
|
||||
): string | undefined => {
|
||||
// Setup writes and its client reads this file on the running host.
|
||||
const origin = inlineRegistrationOverride(
|
||||
client,
|
||||
sources,
|
||||
desired,
|
||||
HOST_PLATFORM.platform,
|
||||
);
|
||||
const origin = inlineRegistrationOverride(client, sources, desired, platform);
|
||||
return origin === undefined
|
||||
? undefined
|
||||
: inlineOverrideRemediation(client, origin);
|
||||
@@ -1034,6 +1075,7 @@ const writeTargetOwnershipBlocker = (
|
||||
sources: readonly ClientConfigurationSource[],
|
||||
parsed: ClientConfigurationDocument | undefined,
|
||||
original: string | undefined,
|
||||
platform: NodeJS.Platform,
|
||||
currentCommandPath?: string,
|
||||
): string | undefined => {
|
||||
if (parsed !== undefined)
|
||||
@@ -1042,12 +1084,14 @@ const writeTargetOwnershipBlocker = (
|
||||
parsed,
|
||||
original ?? "",
|
||||
sources,
|
||||
platform,
|
||||
currentCommandPath,
|
||||
);
|
||||
const inherited = effectiveClientConfiguration(sources, {
|
||||
client,
|
||||
document: undefined,
|
||||
});
|
||||
const inherited = effectiveClientConfiguration(
|
||||
sources,
|
||||
{ client, document: undefined },
|
||||
platform,
|
||||
);
|
||||
return inherited === undefined
|
||||
? undefined
|
||||
: registrationOwnershipBlocker(
|
||||
@@ -1055,6 +1099,7 @@ const writeTargetOwnershipBlocker = (
|
||||
inherited,
|
||||
"",
|
||||
sources,
|
||||
platform,
|
||||
currentCommandPath,
|
||||
);
|
||||
};
|
||||
@@ -1064,17 +1109,19 @@ const registrationOwnershipBlocker = (
|
||||
client: SetupClient,
|
||||
parsed: ClientConfigurationDocument,
|
||||
original: string,
|
||||
sources: readonly ClientConfigurationSource[] = [],
|
||||
sources: readonly ClientConfigurationSource[],
|
||||
platform: NodeJS.Platform,
|
||||
currentCommandPath?: string,
|
||||
): string | undefined => {
|
||||
// Cordis owns registrations by stable entry ID, including malformed launchers.
|
||||
if (parsed.dialect === "dsh") return undefined;
|
||||
const name = PRODUCT_IDENTITY.mcpServerKey;
|
||||
const entries: { value: unknown; paths: readonly string[] }[] = [];
|
||||
const effective = effectiveClientConfiguration(sources, {
|
||||
client,
|
||||
document: parsed,
|
||||
});
|
||||
const effective = effectiveClientConfiguration(
|
||||
sources,
|
||||
{ client, document: parsed },
|
||||
platform,
|
||||
);
|
||||
if (effective !== undefined && Object.hasOwn(effective.servers, name))
|
||||
entries.push({
|
||||
value: effective.servers[name],
|
||||
@@ -1121,6 +1168,7 @@ const registrationOwnershipBlocker = (
|
||||
/** Admit the fallback only when the first native ZCode server would suppress it. */
|
||||
const readZcodeFallbackServers = async (
|
||||
client: SetupClient,
|
||||
platform: NodeJS.Platform,
|
||||
parsed?: ClientConfigurationDocument,
|
||||
): Promise<
|
||||
Result<
|
||||
@@ -1155,6 +1203,8 @@ const readZcodeFallbackServers = async (
|
||||
{ ...client, configPath: path, format: "zcode_fallback" },
|
||||
parsed,
|
||||
source.value,
|
||||
[],
|
||||
platform,
|
||||
);
|
||||
if (ownershipBlocker !== undefined)
|
||||
return err({
|
||||
@@ -1162,7 +1212,7 @@ const readZcodeFallbackServers = async (
|
||||
path,
|
||||
detail: ownershipBlocker,
|
||||
});
|
||||
const environment = clientRegistrationEnvironment(parsed, client);
|
||||
const environment = clientRegistrationEnvironment(parsed, client, platform);
|
||||
if (!environment.ok)
|
||||
return err({
|
||||
kind: "malformed",
|
||||
@@ -1262,7 +1312,11 @@ const registrationCurrent = (
|
||||
!Object.hasOwn(parsed.legacyServers, PRODUCT_IDENTITY.mcpServerKey) &&
|
||||
!clientServerListedDisabled(parsed, PRODUCT_IDENTITY.mcpServerKey);
|
||||
|
||||
/** A private profile with the wrong modes needs publication even when its row is current. */
|
||||
/**
|
||||
* A private profile with the wrong modes needs publication even when its row
|
||||
* is current. Modes belong to the host holding the file, not to the platform
|
||||
* the registration is written for.
|
||||
*/
|
||||
const privateProfileCurrent = async (
|
||||
client: SetupClient,
|
||||
original: string | undefined,
|
||||
@@ -1280,12 +1334,14 @@ const clientConfigurationDesired = async (
|
||||
command: readonly string[],
|
||||
parsed: ClientConfigurationDocument | undefined,
|
||||
sources: readonly ClientConfigurationSource[],
|
||||
platform: NodeJS.Platform,
|
||||
resolveProviderRequestBudget?: ProviderRequestBudgetResolver,
|
||||
): Promise<unknown> => {
|
||||
const effective = effectiveClientConfiguration(sources, {
|
||||
client,
|
||||
document: parsed,
|
||||
});
|
||||
const effective = effectiveClientConfiguration(
|
||||
sources,
|
||||
{ client, document: parsed },
|
||||
platform,
|
||||
);
|
||||
const format = clientWriteDialect(
|
||||
parsed?.dialect ?? client.format ?? "json",
|
||||
sources,
|
||||
@@ -1296,6 +1352,7 @@ const clientConfigurationDesired = async (
|
||||
const validated = clientRegistrationEnvironment(
|
||||
effective,
|
||||
client,
|
||||
platform,
|
||||
providerEnvironment,
|
||||
);
|
||||
if (!validated.ok) throw validated.error;
|
||||
@@ -1303,7 +1360,7 @@ const clientConfigurationDesired = async (
|
||||
parsed === undefined
|
||||
? new DshCordisDocument("")
|
||||
: dshDocument(parsed.document)
|
||||
).desired(launcher, providerEnvironment, HOST_PLATFORM.platform);
|
||||
).desired(launcher, providerEnvironment, platform);
|
||||
if (!desired.ok) throw desired.error;
|
||||
return desired.value;
|
||||
}
|
||||
@@ -1315,6 +1372,7 @@ const clientConfigurationDesired = async (
|
||||
client.name === "codex" || client.name === "kimi"
|
||||
? resolveProviderRequestBudget
|
||||
: undefined,
|
||||
platform,
|
||||
);
|
||||
if (!validated.ok) throw validated.error;
|
||||
// Preserve explicitly configured server settings, never the ambient process
|
||||
@@ -1323,6 +1381,7 @@ const clientConfigurationDesired = async (
|
||||
const environment = clientRegistrationEnvironment(
|
||||
parsed,
|
||||
client,
|
||||
platform,
|
||||
providerEnvironment,
|
||||
"fragment",
|
||||
);
|
||||
@@ -1346,6 +1405,7 @@ const clientConfigurationDesired = async (
|
||||
sources,
|
||||
effective,
|
||||
providerEnvironment,
|
||||
platform,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1371,13 +1431,15 @@ const withInheritedGooseSettings = (
|
||||
sources: readonly ClientConfigurationSource[],
|
||||
effective: ClientConfigurationDocument | undefined,
|
||||
providerEnvironment: SetupProviderEnvironment,
|
||||
platform: NodeJS.Platform,
|
||||
): Record<string, unknown> => {
|
||||
if (format !== "goose") return withoutInherited;
|
||||
// The layers below the user file, which the user entry overlays field by field.
|
||||
const lower = effectiveClientConfiguration(sources, {
|
||||
client,
|
||||
document: undefined,
|
||||
});
|
||||
const lower = effectiveClientConfiguration(
|
||||
sources,
|
||||
{ client, document: undefined },
|
||||
platform,
|
||||
);
|
||||
const declares = (
|
||||
document: ClientConfigurationDocument | undefined,
|
||||
keys: readonly string[],
|
||||
@@ -1422,6 +1484,7 @@ const withInheritedGooseSettings = (
|
||||
const inherited = clientRegistrationEnvironment(
|
||||
effective,
|
||||
client,
|
||||
platform,
|
||||
providerEnvironment,
|
||||
"fragment",
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user