fix(setup): configure every detected client

This commit is contained in:
morluto
2026-07-14 22:25:43 +00:00
3 changed files with 42 additions and 8 deletions
+12 -4
View File
@@ -437,7 +437,8 @@ try {
)
throw new Error("packaged stale-skill upgrade was not isolated");
await writeFile(cursorConfig, "malformed");
await writeFile(claudeConfig, "malformed");
await writeFile(cursorConfig, "{}\n");
const failedExecution = await runWithStatus(
cli,
["setup", "--yes", "--json"],
@@ -447,10 +448,17 @@ try {
if (
failed.status !== "needs_human" ||
failedExecution.status !== 1 ||
(await readFile(cursorConfig, "utf8")) !== "malformed"
failed.clients?.claude_desktop?.status !== "failed" ||
failed.clients?.cursor?.status !== "configured" ||
!failed.appliedActions.includes("configured_cursor") ||
(await readFile(claudeConfig, "utf8")) !== "malformed" ||
json(await readFile(cursorConfig, "utf8")).mcpServers?.rea?.command !==
cli
)
throw new Error("packaged setup failure recovery did not preserve input");
await writeFile(cursorConfig, '{"existing":true}\n');
throw new Error(
`packaged setup did not continue after an earlier client failure: ${JSON.stringify(failed)}`,
);
await writeFile(claudeConfig, "{}\n");
const recoveredExecution = await runWithStatus(
cli,
["setup", "--yes", "--json"],
+5 -4
View File
@@ -16,7 +16,7 @@ const failedConfigurationMessage = (
return "Agent configuration could not be verified after writing. Repair the configuration file or restore its `.rea.backup`, then rerun setup.";
};
/** Configure each detected agent, stopping after the first failed transaction. */
/** Configure every detected agent and report the first failed transaction. */
export const configureDetectedClients = async (options: {
readonly host: SetupHost;
readonly detectedClients: readonly SetupClient[];
@@ -25,6 +25,7 @@ export const configureDetectedClients = async (options: {
readonly clients: Record<string, ClientConfigurationResult>;
readonly appliedActions: string[];
}): Promise<string | undefined> => {
let failure: string | undefined;
for (const client of options.detectedClients) {
const result = await options.host.configureClient(
client,
@@ -33,9 +34,9 @@ export const configureDetectedClients = async (options: {
);
options.clients[client.name] = result;
if (result.status === "failed")
return failedConfigurationMessage(result.reason);
if (result.status === "configured")
failure ??= failedConfigurationMessage(result.reason);
else if (result.status === "configured")
options.appliedActions.push(`configured_${client.name}`);
}
return undefined;
return failure;
};
+25
View File
@@ -301,6 +301,31 @@ describe("setup workflow", () => {
expect(result.remediation).not.toContain("internal_client_key");
});
it("records every detected client outcome after an earlier failure", async () => {
const host = new FakeSetupHost();
host.hopper = "/Applications/Hopper";
host.clients = [
{ name: "first", configPath: "/first.json" },
{ name: "second", configPath: "/second.json" },
{ name: "third", configPath: "/third.json" },
];
host.clientResults.set("first", { status: "failed", reason: "write" });
host.clientResults.set("second", { status: "configured" });
host.clientResults.set("third", { status: "failed", reason: "readback" });
const result = await runSetup(options(true), host);
expect(host.configurations).toBe(3);
expect(result.clients).toEqual({
first: { status: "failed", reason: "write" },
second: { status: "configured" },
third: { status: "failed", reason: "readback" },
});
expect(result.appliedActions).toEqual(["configured_second"]);
expect(result.status).toBe("needs_human");
expect(result.remediation).toContain("could not be updated");
});
it("explains skill installation recovery", async () => {
const host = new FakeSetupHost();
host.hopper = "/Applications/Hopper";