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