diff --git a/backend/src/MoShiro.Backend.Application/Agents/MoShiroFleetHooks.cs b/backend/src/MoShiro.Backend.Application/Agents/MoShiroFleetHooks.cs
index 8a7f80d..75010cf 100644
--- a/backend/src/MoShiro.Backend.Application/Agents/MoShiroFleetHooks.cs
+++ b/backend/src/MoShiro.Backend.Application/Agents/MoShiroFleetHooks.cs
@@ -118,6 +118,7 @@ public sealed class MoShiroNodeEventSink : INodeEventSink
await ApplyHelloAsync(id, hello, ct);
break;
case NodeEventKind.Heartbeat when nodeEvent.Extra is { } extra:
+ await _nodes.SetVersionAsync(id.Value, NodeKinds.Agent, nodeEvent.Node.NodeVersion, ct);
try
{
AgentRuntimeState state = _runtime.Get(id);
@@ -172,6 +173,8 @@ public sealed class MoShiroNodeEventSink : INodeEventSink
}
}
+ await _nodes.SetVersionAsync(id.Value, NodeKinds.Probe, nodeEvent.Node.NodeVersion, ct);
+
await _releases.OfferToNodeAsync(id, ct);
await _schedule.SendToNodeAsync(id, ct);
break;
@@ -182,6 +185,7 @@ public sealed class MoShiroNodeEventSink : INodeEventSink
_quicRuntime.MarkProbeOffline(id.Value, nodeEvent.AtMs);
break;
case NodeEventKind.Heartbeat when nodeEvent.Extra is { } extra:
+ await _nodes.SetVersionAsync(id.Value, NodeKinds.Probe, nodeEvent.Node.NodeVersion, ct);
try
{
ProbeHeartbeatExtra? heartbeat = extra.Deserialize(MoShiroJsonContext.Default.ProbeHeartbeatExtra);
@@ -254,6 +258,11 @@ public sealed class MoShiroNodeEventSink : INodeEventSink
}
_owners.Report(id.Value, meta.Owners);
+ if (meta.Platform is { } platform)
+ {
+ platform.Version = hello.NodeVersion;
+ }
+ await _nodes.SetVersionAsync(id.Value, NodeKinds.Agent, hello.NodeVersion, ct);
AgentRecord? before = await _agents.GetAsync(id, ct);
string? limitsJson = meta.LocalLimits is null ? null : JsonSerializer.Serialize(meta.LocalLimits, LocalLimitsJson.Default.LocalLimits);
string? ipv6Json = meta.Ipv6 is null ? null : JsonSerializer.Serialize(meta.Ipv6, MoShiroJsonContext.Default.Ipv6Diagnostic);
diff --git a/backend/src/MoShiro.Backend.Application/Nodes/NodeInfoService.cs b/backend/src/MoShiro.Backend.Application/Nodes/NodeInfoService.cs
index 45d2926..4270f83 100644
--- a/backend/src/MoShiro.Backend.Application/Nodes/NodeInfoService.cs
+++ b/backend/src/MoShiro.Backend.Application/Nodes/NodeInfoService.cs
@@ -93,6 +93,25 @@ public sealed class NodeInfoService
await SaveAsync(current with { Info = i with { Os = os, OsName = name, Arch = arch, Version = version, Tag = tag, Commit = commit, BuiltAt = builtAt }, UpdatedAtMs = Now() }, ct);
}
+ /// Keep the displayed build version aligned with the authenticated Fleet hello, even if platform metadata is absent.
+ public async ValueTask SetVersionAsync(string nodeId, string kind, string? version, CancellationToken ct)
+ {
+ string? clean = Clean(version, 32);
+ if (clean is null)
+ {
+ return;
+ }
+
+ await EnsureLoadedAsync(ct);
+ NodeMeta current = _cache.TryGetValue(nodeId, out NodeMeta? found) ? found : Empty(nodeId, kind);
+ if (current.Info.Version == clean)
+ {
+ return;
+ }
+
+ await SaveAsync(current with { Info = current.Info with { Version = clean }, UpdatedAtMs = Now() }, ct);
+ }
+
///
/// 节点从某个地址连上来了:查一次它的国家和运营商。已经查过同一个地址(并且没过期)就什么也不做;
/// 私有地址不查;查询失败或服务关着,不影响别的,隔一阵再试。