diff --git a/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs b/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs
index 4691aa4..a585b4c 100644
--- a/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs
+++ b/backend/probe/src/MoShiro.Probe/Link/AgentConnection.cs
@@ -23,6 +23,9 @@ public sealed class AgentConnection : IDisposable
{
private static readonly TimeSpan s_flushInterval = TimeSpan.FromSeconds(ProtocolLimits.ChunkSeconds);
private static readonly TimeSpan s_clockInterval = TimeSpan.FromSeconds(30);
+
+ /// Agent 的任何帧(时钟回复、任务集)都算存活;比时钟同步间隔长得多,容得下差线路上的黑洞,但不会让一个 Agent 已经丢掉的会话永远挂着。
+ private static readonly TimeSpan s_idleLimit = TimeSpan.FromSeconds(120);
private static readonly RpcRouter s_router = BuildRouter();
/// 对等面入站方法的路由器(只读,测试用)。
@@ -376,6 +379,13 @@ public sealed class AgentConnection : IDisposable
long lastClockUs = _env.Clock.NowUs();
while (await flush.WaitForNextTickAsync(ct))
{
+ if (rpc.IdleFor > s_idleLimit)
+ {
+ // Agent 已经不认这个会话(它重启、重连、或者空闲超时关了会话,而 FIN 没有到达):Probe 这边结束它,让外层按退避重拨
+ _env.Log.Warn("agent session is silent, reconnecting", ("agent", Endpoint.AgentId), ("idle_s", (long)rpc.IdleFor.TotalSeconds));
+ throw new IOException("the Agent stopped answering on this session");
+ }
+
session.FlushLineBlocks();
if (_env.Clock.NowUs() - lastClockUs >= s_clockInterval.TotalMicroseconds)
{