feat(desktop): add custom harness inline from agent dialogs (#3252)

Registering a custom ACP harness works today, but only from Settings →
Agents. Anyone whose first touchpoint is "New agent" has no way to
discover the custom path — the dropdown just lists the baked-in presets
plus whatever was registered earlier. This adds an inline "Add custom
harness…" entry to the harness dropdown in all three agent surfaces:
create, edit-definition (`AgentDefinitionDialog`), and instance edit
(`AgentInstanceEditDialog`).

The entry is a sentinel option (`ADD_CUSTOM_HARNESS_VALUE`, NUL-prefixed
so it can never collide with a real harness id — backend ids match
`[a-z0-9_][a-z0-9_-]*`), mirroring the `CUSTOM_ENTRY_ID` trick already
used in `HarnessCatalogDialog`. Picking it never writes into form state;
it opens `AddCustomHarnessDialog`, a thin modal wrapper hosting the
existing `CustomHarnessForm` in `chromeless` mode. `CustomHarnessForm`'s
`onSaved` now carries the saved `definition.id` (the form may rewrite
it); the two existing call sites ignore the argument, so their behavior
is unchanged.

Selection after save is deferred rather than immediate.
`usePendingHarnessSelection` holds the saved id until the runtime
catalog actually publishes it via discovery, then selects it exactly
once — so the dialog never selects an id it cannot render, and
back-to-back registrations resolve correctly. The wait is scoped to the
owning dialog's `open` state: both host dialogs stay mounted when
closed, so an unpublished id is dropped on close rather than selecting
into reset form state when discovery later catches up. Selection is
routed through each dialog's normal dropdown change handler, so
provider/model reset (and command pinning in the instance dialog) behave
identically to a hand-picked harness. Dismissing the modal leaves the
previous selection untouched. `AgentInstanceEditDialog`'s existing
"Custom command" option is a different feature (ad-hoc command override
vs. a registered reusable harness) and is untouched.

Coverage is 16 unit tests in `addCustomHarness.test.mjs` (real React
mount, following the existing `.test.mjs` pattern) plus 4 Playwright
specs in `inline-custom-harness.spec.ts` covering all three surfaces
end-to-end. Both suites were mutation-verified: treating the sentinel as
a real selection, selecting before the catalog publishes, never clearing
the pending id, ignoring the dialog's open state, and reversing
latest-save-wins each turn the unit tests red; reverting the two dialog
diffs turns all four e2e specs red. The `check-file-sizes.mjs` overrides
for the two dialogs are ratcheted to their exact new counts (1048 and
1229) — verified tight in both directions, N passes and N−1 fails, so no
headroom is introduced.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
This commit is contained in:
Will Pfleger
2026-07-28 16:01:41 -04:00
committed by GitHub
parent 1e307e178a
commit b0503d80c2
9 changed files with 763 additions and 12 deletions

View File

@@ -697,12 +697,18 @@ const overrides = new Map([
// hidden-key projection keeps the top-level secret out of Advanced rows.
// +6 (1195 -> 1201): rebase onto main — this PR's model-source label wiring
// lands on top of main's dialog growth. Queued to split.
["src/features/agents/ui/AgentInstanceEditDialog.tsx", 1201],
// +28 (1201 -> 1229): inline "Add custom harness…" entry — sentinel option,
// modal state, and the AddCustomHarnessDialog mount. The shared routing and
// deferred-selection logic lives in addCustomHarness.ts to keep this minimal.
["src/features/agents/ui/AgentInstanceEditDialog.tsx", 1229],
// AgentDefinitionDialog grew past 1000 with the following load-bearing fixes:
// isRuntimeAutoSeededRef tracking for edit-mode seeding (Fizz shows models);
// runtimeSupportsLlmProviderSelection guard on discovery provider (codex fix);
// hideProviderIds computation for Databricks v1 gate. Queued to split.
["src/features/agents/ui/AgentDefinitionDialog.tsx", 1035],
// +28 (1020 -> 1048): inline "Add custom harness…" entry — sentinel option,
// modal state, and the AddCustomHarnessDialog mount. The shared routing and
// deferred-selection logic lives in addCustomHarness.ts to keep this minimal.
["src/features/agents/ui/AgentDefinitionDialog.tsx", 1048],
// #2630 emoji picker search: the shadow-root search-input autofocus effect
// (rAF retry loop) took this file 999 -> 1026 and landed without this entry,
// so main's Desktop Core went red. Queued to split with the rest of this list.