From 6092f6da7eaa78956a9e3987cd9185e5020b7f07 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 13 Oct 2026 20:26:21 +0800 Subject: [PATCH] fix(setup): require product evidence for FreeBuff discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review findings at their producing boundaries and preserve directly affected consumers. Source inspection and git diff --check completed. Tests and automated checks were not run at the user’s explicit request. --- docs/client-configuration.md | 12 ++++++--- docs/client-verification.md | 5 ++++ src/application/SupportedClients.ts | 26 ++++++++++++++----- .../freebuffClientConfiguration.test.ts | 14 ++++++++++ 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/docs/client-configuration.md b/docs/client-configuration.md index 5d7f48321c..a99a4f7003 100644 --- a/docs/client-configuration.md +++ b/docs/client-configuration.md @@ -312,16 +312,22 @@ writing a registration ZCode cannot load. ## FreeBuff -For FreeBuff, setup writes `mcpServers.rea` into the user-scope +For FreeBuff, the proposed integration writes `mcpServers.rea` into the user-scope `~/.agents/mcp.json` shared-agent file. Entries carry `command`, `args`, and -`env`; stdio is the default transport. FreeBuff discovers personal skills from +`env`; stdio is the default transport. The proposed personal skill location is `~/.agents/skills`, so setup installs its skill copy there. Because the same file doubles as ZCode's user-scope fallback (see above), a `freebuff` -registration also loads in ZCode until ZCode defines its own native server. +registration is also in the file ZCode uses until ZCode defines its own native server. Setup and doctor detect FreeBuff from its own profile directory (`~/.config/freebuff-desktop`), not from the shared `.agents` folder that REA's skill installs create for every client. +The configuration write and personal skill locations are proposed integration +boundaries. FreeBuff MCP loading and skill discovery have not been verified +with an installed native FreeBuff client/version; filesystem tests cover REA's +editing, discovery, and ownership behavior only. Automatic applicability +requires the FreeBuff profile directory even when the shared MCP file exists. + FreeBuff is main-only and is not part of npm 6.4.0; `rea setup --client freebuff` against `@latest` reports an unsupported client until a release containing it is published. diff --git a/docs/client-verification.md b/docs/client-verification.md index 575cc61d24..f9f3154e50 100644 --- a/docs/client-verification.md +++ b/docs/client-verification.md @@ -1,5 +1,10 @@ # Client and agent verification +FreeBuff MCP loading and personal skill discovery are currently unverified with +an installed native client/version. Its temporary-filesystem coverage verifies +REA's configuration and applicability boundaries only; it does not establish +that FreeBuff consumes the written registration or skill. + Use these optional lanes to verify an installed client or evaluate an agent's REA workflow. [Testing REA](testing.md) provides the normal development commands and provider lanes. Each record below states its host, client version, model diff --git a/src/application/SupportedClients.ts b/src/application/SupportedClients.ts index 828c007c32..fa9dbc426b 100644 --- a/src/application/SupportedClients.ts +++ b/src/application/SupportedClients.ts @@ -35,6 +35,8 @@ export interface SetupClient { readonly msixPackage?: MsixPackageView; /** A running client can rewrite its configuration from memory. */ readonly quitBeforeSetup?: boolean; + /** Product-specific applicability evidence when configuration storage is shared. */ + readonly discoveryPaths?: readonly string[]; readonly format?: | "json" | "toml" @@ -96,6 +98,7 @@ type ClientDefinition = ClientDefinitionBase & interface ClientDefinitionBase { readonly name: string; readonly displayName: string; + readonly discoveryPaths?: (context: ClientPathContext) => readonly string[]; readonly format: NonNullable; readonly quitBeforeSetup?: boolean; readonly zcodeFallbackPath?: (context: ClientPathContext) => string; @@ -713,6 +716,9 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [ configPath: ({ home }: ClientPathContext) => join(home, ".agents", "mcp.json"), markerPath: freebuffProfileDirectory, + discoveryPaths: (context: ClientPathContext) => [ + freebuffProfileDirectory(context), + ], format: "json", }, { @@ -730,14 +736,19 @@ export const SUPPORTED_CLIENT_DEFINITIONS = [ * directory override does not exist. */ export const clientEvidencePaths = ( - client: Pick, + client: Pick< + SetupClient, + "configPath" | "configPaths" | "markerPath" | "discoveryPaths" + >, ): readonly string[] => [ ...new Set( - [ - client.configPath, - ...(client.configPaths ?? []), - client.markerPath, - ].filter((path): path is string => path !== undefined && path.length > 0), + ( + client.discoveryPaths ?? [ + client.configPath, + ...(client.configPaths ?? []), + client.markerPath, + ] + ).filter((path): path is string => path !== undefined && path.length > 0), ), ]; @@ -805,6 +816,9 @@ export const supportedClients = ( ...(definition.quitBeforeSetup === true ? { quitBeforeSetup: true } : {}), + ...(definition.discoveryPaths === undefined + ? {} + : { discoveryPaths: definition.discoveryPaths(context) }), format: definition.format, }; } catch (cause: unknown) { diff --git a/tests/boundary/filesystem/freebuffClientConfiguration.test.ts b/tests/boundary/filesystem/freebuffClientConfiguration.test.ts index 92a94dfc7d..d701ccb167 100644 --- a/tests/boundary/filesystem/freebuffClientConfiguration.test.ts +++ b/tests/boundary/filesystem/freebuffClientConfiguration.test.ts @@ -36,6 +36,7 @@ describe("FreeBuff configuration paths", () => { displayName: "FreeBuff", configPath: join("/home/a", ".agents", "mcp.json"), markerPath: join("/home/a", ".config", "freebuff-desktop"), + discoveryPaths: [join("/home/a", ".config", "freebuff-desktop")], format: "json", }); expect(freebuffPath("/Users/a", "darwin")?.markerPath).toBe( @@ -50,9 +51,21 @@ describe("FreeBuff configuration paths", () => { const home = await createTestTempDirectory("rea-freebuff-shared-"); // REA's own skill installs create this directory for every client. await mkdir(join(home, ".agents", "skills"), { recursive: true }); + await writeFile( + join(home, ".agents", "mcp.json"), + '{"mcpServers":{"other":{"command":"other-server"}}}', + ); expect((await detectClients(home)).map(({ name }) => name)).not.toContain( "freebuff", ); + expect( + ( + await readClientRegistrationStatuses(home, undefined, { + platform: process.platform, + environment: {}, + }) + ).map(({ client }) => client), + ).not.toContain("freebuff"); await mkdir(join(home, ".config", "freebuff-desktop"), { recursive: true, @@ -85,6 +98,7 @@ describe("FreeBuff JSON registration", () => { const client = freebuffPath(home, process.platform); if (client === undefined) throw new Error("missing FreeBuff client"); await mkdir(dirname(client.configPath), { recursive: true }); + await mkdir(client.markerPath!, { recursive: true }); return { home, client }; };