agent: unify listening and dialing with directed mutual measurements

Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry.

Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
This commit is contained in:
justme committed 2026-10-21 11:55:36 +08:00
1 parent d5ebf29e57
commit ad5efc62ff
206 files changed
+4202 -4964

No files matched your search

@@ -9,28 +9,26 @@ namespace MoShiro.Backend.Application;
public sealed record DesiredTaskSet(long Version, TaskSet Document);
/// <summary>
/// 为每个 Agent 计算期望状态(<see cref="TaskSet"/>):有效任务集 + 允许连接的 Probe 公钥。
/// 为每个 Agent 计算期望状态(<see cref="TaskSet"/>):有效任务集 + 允许拨入的拨号方公钥。
/// 只有内容真的变了才递增版本号,所以断线重连后 Agent 收到的永远是当前版本的完整快照。
/// </summary>
public sealed class TaskPlanner : IDisposable
{
public const string ProbeAgentsVersionKey = "probe_agents_version";
public const string DialTargetsVersionKey = "dial_targets_version";
private readonly ITaskStore _tasks;
private readonly IAgentStore _agents;
private readonly IProbeStore _probes;
private readonly ISettingsStore _settings;
private readonly LinkService _links;
private readonly TrustService _trust;
private readonly SemaphoreSlim _gate = new(1, 1);
public TaskPlanner(ITaskStore tasks, IAgentStore agents, IProbeStore probes, ISettingsStore settings, LinkService links, TrustService trust)
public TaskPlanner(ITaskStore tasks, IAgentStore agents, ISettingsStore settings, LinkService links, TrustService trust)
{
_trust = trust;
_links = links;
_tasks = tasks;
_agents = agents;
_probes = probes;
_settings = settings;
}
@@ -44,7 +42,7 @@ public sealed class TaskPlanner : IDisposable
}
IReadOnlyList<TaskRecord> all = await _tasks.ListAsync(ct);
IReadOnlyList<ProbeRecord> probes = await _probes.ListAsync(ct);
IReadOnlyList<AgentRecord> nodes = await _agents.ListAsync(ct);
LinkSnapshot links = await _links.SnapshotAsync(ct);
var document = new TaskSet();
@@ -62,9 +60,12 @@ public sealed class TaskPlanner : IDisposable
});
}
foreach (ProbeRecord probe in probes.Where(p => p.RevokedAtMs is null && p.PeerPublicKey is { Length: > 0 } && links.IsLinked(p.Id, agentId.Value)))
if (agent.Listens)
{
document.Peers.Add(new PeerSpec { ProbeId = new ProbeId(probe.Id), PublicKey = Convert.ToBase64String(probe.PeerPublicKey!) });
foreach (AgentRecord dialer in nodes.Where(n => n.Status == NodeStatus.Active && n.TlsPublicKey is { Length: > 0 } && links.IsLinked(n, agent)))
{
document.Peers.Add(new PeerSpec { NodeId = dialer.Id, PublicKey = Convert.ToBase64String(dialer.TlsPublicKey!) });
}
}
IReadOnlyList<Fleet.Core.SignedStatementDto> chain = await _trust.ChainAsync(ct);
@@ -96,5 +97,5 @@ public sealed class TaskPlanner : IDisposable
public void Dispose() => _gate.Dispose();
public ValueTask<long> BumpProbeAgentsVersionAsync(CancellationToken ct) => _settings.IncrementAsync(ProbeAgentsVersionKey, ct);
public ValueTask<long> BumpDialTargetsVersionAsync(CancellationToken ct) => _settings.IncrementAsync(DialTargetsVersionKey, ct);
}