added agent emit event explicit flag for immediate upload

This commit is contained in:
Oliver Kieselbach
2026-03-20 00:31:48 +01:00
parent c9ff72d9b8
commit 66733209ec
12 changed files with 99 additions and 49 deletions
+3
View File
@@ -107,8 +107,11 @@ tmpclaude-*-cwd
.claude/commands/session-debug.md
.claude/commands/table-query.md
.claude/commands/ux-insights.md
.claude/commands/diag-agent-log.md
.claude/commands/diag-ime-log.md
.claude/scripts/query-appinsights.sh
.claude/scripts/query-table.sh
.claude/scripts/fetch-diag-zip.sh
# Deployment scripts (local only)
scripts/Deployment/build_and_upload_debug_agent_build.ps1
@@ -263,7 +263,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown,
Message = message,
Data = data
Data = data,
ImmediateUpload = shouldTriggerHelloCompleted || eventType == "hello_provisioning_failed"
});
_logger.Info($"Hello event detected: {eventType} (EventID {eventId}{(isBackfill ? ", backfill" : "")})");
@@ -340,7 +341,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
{ "espExitDetected", _espExitDetected },
{ "helloPolicyEnabled", true },
{ "action", "extended_wait" }
}
},
ImmediateUpload = true
});
// Arm the long completion timer so we don't wait forever
@@ -372,7 +374,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
{ "espExitDetected", _espExitDetected },
{ "helloPolicyEnabled", false },
{ "action", "enrollment_complete" }
}
},
ImmediateUpload = true
});
// Trigger HelloCompleted so enrollment can proceed
@@ -486,7 +489,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
{
{ "timeoutSeconds", HelloCompletionTimeoutSeconds },
{ "helloWizardStarted", _helloWizardStarted }
}
},
ImmediateUpload = true
});
try
@@ -214,7 +214,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Collectors
Source = "EspAndHelloTracker",
Phase = EnrollmentPhase.Unknown, // Let EnrollmentTracker decide phase
Message = message,
Data = eventData
Data = eventData,
ImmediateUpload = true
});
_logger.Info($"Shell-Core event detected: {eventType} (EventID {eventId})");
@@ -210,7 +210,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
{ "failureType", "agent_timeout" },
{ "failureSource", "max_lifetime_timer" },
{ "agentUptimeMinutes", Math.Round(uptimeMinutes, 1) }
}
},
ImmediateUpload = true
});
}
@@ -235,7 +236,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
Severity = EventSeverity.Info,
Source = "DesktopArrivalDetector",
Phase = EnrollmentPhase.Unknown,
Message = "User desktop detected (explorer.exe under real user)"
Message = "User desktop detected (explorer.exe under real user)",
ImmediateUpload = true
});
// Notify EnrollmentTracker — it decides whether this triggers completion
@@ -27,7 +27,10 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
{
evt.Sequence = Interlocked.Increment(ref _eventSequence);
_spool.Add(evt);
_logger.Info($"Event emitted: {evt.EventType} - {evt.Message}");
if (evt.Severity <= EventSeverity.Debug)
_logger.Verbose($"Event emitted: {evt.EventType} - {evt.Message}");
else
_logger.Info($"Event emitted: {evt.EventType} - {evt.Message}");
// Track real enrollment activity for idle timeout
if (!string.IsNullOrEmpty(evt.EventType) && !IsPeriodicEvent(evt.EventType))
@@ -59,13 +62,16 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
_maxLifetimeTimer?.Change(Timeout.Infinite, Timeout.Infinite);
}
// Check if this is a phase transition
bool isPhaseTransition = false;
// Track phase transitions for logging and gather rule notifications
if (evt.Phase != _lastPhase)
{
_logger.Info($"Phase transition: {_lastPhase?.ToString() ?? "null"} -> {evt.Phase}");
// Unknown is an intermediate/reset state — log at Debug to reduce noise.
// Real phase boundaries (DeviceSetup, AccountSetup, etc.) stay at Info.
if (evt.Phase == EnrollmentPhase.Unknown)
_logger.Debug($"Phase transition: {_lastPhase?.ToString() ?? "null"} -> {evt.Phase}");
else
_logger.Info($"Phase transition: {_lastPhase?.ToString() ?? "null"} -> {evt.Phase}");
_lastPhase = evt.Phase;
isPhaseTransition = true;
// Notify gather rule executor of phase change
try { _gatherRuleExecutor?.OnPhaseChanged(evt.Phase); }
@@ -139,17 +145,9 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
return; // Don't continue with normal event processing
}
// Immediate upload for:
// 1. Critical events (errors) - for troubleshooting
// 2. Phase transitions (start/end) - for real-time phase tracking in UI
// 3. Events with "phase" in EventType - explicit phase-related events
// 4. App download/install events - for real-time download progress UI updates
var isAppEvent = evt.EventType?.StartsWith("app_", StringComparison.OrdinalIgnoreCase) == true;
if (evt.Severity >= EventSeverity.Error ||
isPhaseTransition ||
evt.EventType?.Contains("phase", StringComparison.OrdinalIgnoreCase) == true ||
isAppEvent)
// Immediate upload when explicitly requested by the emitter, or as a safety net for
// unhandled errors. All other events batch via the debounce timer.
if (evt.ImmediateUpload || evt.Severity >= EventSeverity.Error)
{
_logger.Info($"Triggering immediate upload for {evt.EventType} (bypassing debounce)");
Task.Run(() => UploadEventsAsync());
@@ -614,7 +612,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
Data = new Dictionary<string, object>
{
{ "rebootDelaySeconds", _configuration.RebootDelaySeconds }
}
},
ImmediateUpload = true
});
// Final upload to ensure the reboot event is sent
@@ -210,7 +210,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
Source = "Agent",
Phase = EnrollmentPhase.Start,
Message = "Autopilot Monitor Agent started",
Data = startupData
Data = startupData,
ImmediateUpload = true
});
// WhiteGlove Part 2 detection: if the previous boot completed pre-provisioning,
@@ -226,7 +227,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
Severity = EventSeverity.Info,
Source = "Agent",
Phase = EnrollmentPhase.Start,
Message = "WhiteGlove Part 2 — user enrollment started after pre-provisioning"
Message = "WhiteGlove Part 2 — user enrollment started after pre-provisioning",
ImmediateUpload = true
});
_sessionPersistence.ClearWhiteGloveComplete();
_sessionPersistence.ResetSessionCreatedAt();
@@ -270,7 +272,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
Source = "Network",
Phase = EnrollmentPhase.Unknown,
Message = $"Device location: {attempt.Location.City}, {attempt.Location.Region}, {attempt.Location.Country} (via {attempt.Location.Source})",
Data = attempt.Location.ToDictionary()
Data = attempt.Location.ToDictionary(),
ImmediateUpload = true
});
}
else
@@ -441,7 +444,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
["unrestrictedMode"] = true,
["source"] = "initial_config",
["configVersion"] = config.ConfigVersion
}
},
ImmediateUpload = true
});
}
}
@@ -464,7 +468,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
["previousValue"] = false,
["source"] = "config_refresh",
["configVersion"] = config.ConfigVersion
}
},
ImmediateUpload = true
});
}
else
@@ -483,7 +488,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Core
["previousValue"] = true,
["source"] = "config_refresh",
["configVersion"] = config.ConfigVersion
}
},
ImmediateUpload = true
});
}
}
@@ -106,7 +106,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "triggeredBy", "self_deploying_provisioning_complete" },
{ "autopilotMode", _autopilotMode },
{ "skipUserStatusPage", _skipUserStatusPage }
}
},
ImmediateUpload = true
});
try { CollectDeviceInfoAtFinalizingSetup("self_deploying_provisioning_complete"); }
@@ -137,7 +138,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Severity = EventSeverity.Info,
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.Complete,
Message = "WhiteGlove (Pre-Provisioning) completed \u2014 device entering pending state"
Message = "WhiteGlove (Pre-Provisioning) completed \u2014 device entering pending state",
ImmediateUpload = true
});
// WICHTIG: Kein WriteEnrollmentCompleteMarker()!
@@ -287,7 +289,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "failureSource", failureSource },
{ "signalsSeen", _stateData.SignalsSeen },
{ "agentUptimeSeconds", (DateTime.UtcNow - _agentStartTimeUtc).TotalSeconds }
}
},
ImmediateUpload = true
});
// Write final status dump before cleanup
@@ -341,7 +344,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "triggeredBy", reason },
{ "previousPhase", _lastEspPhase ?? "unknown" },
{ "desktopArrivedBackup", _desktopArrived && !string.Equals(_lastEspPhase, "AccountSetup", StringComparison.OrdinalIgnoreCase) }
}
},
ImmediateUpload = true
});
try { CollectDeviceInfoAtFinalizingSetup(reason); }
@@ -380,7 +384,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "triggeredBy", "device_only_esp_registry" },
{ "previousPhase", _lastEspPhase ?? "unknown" },
{ "skipUserStatusPage", true }
}
},
ImmediateUpload = true
});
try { CollectDeviceInfoAtFinalizingSetup("device_only_esp_registry"); }
@@ -437,7 +442,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "espPhase", "FinalizingSetup" },
{ "autoDetected", true },
{ "triggeredBy", reason }
}
},
ImmediateUpload = true
});
try { CollectDeviceInfoAtFinalizingSetup(reason); }
@@ -603,7 +609,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "signalsSeen", _stateData.SignalsSeen },
{ "signalTimestamps", signalTimestamps },
{ "agentUptimeSeconds", (DateTime.UtcNow - _agentStartTimeUtc).TotalSeconds }
}
},
ImmediateUpload = true
});
// Write final status dump and enrollment complete marker
@@ -636,6 +643,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.Unknown,
Message = $"Completion source '{source}' evaluated — {result}",
ImmediateUpload = true,
Data = new Dictionary<string, object>
{
{ "source", source },
@@ -714,7 +722,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "correctedBy", "desktop_arrival" },
{ "previousPhase", previousPhase },
{ "skipUserStatusPage", true }
}
},
ImmediateUpload = true
});
try { CollectDeviceInfoAtFinalizingSetup("desktop_arrived_skip_user"); }
@@ -741,7 +750,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
{ "autoDetected", true },
{ "correctedBy", "desktop_arrival" },
{ "previousPhase", previousPhase }
}
},
ImmediateUpload = true
});
}
}
@@ -110,17 +110,26 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
_lastEmittedSummaryHash = hash;
// Elevate to Info only when completed/error counts change — mere downloading/installing
// state changes (same completed count) are Verbose to reduce log noise.
var compactHash = $"{states.CountCompleted}_{states.ErrorCount}";
var severity = (compactHash != _lastEmittedSummaryCompactHash)
? (states.HasError ? EventSeverity.Warning : EventSeverity.Info)
: EventSeverity.Debug;
_lastEmittedSummaryCompactHash = compactHash;
_emitEvent(new EnrollmentEvent
{
SessionId = _sessionId,
TenantId = _tenantId,
EventType = "app_tracking_summary",
Severity = states.HasError ? EventSeverity.Warning : EventSeverity.Info,
Severity = severity,
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.Unknown,
Message = $"App tracking: {states.CountCompleted}/{states.CountAll} completed" +
(states.HasError ? $" ({states.ErrorCount} errors)" : ""),
Data = states.GetSummaryData()
Data = states.GetSummaryData(),
ImmediateUpload = (severity != EventSeverity.Debug)
});
}
@@ -144,7 +153,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Phase = EnrollmentPhase.Unknown,
Message = $"App tracking: {states.CountCompleted}/{states.CountAll} completed" +
(states.HasError ? $" ({states.ErrorCount} errors)" : ""),
Data = states.GetSummaryData()
Data = states.GetSummaryData(),
ImmediateUpload = true
});
}
@@ -103,7 +103,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Source = "ImeLogTracker",
Phase = enrollmentPhase,
Message = $"ESP phase: {phase}",
Data = new Dictionary<string, object> { { "espPhase", phase } }
Data = new Dictionary<string, object> { { "espPhase", phase } },
ImmediateUpload = true
});
// Re-collect enrollment-dependent device info (AAD join, profile, ESP config)
@@ -170,7 +171,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.AppsDevice,
Message = "ESP phase: AppsDevice (auto-detected from app activity)",
Data = new Dictionary<string, object> { { "espPhase", "AppsDevice" }, { "autoDetected", true } }
Data = new Dictionary<string, object> { { "espPhase", "AppsDevice" }, { "autoDetected", true } },
ImmediateUpload = true
});
}
else if (string.Equals(_lastEspPhase, "AccountSetup", StringComparison.OrdinalIgnoreCase))
@@ -187,7 +189,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.AppsUser,
Message = "ESP phase: AppsUser (auto-detected from app activity)",
Data = new Dictionary<string, object> { { "espPhase", "AppsUser" }, { "autoDetected", true } }
Data = new Dictionary<string, object> { { "espPhase", "AppsUser" }, { "autoDetected", true } },
ImmediateUpload = true
});
}
else if (string.Equals(_lastEspPhase, "FinalizingSetup", StringComparison.OrdinalIgnoreCase))
@@ -204,7 +207,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.AppsUser,
Message = "ESP phase: AppsUser (auto-detected from app activity)",
Data = new Dictionary<string, object> { { "espPhase", "AppsUser" }, { "autoDetected", true } }
Data = new Dictionary<string, object> { { "espPhase", "AppsUser" }, { "autoDetected", true } },
ImmediateUpload = true
});
}
}
@@ -320,7 +324,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Source = "ImeLogTracker",
Phase = phase,
Message = message,
Data = app.ToEventData()
Data = app.ToEventData(),
ImmediateUpload = true
});
// Emit summary immediately if state-breakdown counters changed (instant UI updates)
@@ -530,7 +535,8 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
Severity = EventSeverity.Info,
Source = "EnrollmentTracker",
Phase = EnrollmentPhase.FinalizingSetup,
Message = "User apps completed - waiting for Windows Hello provisioning to finish"
Message = "User apps completed - waiting for Windows Hello provisioning to finish",
ImmediateUpload = true
});
// Set flag so we know we're waiting
@@ -40,6 +40,7 @@ namespace AutopilotMonitor.Agent.Core.Monitoring.Tracking
private bool _enrollmentStartDeviceInfoCollected = false; // Re-collect enrollment-dependent info once at first ESP phase
private bool _finalDeviceInfoCollected = false; // Ensure final device info is emitted only once
private string _lastEmittedSummaryHash; // Track last emitted state-breakdown to avoid redundant summary events
private string _lastEmittedSummaryCompactHash; // Track completed+error counts only — changes here elevate severity to Info
// ESP failure handling (Phase 1)
private Timer _espFailureTimer;
@@ -1 +1 @@
512
518
@@ -79,6 +79,14 @@ namespace AutopilotMonitor.Shared.Models
[JsonPropertyName("phaseName")]
public string PhaseName => GetPhaseName(Phase);
/// <summary>
/// When true, the event bypasses the debounce timer and triggers an immediate upload.
/// Set by the emitter when real-time delivery is required (phase changes, app installs,
/// terminal events). Not serialized — upload transport only.
/// </summary>
[JsonIgnore]
public bool ImmediateUpload { get; set; }
private static string GetPhaseName(EnrollmentPhase phase)
{
return phase switch