diff --git a/admin/slices/agent/peer/components/peer/Picker.vue b/admin/slices/agent/peer/components/peer/Picker.vue index 63657e22..320b84b4 100644 --- a/admin/slices/agent/peer/components/peer/Picker.vue +++ b/admin/slices/agent/peer/components/peer/Picker.vue @@ -31,6 +31,11 @@ import { * The preview expands under the clicked row, accordion-style: at ten * candidates a panel below the list sits under the fold, and a click that * renders off-screen reads as a click that did nothing (CLEAN-94). + * + * The two ways in — an agent of this ranch, or any A2A address — are tabs, not + * one scrolling column (CLEAN-98). Stacked, the address form sat below every + * candidate: on a ranch with a dozen agents nothing on the first screen said an + * external import existed at all. */ const props = defineProps<{ open: boolean; agentId: string }>(); const emit = defineEmits<{ 'update:open': [value: boolean]; connected: [] }>(); @@ -46,7 +51,24 @@ const error = ref(null); const FILTER_FROM = 6; +type AddMode = 'ranch' | 'external'; + +const mode = ref('ranch'); +/** True once the operator has picked a tab themselves; after that nothing + * moves them, least of all the empty-ranch default below. */ +const modeTouched = ref(false); + +function selectMode(value: unknown): void { + if (value !== 'ranch' && value !== 'external') return; + mode.value = value; + modeTouched.value = true; +} + const candidates = computed(() => store.candidates(props.agentId)); +/** What the tab counts: agents that can still be added, not the whole ranch. */ +const available = computed( + () => candidates.value.filter((c) => !c.connected).length, +); const visible = computed(() => { const q = filter.value.trim().toLowerCase(); @@ -61,7 +83,7 @@ const isOpen = computed({ watch( () => props.open, - (open) => { + async (open) => { if (!open) return; filter.value = ''; selected.value = null; @@ -71,7 +93,14 @@ watch( urlToken.value = ''; urlPreview.value = null; urlError.value = null; - void store.loadCandidates(props.agentId); + mode.value = 'ranch'; + modeTouched.value = false; + await store.loadCandidates(props.agentId); + // With nothing left to connect on this ranch, the address form IS the + // dialog — opening on an empty list would read as "no way to add a peer". + if (!modeTouched.value && candidates.value.length === 0) { + mode.value = 'external'; + } }, ); @@ -197,193 +226,237 @@ const urlPreviewAdvertisesNothing = computed( Add peer - Agents on this ranch this one isn't connected to yet. Click an agent - to read its card, then connect. + + -
-
- - -
+ + + + On this ranch + {{ available }} + + External agent + -
    -
  • - + - -
    -
    - - - -
    - -

    {{ error }}

    -
    -
  • -
+ + -

- No agent matches "{{ filter }}". -

-

- No unconnected agents left on this ranch. -

+

+ No agent matches "{{ filter }}". +

+

+ No unconnected agents left on this ranch. Anything outside + it comes in under External agent. +

+ - -
-

Import an external agent

-

- Any A2A 1.0 agent outside this ranch, by its address. Importing - the same address again updates the entry — no duplicates. -

-
- - - -
+ + + +
+ + + +
-
-
- - + +
+ + +
+
- -
-

- {{ urlError }} -

+

+ {{ urlError }} +

+
-
+