From f149eef3fe757f1e3f3079fda9e3221be8ed499a Mon Sep 17 00:00:00 2001 From: Oliver Kieselbach Date: Wed, 22 Apr 2026 16:06:58 +0200 Subject: [PATCH] feat(agent-v2): wire --replay-log-dir CLI flag to ImeLogTracker SimulationMode V2 had the complete replay infrastructure in place but never wired up: ImeLogTracker.SimulationMode / SpeedFactor properties (ImeLogTracker.cs:66-67) and the ApplySimulationDelay compression loop (ImeLogTracker.Handlers.cs:529-546) were fully implemented, and AgentConfiguration had ReplayLogDir + ReplaySpeedFactor fields (L.128, L.134), but nothing set them from the CLI and nothing read them in the factory. This commit adds: - CLI args --replay-log-dir and --replay-speed-factor in Program.BuildAgentConfiguration (invariant-culture parse, zero/malformed rejected). - DefaultComponentFactory.ImeLogHost ctor accepts a simulation mode + speed factor and applies them to the inner tracker when ReplayLogDir is non-empty. - PrintUsage documents the flags under a new "Dev / Test" section. For the deterministic signal-level replay (V2 ReplayHarness + signal-log.jsonl fixtures) no wiring is needed - that is already the primary V2 test path. The IME- log replay activated here is the V1 compatibility dev-mode for support cases where only raw IME logs are available. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../BuildAgentConfigurationReplayTests.cs | 66 +++++++++++++++++++ .../Orchestration/DefaultComponentFactory.cs | 31 ++++++++- .../AutopilotMonitor.Agent.V2/Program.cs | 22 ++++++- .../buildcounter.txt | 2 +- .../AutopilotMonitor.Agent/buildcounter.txt | 2 +- 5 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Program/BuildAgentConfigurationReplayTests.cs diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Program/BuildAgentConfigurationReplayTests.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Program/BuildAgentConfigurationReplayTests.cs new file mode 100644 index 00000000..d205ebf3 --- /dev/null +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core.Tests/Program/BuildAgentConfigurationReplayTests.cs @@ -0,0 +1,66 @@ +using System; +using AutopilotMonitor.Agent.V2; +using AutopilotMonitor.Agent.V2.Core.Configuration; +using Xunit; + +namespace AutopilotMonitor.Agent.V2.Core.Tests.Program +{ + /// + /// Verifies that + /// propagates the dev-only --replay-log-dir + --replay-speed-factor CLI flags + /// onto so the IME log replay SimulationMode actually + /// activates downstream. V2 parity PR-A. + /// + public sealed class BuildAgentConfigurationReplayTests + { + private static AgentConfiguration Build(params string[] args) => + AutopilotMonitor.Agent.V2.Program.BuildAgentConfiguration( + args: args, + tenantId: "tenant-t", + sessionId: "session-s", + bootstrapConfig: null, + awaitConfig: null); + + [Fact] + public void Without_replay_flags_ReplayLogDir_stays_null_and_speed_factor_default() + { + var cfg = Build(); + + Assert.Null(cfg.ReplayLogDir); + Assert.Equal(50.0, cfg.ReplaySpeedFactor); + } + + [Fact] + public void Replay_log_dir_populates_configuration() + { + var cfg = Build("--replay-log-dir", @"C:\tmp\ime-logs"); + + Assert.Equal(@"C:\tmp\ime-logs", cfg.ReplayLogDir); + } + + [Fact] + public void Replay_speed_factor_is_parsed_as_invariant_double() + { + // Comma locale must NOT flip the meaning — parser uses InvariantCulture. + var cfg = Build("--replay-log-dir", @"C:\tmp\x", "--replay-speed-factor", "125.5"); + + Assert.Equal(125.5, cfg.ReplaySpeedFactor); + } + + [Fact] + public void Replay_speed_factor_zero_is_rejected_and_default_kept() + { + var cfg = Build("--replay-log-dir", @"C:\tmp\x", "--replay-speed-factor", "0"); + + Assert.Equal(50.0, cfg.ReplaySpeedFactor); + } + + [Fact] + public void Replay_speed_factor_malformed_is_rejected_and_default_kept() + { + var cfg = Build("--replay-log-dir", @"C:\tmp\x", "--replay-speed-factor", "fast"); + + Assert.Equal(50.0, cfg.ReplaySpeedFactor); + } + } +} diff --git a/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/DefaultComponentFactory.cs b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/DefaultComponentFactory.cs index 7ea05d3e..aa8c319c 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/DefaultComponentFactory.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2.Core/Orchestration/DefaultComponentFactory.cs @@ -126,6 +126,14 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration hosts.Add(new AadJoinHost(logger, ingress, clock)); + // Dev / test — if --replay-log-dir is set, the tracker reads from the replay folder + // with SimulationMode ON + the configured SpeedFactor instead of tailing the live + // IME log folder. Production agents leave ReplayLogDir empty. + var simulationMode = !string.IsNullOrEmpty(_agentConfig.ReplayLogDir); + var imeLogFolder = simulationMode + ? _agentConfig.ReplayLogDir + : _agentConfig.ImeLogPathOverride; + _imeLogHost = new ImeLogHost( sessionId: sessionId, tenantId: tenantId, @@ -133,11 +141,13 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration logger: logger, ingress: ingress, clock: clock, - imeLogPathOverride: _agentConfig.ImeLogPathOverride, + imeLogPathOverride: imeLogFolder, imeMatchLogPath: _agentConfig.ImeMatchLogPath, imePatterns: _remoteConfig.ImeLogPatterns, stateDirectory: _stateDirectory, - whiteGloveSealingPatternIds: whiteGloveSealingPatternIds); + whiteGloveSealingPatternIds: whiteGloveSealingPatternIds, + simulationMode: simulationMode, + simulationSpeedFactor: _agentConfig.ReplaySpeedFactor); hosts.Add(_imeLogHost); if (collectors.StallProbeEnabled) @@ -401,6 +411,12 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration /// internal Monitoring.Enrollment.Ime.ImeLogTracker Tracker => _tracker; + /// + /// Exposes the tracker's simulation flag so the Dev / Test CLI flag + /// --replay-log-dir is testable without poking through reflection. + /// + internal bool IsSimulationMode => _tracker.SimulationMode; + public ImeLogHost( string sessionId, string tenantId, @@ -412,7 +428,9 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration string? imeMatchLogPath, List? imePatterns, string stateDirectory, - IReadOnlyCollection? whiteGloveSealingPatternIds) + IReadOnlyCollection? whiteGloveSealingPatternIds, + bool simulationMode = false, + double simulationSpeedFactor = 50) { _logger = logger; var logFolder = string.IsNullOrEmpty(imeLogPathOverride) ? DefaultImeLogFolder : imeLogPathOverride!; @@ -428,6 +446,13 @@ namespace AutopilotMonitor.Agent.V2.Core.Orchestration matchLogPath: expandedMatchLogPath, stateDirectory: stateDirectory); + if (simulationMode) + { + _tracker.SimulationMode = true; + _tracker.SpeedFactor = simulationSpeedFactor; + logger.Info($"ImeLogHost: SimulationMode ENABLED (speedFactor={simulationSpeedFactor}, path={logFolder})"); + } + _adapter = new ImeLogTrackerAdapter(_tracker, ingress, clock, whiteGloveSealingPatternIds); _processWatcher = new ImeProcessWatcher(sessionId, tenantId, onEnrollmentEvent, logger); diff --git a/src/Agent/AutopilotMonitor.Agent.V2/Program.cs b/src/Agent/AutopilotMonitor.Agent.V2/Program.cs index 0550e794..db01c975 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2/Program.cs +++ b/src/Agent/AutopilotMonitor.Agent.V2/Program.cs @@ -1113,7 +1113,7 @@ namespace AutopilotMonitor.Agent.V2 // ---------------------------------------------------------------- Configuration - private static AgentConfiguration BuildAgentConfiguration( + internal static AgentConfiguration BuildAgentConfiguration( string[] args, string tenantId, string sessionId, @@ -1124,6 +1124,20 @@ namespace AutopilotMonitor.Agent.V2 var imeLogPathOverride = GetArgValue(args, "--ime-log-path"); var imeMatchLogPath = GetArgValue(args, "--ime-match-log"); + // Dev / test — IME log replay (V1 compat mode). Feeds ImeLogTracker.SimulationMode + // + SpeedFactor so recorded raw IME logs are replayed at an accelerated rate; signal + // timestamps + ingress ordinals are regenerated as the replay runs. + var replayLogDir = GetArgValue(args, "--replay-log-dir"); + var replaySpeedFactorRaw = GetArgValue(args, "--replay-speed-factor"); + var replaySpeedFactor = 50.0; + if (!string.IsNullOrEmpty(replaySpeedFactorRaw) + && double.TryParse(replaySpeedFactorRaw, System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, out var parsedSpeed) + && parsedSpeed > 0) + { + replaySpeedFactor = parsedSpeed; + } + var bootstrapToken = GetArgValue(args, "--bootstrap-token") ?? bootstrapConfig?.BootstrapToken; @@ -1169,6 +1183,8 @@ namespace AutopilotMonitor.Agent.V2 KeepLogFile = keepLogFile, SelfDestructOnComplete = !noCleanup, CommandLineArgs = FormatArgsForLog(args), + ReplayLogDir = replayLogDir, + ReplaySpeedFactor = replaySpeedFactor, }; } @@ -1269,6 +1285,10 @@ namespace AutopilotMonitor.Agent.V2 Console.Out.WriteLine(" --api-url Override backend API base URL (alias: --backend-api)"); Console.Out.WriteLine(" --ime-log-path Override IME logs directory"); Console.Out.WriteLine(" --ime-match-log Write matched IME log lines to file (debug)"); + Console.Out.WriteLine(); + Console.Out.WriteLine("Dev / Test:"); + Console.Out.WriteLine(" --replay-log-dir Replay IME logs from this directory (simulation mode)"); + Console.Out.WriteLine(" --replay-speed-factor Time-compression factor for log replay (default: 50)"); } private static void PrintVersion() diff --git a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt index b16e5f75..fe4afb0d 100644 --- a/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent.V2/buildcounter.txt @@ -1 +1 @@ -104 +106 diff --git a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt index bba0bc80..cf635ffb 100644 --- a/src/Agent/AutopilotMonitor.Agent/buildcounter.txt +++ b/src/Agent/AutopilotMonitor.Agent/buildcounter.txt @@ -1 +1 @@ -1073 +1074