fix(ai): serialize linkedVmId/linkedContainerId and harden mention status (#1252)

HostFrontend was missing LinkedVmId and LinkedContainerId fields, so the
frontend dedup aliases for VM/container agents resolved to undefined and
never matched. Also add .trim() to getStatusColor and default host agent
status to 'online' to fix grey status dots.
This commit is contained in:
rcourtman 2026-02-21 22:00:43 +00:00
parent b445f8d8fa
commit 1170da6a57
5 changed files with 42 additions and 30 deletions

View file

@ -110,7 +110,7 @@ export function MentionAutocomplete(props: MentionAutocompleteProps) {
// Get status color
const getStatusColor = (status?: string) => {
switch (status?.toLowerCase()) {
switch (status?.trim().toLowerCase()) {
case 'running':
case 'online':
case 'healthy':

View file

@ -253,7 +253,7 @@ export function buildMentionResources(state: MentionStateSubset): MentionResourc
id: `host:${host.id}`,
name: hostName,
type: 'host',
status: host.status,
status: host.status || 'online',
},
aliases,
);

View file

@ -462,28 +462,30 @@ func (r RemovedKubernetesCluster) ToFrontend() RemovedKubernetesClusterFrontend
// ToFrontend converts a Host to HostFrontend.
func (h Host) ToFrontend() HostFrontend {
host := HostFrontend{
ID: h.ID,
Hostname: h.Hostname,
DisplayName: h.DisplayName,
Platform: h.Platform,
OSName: h.OSName,
OSVersion: h.OSVersion,
KernelVersion: h.KernelVersion,
Architecture: h.Architecture,
CPUCount: h.CPUCount,
CPUUsage: h.CPUUsage,
Status: h.Status,
UptimeSeconds: h.UptimeSeconds,
IntervalSeconds: h.IntervalSeconds,
AgentVersion: h.AgentVersion,
TokenID: h.TokenID,
TokenName: h.TokenName,
TokenHint: h.TokenHint,
Tags: append([]string(nil), h.Tags...),
LastSeen: h.LastSeen.Unix() * 1000,
CommandsEnabled: h.CommandsEnabled,
IsLegacy: h.IsLegacy,
LinkedNodeId: h.LinkedNodeID,
ID: h.ID,
Hostname: h.Hostname,
DisplayName: h.DisplayName,
Platform: h.Platform,
OSName: h.OSName,
OSVersion: h.OSVersion,
KernelVersion: h.KernelVersion,
Architecture: h.Architecture,
CPUCount: h.CPUCount,
CPUUsage: h.CPUUsage,
Status: h.Status,
UptimeSeconds: h.UptimeSeconds,
IntervalSeconds: h.IntervalSeconds,
AgentVersion: h.AgentVersion,
TokenID: h.TokenID,
TokenName: h.TokenName,
TokenHint: h.TokenHint,
Tags: append([]string(nil), h.Tags...),
LastSeen: h.LastSeen.Unix() * 1000,
CommandsEnabled: h.CommandsEnabled,
IsLegacy: h.IsLegacy,
LinkedNodeId: h.LinkedNodeID,
LinkedVmId: h.LinkedVMID,
LinkedContainerId: h.LinkedContainerID,
}
// Fall back to Hostname if DisplayName is empty

View file

@ -1080,9 +1080,11 @@ func TestHostToFrontend(t *testing.T) {
Sensors: HostSensorSummary{
TemperatureCelsius: map[string]float64{"cpu": 55.0},
},
CommandsEnabled: true,
IsLegacy: false,
LinkedNodeID: "node-abc",
CommandsEnabled: true,
IsLegacy: false,
LinkedNodeID: "node-abc",
LinkedVMID: "vm-xyz",
LinkedContainerID: "ct-456",
}
frontend := host.ToFrontend()
@ -1135,6 +1137,12 @@ func TestHostToFrontend(t *testing.T) {
if frontend.LinkedNodeId != host.LinkedNodeID {
t.Errorf("LinkedNodeId = %q, want %q", frontend.LinkedNodeId, host.LinkedNodeID)
}
if frontend.LinkedVmId != host.LinkedVMID {
t.Errorf("LinkedVmId = %q, want %q", frontend.LinkedVmId, host.LinkedVMID)
}
if frontend.LinkedContainerId != host.LinkedContainerID {
t.Errorf("LinkedContainerId = %q, want %q", frontend.LinkedContainerId, host.LinkedContainerID)
}
if frontend.TokenLastUsedAt == nil || *frontend.TokenLastUsedAt != tokenLastUsed.Unix()*1000 {
t.Errorf("TokenLastUsedAt = %v, want %d", frontend.TokenLastUsedAt, tokenLastUsed.Unix()*1000)
}

View file

@ -438,9 +438,11 @@ type HostFrontend struct {
TokenHint string `json:"tokenHint,omitempty"`
TokenLastUsedAt *int64 `json:"tokenLastUsedAt,omitempty"`
Tags []string `json:"tags,omitempty"`
CommandsEnabled bool `json:"commandsEnabled,omitempty"` // Whether AI command execution is enabled
IsLegacy bool `json:"isLegacy,omitempty"` // True if using legacy agent protocol
LinkedNodeId string `json:"linkedNodeId,omitempty"` // ID of linked PVE node (if running on a node)
CommandsEnabled bool `json:"commandsEnabled,omitempty"` // Whether AI command execution is enabled
IsLegacy bool `json:"isLegacy,omitempty"` // True if using legacy agent protocol
LinkedNodeId string `json:"linkedNodeId,omitempty"` // ID of linked PVE node (if running on a node)
LinkedVmId string `json:"linkedVmId,omitempty"` // ID of linked VM (if running inside a VM)
LinkedContainerId string `json:"linkedContainerId,omitempty"` // ID of linked container (if running inside a container)
}
// HostSensorSummaryFrontend mirrors HostSensorSummary with primitives for the frontend.