@@ -114,6 +114,10 @@ internal sealed class AgentOsBridgeHost : IDisposable
114114 private readonly ConcurrentDictionary < string , byte > _agentRuns =
115115 new ( StringComparer . OrdinalIgnoreCase ) ;
116116 private readonly SemaphoreSlim _bootGate = new ( 1 , 1 ) ;
117+ private readonly CancellationTokenSource _lifetime = new ( ) ;
118+ private Task ? _evolutionDiscoveryLoop ;
119+ private long _lastForegroundActivityUnixMs =
120+ DateTimeOffset . Now . ToUnixTimeMilliseconds ( ) ;
117121 private bool _booted ;
118122
119123 public AgentOsBridgeHost ( Func < string , object , Task > publish )
@@ -125,7 +129,15 @@ public AgentOsBridgeHost(Func<string, object, Task> publish)
125129 }
126130
127131 public async Task < object ? > ExecuteAsync ( string method , JsonObject parameters )
128- => method switch
132+ {
133+ if ( IsForegroundActivity ( method ) )
134+ {
135+ Interlocked . Exchange (
136+ ref _lastForegroundActivityUnixMs ,
137+ DateTimeOffset . Now . ToUnixTimeMilliseconds ( ) ) ;
138+ }
139+
140+ return method switch
129141 {
130142 "boot" => await BootAsync ( ) ,
131143 "health" => await HealthAsync ( ) ,
@@ -165,6 +177,7 @@ public AgentOsBridgeHost(Func<string, object, Task> publish)
165177 RequiredString ( parameters , "id" ) ) ,
166178 _ => throw new InvalidOperationException ( $ "Unknown bridge method: { method } ")
167179 } ;
180+ }
168181
169182 private async Task < object > BootAsync ( )
170183 {
@@ -187,6 +200,8 @@ await _kernel.ReportServiceAsync(
187200 "Electron bridge lease layer active" ,
188201 boot . BootId ) ;
189202 _booted = true ;
203+ _evolutionDiscoveryLoop = RunEvolutionDiscoveryLoopAsync (
204+ _lifetime . Token ) ;
190205 }
191206 }
192207 finally
@@ -197,6 +212,68 @@ await _kernel.ReportServiceAsync(
197212 return ProjectKernel ( ) ;
198213 }
199214
215+ private async Task RunEvolutionDiscoveryLoopAsync (
216+ CancellationToken cancellationToken )
217+ {
218+ using var timer = new PeriodicTimer ( TimeSpan . FromMinutes ( 1 ) ) ;
219+ try
220+ {
221+ while ( await timer . WaitForNextTickAsync ( cancellationToken ) )
222+ {
223+ try
224+ {
225+ if ( _active . Count > 0 || _agentRuns . Count > 0 )
226+ {
227+ continue ;
228+ }
229+
230+ var lastActivity = DateTimeOffset . FromUnixTimeMilliseconds (
231+ Interlocked . Read ( ref _lastForegroundActivityUnixMs ) ) ;
232+ if ( DateTimeOffset . Now - lastActivity < TimeSpan . FromMinutes ( 10 ) )
233+ {
234+ continue ;
235+ }
236+
237+ var discovery = await _evolutionLab . TryDiscoverCandidateAsync (
238+ _snapshots . LoadAll ( ) ,
239+ cancellationToken : cancellationToken ) ;
240+ if ( ! discovery . Scanned )
241+ {
242+ continue ;
243+ }
244+
245+ await _publish ( "evolution_event" , new
246+ {
247+ kind = discovery . Candidate is null ? "scan" : "candidate" ,
248+ candidateId = discovery . Candidate ? . Id ,
249+ objective = discovery . Candidate ? . Objective ,
250+ discovery . Snapshot . DiscoveryStatus ,
251+ discovery . Snapshot . LastDiscoveryAt ,
252+ discovery . Snapshot . NextDiscoveryAt
253+ } ) ;
254+ }
255+ catch ( OperationCanceledException ) when (
256+ cancellationToken . IsCancellationRequested )
257+ {
258+ throw ;
259+ }
260+ catch ( Exception exception )
261+ {
262+ await _publish ( "evolution_event" , new
263+ {
264+ kind = "error" ,
265+ discoveryStatus = $ "自动发现本轮失败,将在下个检查周期重试:{ exception . Message } ",
266+ lastDiscoveryAt = DateTimeOffset . Now
267+ } ) ;
268+ }
269+ }
270+ }
271+ catch ( OperationCanceledException ) when ( cancellationToken . IsCancellationRequested )
272+ {
273+ // Normal bridge shutdown.
274+ }
275+ }
276+
200277 private async Task < object > HealthAsync ( )
201278 {
202279 await BootAsync ( ) ;
@@ -1198,11 +1275,29 @@ private static TaskState NormalizeRecoveredState(TaskState state)
11981275 ? TaskState . Paused
11991276 : state ;
12001277
1278+ private static bool IsForegroundActivity ( string method )
1279+ => method is
1280+ "start_task"
1281+ or "run_agent"
1282+ or "verify_result"
1283+ or "task_event"
1284+ or "complete_task"
1285+ or "propose_evolution"
1286+ or "prepare_evolution"
1287+ or "evaluate_evolution"
1288+ or "adopt_evolution"
1289+ or "reject_evolution"
1290+ or "configure_evolution_lab" ;
1291+
12011292 private static string NormalizeRecoveredStage ( TaskState state , string stage )
12021293 => state is TaskState . Running or TaskState . Waiting or TaskState . BudgetExhausted
12031294 ? "Previous host stopped; task is safely paused"
12041295 : stage ;
12051296
12061297 public void Dispose ( )
1207- => _supervisor . Dispose ( ) ;
1298+ {
1299+ _lifetime . Cancel ( ) ;
1300+ _supervisor . Dispose ( ) ;
1301+ _lifetime . Dispose ( ) ;
1302+ }
12081303}
0 commit comments