Commit Graph
1 Commits
Author SHA1 Message Date
morluto fd2eabb12a feat(setup): manage fx, Junie, Goose and Kilo registrations through the client table (#10709)
* feat(setup): manage fx registrations with its private profile modes

fx becomes a client-table row, so doctor, update and uninstall see the
registration rea mcp add wrote, and setup offers fx when ~/.fx or
FX_HOME exists. Setup now publishes a private profile through the same
owner as mcp add (folder 0700, file 0600) and repairs those modes when
the row is otherwise current. Doctor parses fx's array launcher instead
of reporting it invalid.

* fix(setup): judge fx's private profile by its targets and keep its cleanup owner

Address review: privateProfileModesCurrent reads the modes of the canonical file and folder, so a linked profile becomes current; clientConfigurationAligned applies the same modes rule as plan and apply; a private-folder close failure reaches the caller with its cleanup owner instead of becoming a write failure; and the setup plan names the folder and file modes it sets.

* fix(setup): keep fx's recovery backups owner-only beside a linked profile

Setup and uninstall copied a private profile's original bytes with copyFile, so a backup beside a linked ~/.fx/mcp.json kept the target's readable mode outside the folder setup secures. Both now use the owner-only backup owner that mcp add already used.

* feat(setup): manage Junie registrations and apply its location policy (#10712)

* feat(setup): manage Junie registrations and apply its location policy

Junie becomes a client-table row, so doctor, update and uninstall see
the registration rea mcp add wrote, and setup offers Junie when its home
exists. Whether Junie loads its default user MCP file now has one owner,
read through the client policy block that setup, doctor and mcp add
already consult: setup and mcp add refuse, and doctor reports, a
registration Junie would not load.

* fix(setup): read Junie's user configuration as strict JSON

Address review: Junie loads config.json as strict JSON, so comments, trailing commas or an empty file leave its MCP location choice unknown, as before the move into the row. Filesystem tests also clear JUNIE_*, FX_HOME and MCPORTER_CONFIG so a developer's own client locations cannot be inspected or removed.

* test(setup): keep the detected client order out of the lifecycle suite body

The expected detection order grew with each folded client until the suite callback exceeded the max-lines-per-function lint limit.

* fix(update): report a client whose policy setup refuses instead of failing maintenance

Junie's disabled default locations (and Gemini's settings policy) make
doctor report the registration stale, but setup refuses that client, so
selecting it failed every other client's maintenance. The registration
inspection now carries the setup blocker, and maintenance lists that
client with its reason instead of selecting it.

* refactor(setup): read fx's private-profile host through HOST_PLATFORM

The fx private-profile mode checks and permissions describe the host REA runs
on, so they take it from the host-platform owner instead of reading
process.platform (refused by the ratchet from #10911).

* feat(setup): manage Goose registrations across its configuration layers (#10715)

* feat(setup): manage Goose registrations across its configuration layers

Goose becomes a client-table row, so doctor, update and uninstall see
the registration rea mcp add wrote, and setup offers Goose when its user
file or system file exists. Goose loads its system file and
GOOSE_ADDITIONAL_CONFIG_FILES beneath the user file and overlays each
extension one field deep; doctor now reports that effective
registration, while setup, update and uninstall change only the user
file. Doctor also parses Goose's cmd launcher instead of reporting it
invalid.

* fix(setup): keep Goose's layered environment and load order through setup and uninstall

Address review: read Goose's env alias and apply requested settings over envs; keep an inherited lower envs map instead of masking it with an empty default; keep each layer's last load so a repeated user file keeps its precedence; split GOOSE_ADDITIONAL_CONFIG_FILES with Windows quoting; validate only the effective environment across read-only layers; refuse uninstall while a read-only layer keeps REA enabled; and clear Goose's location variables in tests.

* fix(setup): apply requested Goose settings over the inherited environment

* fix(setup): leave lower-layer Goose settings in force and read admin layers read-only

Setup no longer writes its default timeout, enabled, name or description into Goose's user entry when only a lower layer declares that setting, so the inherited value keeps applying and a repeat setup stays current. Uninstall reads Goose's read-only lower layers where Goose reads them, without the write target's ownership check, so an administrator-owned system file no longer blocks removal. The client table accepts an injected Goose system-config path so tests no longer read or skip on the host's /etc/goose/config.yaml; adds a shadowed-invalid-lower-layer regression.

* fix(setup): keep an inherited Goose env alias's spelling when writing the user entry

Goose overlays each extension field separately, so writing envs into the user file beside a lower layer's env alias left both in force, which readers reject as ambiguous. Setup now writes the user map under the alias a lower layer declares, and mcp add neither adds a default envs map beside an inherited alias nor renames a user alias the lower layer shares.

* test(setup): inject Goose's system layer into mcp add selection

resolveMcpRegistrationClients accepts the caller's system configuration paths, so the Goose layers test no longer reads the host's /etc/goose/config.yaml.

* fix(setup): report REA's Goose registration that only a read-only layer defines

* test(setup): pass an isolated Goose system path through update maintenance and the mcp add table

* fix(setup): judge Goose uninstall by the merged entry and stop on an unreadable lower layer

* fix(setup): select Goose's env alias by presence so a null alias stays malformed

* fix(setup): use the shared record predicate for Goose's layered merge after #10588

* feat(setup): give Kilo a client-table row with an async layered location (#10823)

* feat(setup): give Kilo a client-table row with an async layered location

The client table gains a layeredLocation hook and resolveSupportedClients, which awaits it. Kilo's row resolves its user-scope layers through the same owner rea mcp add uses (KiloClientLocation), so both pick the same write target; the synchronous row keeps kilo.json in Kilo's config root with every candidate layer. The table refuses a relative KILO_CONFIG, KILO_CONFIG_DIR or XDG_CONFIG_HOME because Kilo's working directory is unknown.

Refs #7181

* fix(setup): treat every folder Kilo reads as evidence that it is installed

* fix(setup): normalize Kilo's absolute table paths and parse its command array

* feat(setup): resolve the client table's layered locations in setup, doctor, update and uninstall (#10832)

SetupHost (detection and the selectable table), ClientRegistrationStatus, uninstall, doctor's selected-registration environment and the mcp add catalog now read resolveSupportedClients, so each sees the Kilo layer Kilo writes rather than the synchronous default.

Refs #7181

* docs(adr): record the one-client-table decision for setup and mcp add

* fix(setup): refuse to read a client row whose location is unresolved

Doctor's selected-client settings read a Kilo row whose relative
KILO_CONFIG left only path evidence, so it inspected a file relative to
REA's folder. The shared configuration reader now refuses such a row,
so every reader reports the registration as unreadable.

* docs(adr): name the row's layeredLocation hook

* feat(setup): read KILO_CONFIG_CONTENT as Kilo's top read-only layer (#10851)

* feat(setup): read KILO_CONFIG_CONTENT as Kilo's top read-only layer

* fix(setup): refuse a Kilo uninstall that would expose or keep another layer's rea entry (#10852)

* fix(setup): recheck KILO_CONFIG_CONTENT at write time and name it in doctor's recovery

* refactor(mcp): register Kilo through its table row and remove the registration-only catalog kind (#10862)

* refactor(mcp): register Kilo through its table row and remove the registration-only catalog kind

* refactor(mcp): resolve user-scope targets in the catalog so location modules never import the client table

* test(setup): cover Kilo's KILO_CONFIG, home-folder and inline-only layers

* fix(setup): report REA's Kilo entry that only KILO_CONFIG_CONTENT sets during uninstall

Doctor reports a registration that KILO_CONFIG_CONTENT alone defines, but
uninstall found no file entry and reported Kilo as skipped. Uninstall now
reports it once as failed and names the variable to edit.

* docs(adr): record Kilo's inline layer in the client-table decision

* fix(setup): compare Windows environment names without case when a higher layer overrides REA's entry

Kilo's inline-layer check and Gemini's system-override check compared
environment maps by exact key, so an equal Path beside PATH on Windows was
reported as an override and setup refused the client. Both compare the
entry as the client launches it.

* fix(update): leave a stale Kilo registration KILO_CONFIG_CONTENT sets out of maintenance

Setup refuses a refresh the inline layer would override, and one refused
client failed every other client's maintenance. The registration inspection
now names the inline layer as the setup blocker of a stale entry, and
maintenance reports Kilo with that recovery instead of selecting it.

* fix(setup): refuse relative JUNIE_HOME and Cline CLI overrides like the other client roots (#10909)

* fix(setup): refuse relative JUNIE_HOME and Cline CLI overrides like the other client roots

* fix(setup): refuse Windows client paths without a drive that follow the client's current drive

* fix(update): select maintenance clients through setup's own refresh refusal check

Maintenance predicted setup's refusal with its own rule: any inline rea entry
blocked a stale registration, while an aligned one was never checked. A
harmless KILO_CONFIG_CONTENT entry (enabled: true) kept a stale Kilo launcher
pinned, and an inline value setup would not write (an extra environment
variable) let an aligned Kilo registration block every client's maintenance.

Setup's refusal checks (client policy, then the inline override comparison of
the entry it would write) now live in one reader that setup's alignment check
and maintenance share. Maintenance asks it for every owned enabled candidate
and reports a refused client with setup's remediation and its real state.

* fix(update): keep failed refresh-check reads owned by the supplied maintenance scope

clientRefreshRefusal read the client's layers, target file and policy without
the caller's resource scope, so a close that failed during those reads threw a
cleanup failure no scope retained; maintenance then reported it as text and
the scope's close could not retry the descriptor. The refresh reader now takes
the scope and passes it to every read.

* fix(setup): take the Kilo layer readers' default host from HOST_PLATFORM and the shared record predicate

After rebasing on main (#10911, #10588): the new inlineRegistrationOverride and
resolveSupportedClients defaults read the running host through the
host-platform owner, and the layered launcher merge uses isRecord.

* fix(update): keep an unreadable layer's reason in the refresh refusal check

readClientRefresh returned undefined for an unreadable or malformed layer, so
clientRefreshRefusal reported no refusal and maintenance selected a client
whose setup preflight then failed. The reader returns the typed file failure,
and the refusal check reports its detail like setup's preflight does.

* fix(setup): count REA's entry in KILO_CONFIG_CONTENT as Kilo lifecycle evidence

Doctor, update and uninstall inspected a client only when one of its folders
existed, so a registration set only by KILO_CONFIG_CONTENT was skipped. A
read-only inline layer that sets REA's entry is now evidence on its own.

* fix(update): report a client whose only conflict is a discovered provider path as needing setup

* fix(update): match discovered settings by the platform's environment identity

* fix(update): preflight maintenance with setup's write-target checks and treat discovered inline paths as unknown
2026-11-15 19:39:33 +08:00