Skip to content

feat(app): connect to fleet device from the sidebar #1409

Description

@jeonghun-jj-lee

Important

Problem — The fleet sidebar lists devices but clicking one does nothing.
Connecting to a remote device requires knowing the CLI (amico fleet attach)
or using the hub-only Remote-SSH command. Users should be able to click a device
and start working on it.

Approach — Make device rows clickable. Left-click a remote device → VS Code
Quick Pick with "Thin Client (Poor Connection)" (re-attach via POST /amicode/fleet/attach

  • credential provisioning + window reload) and "Remote SSH (Strong Connection)"
    (generalise resolveRemoteSshTarget for any device's sshAlias → new Remote-SSH
    window). Left-click the local device → "Go Standalone" (existing command, with
    hub-aware warning). Includes wiring boot-time pointer recovery so the attach path
    actually works after reload (ADR 0029 §9 gap).

Scope — in: device row click handler, bridge message, Quick Pick, boot-time
pointer recovery, two-branch device resolution (roster + topology), credential
provisioning, generalised Remote SSH resolver, local device standalone path,
attach API amendment for canonical-server row.
out: multi-target attachment, per-device workspace paths, live re-target without
reload (#1353).

Acceptance Criteria

  • Clicking a remote device row in the fleet sidebar shows a Quick Pick with
    "Thin Client (Poor Connection)" and "Remote SSH (Strong Connection)"
  • "Thin Client" calls POST /amicode/fleet/attach with the target machine_id,
    base_url, and token, then reloads the window on success
  • After reload, the extension reads the attachment pointer at boot, spins up
    the per-attachment transport, and registers the HubProxy — remote sessions are
    visible (boot-time pointer recovery, ADR 0030 §D3)
  • "Remote SSH" opens a new VS Code window connected to the device via
    vscode-remote://ssh-remote+<sshAlias>/
  • Clicking the local device ("This Machine") shows a Quick Pick with
    "Go Standalone"
  • On the hub, "Go Standalone" warns about disconnecting N other devices
  • Thin Client disabled for devices without serving capability
    (inline note: "Not running a server")
  • Thin Client disabled for devices with health === "down"
    (inline note: "Device unreachable")
  • Thin Client disabled when no credentials can be resolved
    (inline note: "No credentials available — use the Fleet Manager to connect")
  • Remote SSH disabled for devices with no sshAlias
    (inline note: "No SSH alias configured")
  • Remote SSH disabled when ms-vscode-remote.remote-ssh is not installed
    (inline note: "Requires Remote-SSH extension")
  • Device rows show cursor: pointer and a hover highlight
  • One-shot click guard prevents rapid double-clicks from stacking Quick Picks
  • Confirmation dialog before window reload for Thin Client
  • Two-branch device resolution: roster lookup by machine_id, then topology
    fallback for the canonical-server row (ADR 0030 §D4)
  • Attach API amended to accept canonical server as valid target when not in roster
  • Write seam is separate from the read-only FleetSectionDeps (ADR 0030 §D6)

Testing Decisions

Extend the existing fleet sidebar tests (sidebar_fleet_section.test.ts) for:

  • Click handler emits the correct bridge message with machineId/isLocal
  • renderDeviceRow includes cursor pointer styling
  • One-shot click guard prevents duplicate messages

New unit tests for fleet_connect_device.ts:

  • Quick Pick selection dispatches to the correct path (attach vs Remote SSH vs standalone)
  • Thin Client calls attach API with correct machine_id + credentials
  • Remote SSH resolves the correct URI from sshAlias
  • Two-branch resolution: roster hit, roster miss + topology fallback
  • Precondition gating: no serving capability, down health, no sshAlias, no Remote-SSH ext, no credentials
  • Hub-specific Go Standalone warning includes device count

New unit tests for boot-time pointer recovery:

  • Valid pointer at boot → transport spun up, proxy registered
  • Empty/absent pointer → no proxy, local sessions shown
  • Malformed pointer → honest degradation, no proxy

Extend fleet_connect_remote_ssh.test.ts for the generalised resolver:

  • resolveDeviceRemoteSshTarget with a valid alias
  • resolveDeviceRemoteSshTarget with an empty alias

Extend attach_action.test.ts for the canonical-server amendment:

  • machine_id not in roster but matches canonical → attach succeeds
  • machine_id not in roster AND not canonical → refused

Key Decisions

  • Left-click + Quick Pick (not context menu) — consistent with VS Code patterns
  • Re-attach for Thin Client (not full per-device proxy) — reuses existing infra
  • Boot-time pointer recovery scoped here, live re-target stays with Peer fleet studios (ADR 0027, Horizon 1) — #1341–#1346 #1353
  • capabilities.includes("serving") as the Thin Client gate (ADR 0029 model)
  • Separate write seam (FleetConnectDeps), not the read-only FleetSectionDeps
  • Two-branch resolution (roster + topology) for synthesized canonical-server row
  • Generalise the existing Remote SSH resolver (not fork)
  • Roster-sole-candidate invariant amended for topology canonical (security preserved)

Constraints & Invariants

  • ADR 0026: sidebar emits no roster-write message (connect-to-device is navigation)
  • ADR 0027 §D3: single-writer attachment pointer; Thin Client uses existing attach API
  • ADR 0027 §D3: roster-as-sole-candidate amended — topology canonical is a second
    trusted source; security property preserved (coordinates from fleet.json, not request)
  • ADR 0025: Remote-SSH uses the same vscode-remote://ssh-remote+ URI scheme
  • ADR 0029: serving capability is the attachability predicate, not server_mode
  • FleetSectionDeps remains read-only; write path uses a separate seam

Prior Art

  • fleet_connect_remote_ssh.ts — the hub-only Remote-SSH command (generalise it)
  • attach_action.ts — POST /amicode/fleet/attach (amend for canonical server)
  • attachment_transport.ts — per-attachment transport bring-up (reuse at boot)
  • amicode_service_wiring.ts — where boot-time fleet options are constructed (wire pointer recovery)
  • sidebar_fleet_section.ts — device row rendering (add click handler)
  • sidebar_bridge.ts — typed message bridge (add new message kind)

Source

ADR 0030 — connect to fleet device from the sidebar.
Adversarial review: round 2, approved-mechanical (3 manual critics, no tooling).

Sub-issues

Dependency graph

#1410 (boot-time recovery)    ──┐
#1411 (attach API amendment)  ──┼──► #1413 (sidebar click → Quick Pick)
#1412 (Remote SSH generalise) ─┘

Slices 1–3 are independent (parallelizable). Slice 4 integrates them.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions