Skip to content
Merged
1 change: 1 addition & 0 deletions src/Runner.Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ public static class Features
public static readonly string EmitCompositeMarkers = "actions_runner_emit_composite_markers";
public static readonly string BatchActionResolution = "actions_batch_action_resolution";
public static readonly string UseBearerTokenForCodeload = "actions_use_bearer_token_for_codeload";
public static readonly string OverrideDebuggerWelcomeMessage = "actions_runner_override_debugger_welcome_message";
}

// Node version migration related constants
Expand Down
35 changes: 35 additions & 0 deletions src/Runner.Worker/Dap/DapDebugger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public sealed class DapDebugger : RunnerService, IDapDebugger
private volatile DapSessionState _state = DapSessionState.NotStarted;
private CancellationTokenRegistration? _cancellationRegistration;
private bool _isFirstStep = true;
private bool _welcomeMessageSent;

// Dev Tunnel relay host for remote debugging
private TunnelRelayTunnelHost _tunnelRelayHost;
Expand Down Expand Up @@ -490,6 +491,11 @@ internal async Task HandleMessageAsync(string messageJson, CancellationToken can
});
Trace.Info("Sent initialized event");
}

if (request.Command == "configurationDone")
{
SendWelcomeMessage();
}
}
catch (Exception ex)
{
Expand All @@ -508,6 +514,7 @@ internal async Task HandleMessageAsync(string messageJson, CancellationToken can
internal void HandleClientConnected()
{
_isClientConnected = true;
_welcomeMessageSent = false;
Trace.Info("Client connected to debug session");

// If we're paused, re-send the stopped event so the new client
Expand Down Expand Up @@ -818,6 +825,34 @@ private void SendOutput(string category, string text)
});
}

internal void SendWelcomeMessage()
{
if (_welcomeMessageSent)
{
return;
}
_welcomeMessageSent = true;

Comment thread
rentziass marked this conversation as resolved.
var debuggerConfig = _jobContext?.Global?.Debugger;
if (debuggerConfig?.OverrideWelcomeMessage == true)
{
if (!string.IsNullOrEmpty(debuggerConfig.WelcomeMessage))
{
SendOutput("console", debuggerConfig.WelcomeMessage);
Trace.Info("Sent custom welcome message");
}
else
{
Trace.Info("Welcome message suppressed by override");
}
}
else
{
SendOutput("console", DapReplParser.GetGeneralHelp());
Trace.Info("Sent default welcome message");
}
}

internal async Task OnStepStartingAsync(IStep step, bool isFirstStep)
{
bool pauseOnNextStep;
Expand Down
19 changes: 17 additions & 2 deletions src/Runner.Worker/Dap/DebuggerConfig.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using GitHub.DistributedTask.Pipelines;
using GitHub.DistributedTask.Pipelines;

namespace GitHub.Runner.Worker.Dap
{
Expand All @@ -8,10 +8,12 @@ namespace GitHub.Runner.Worker.Dap
/// </summary>
public sealed class DebuggerConfig
{
public DebuggerConfig(bool enabled, DebuggerTunnelInfo tunnel)
public DebuggerConfig(bool enabled, DebuggerTunnelInfo tunnel, bool overrideWelcomeMessage = false, string welcomeMessage = null)
{
Enabled = enabled;
Tunnel = tunnel;
OverrideWelcomeMessage = overrideWelcomeMessage;
WelcomeMessage = welcomeMessage;
}

/// <summary>Whether the debugger is enabled for this job.</summary>
Expand All @@ -23,6 +25,19 @@ public DebuggerConfig(bool enabled, DebuggerTunnelInfo tunnel)
/// </summary>
public DebuggerTunnelInfo Tunnel { get; }

/// <summary>
/// When true, the runner overrides the default welcome message with
/// <see cref="WelcomeMessage"/>. A null or empty <see cref="WelcomeMessage"/>
/// suppresses the message entirely. When false, the default help text is shown.
/// </summary>
public bool OverrideWelcomeMessage { get; }

/// <summary>
/// Optional welcome message content for the debugger console. Only used when
/// <see cref="OverrideWelcomeMessage"/> is true.
/// </summary>
public string WelcomeMessage { get; }

/// <summary>Whether the tunnel configuration is complete and valid.</summary>
public bool HasValidTunnel => Tunnel != null
&& !string.IsNullOrEmpty(Tunnel.TunnelId)
Expand Down
3 changes: 2 additions & 1 deletion src/Runner.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,8 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation
Global.WriteDebug = Global.Variables.Step_Debug ?? false;

// Debugger enabled flag (from acquire response).
Global.Debugger = new Dap.DebuggerConfig(message.EnableDebugger, message.DebuggerTunnel);
var overrideDebuggerWelcomeMessage = Global.Variables.GetBoolean(Constants.Runner.Features.OverrideDebuggerWelcomeMessage) ?? false;
Global.Debugger = new Dap.DebuggerConfig(message.EnableDebugger, message.DebuggerTunnel, overrideDebuggerWelcomeMessage, message.DebuggerWelcomeMessage);

// Hook up JobServerQueueThrottling event, we will log warning on server tarpit.
_jobServerQueue.JobServerQueueThrottling += JobServerQueueThrottling_EventReceived;
Expand Down
14 changes: 14 additions & 0 deletions src/Sdk/DTPipelines/Pipelines/AgentJobRequestMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,20 @@ public DebuggerTunnelInfo DebuggerTunnel
set;
}

/// <summary>
Comment thread
rentziass marked this conversation as resolved.
/// Optional welcome message content shown in the debugger console when a client
/// connects. Only used when the
/// <c>actions_runner_override_debugger_welcome_message</c> feature flag is set
/// in <see cref="Variables"/>; a null or empty value with the flag set
/// suppresses the message entirely.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public string DebuggerWelcomeMessage
{
get;
set;
}

/// <summary>
/// Gets the workflow-level action dependencies (lockfile entries)
/// </summary>
Expand Down
34 changes: 27 additions & 7 deletions src/Test/L0/Sdk/RSWebApi/AgentJobRequestMessageL0.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
Expand All @@ -17,13 +17,13 @@ public void VerifyEnableDebuggerDeserialization_WithTrue()
// Arrange
var serializer = new DataContractJsonSerializer(typeof(AgentJobRequestMessage));
string jsonWithEnabledDebugger = DoubleQuotify("{'EnableDebugger': true}");

// Act
using var stream = new MemoryStream();
stream.Write(Encoding.UTF8.GetBytes(jsonWithEnabledDebugger));
stream.Position = 0;
var recoveredMessage = serializer.ReadObject(stream) as AgentJobRequestMessage;

// Assert
Assert.NotNull(recoveredMessage);
Assert.True(recoveredMessage.EnableDebugger, "EnableDebugger should be true when JSON contains 'EnableDebugger': true");
Expand All @@ -37,13 +37,13 @@ public void VerifyEnableDebuggerDeserialization_DefaultToFalse()
// Arrange
var serializer = new DataContractJsonSerializer(typeof(AgentJobRequestMessage));
string jsonWithoutDebugger = DoubleQuotify("{'messageType': 'PipelineAgentJobRequest'}");

// Act
using var stream = new MemoryStream();
stream.Write(Encoding.UTF8.GetBytes(jsonWithoutDebugger));
stream.Position = 0;
var recoveredMessage = serializer.ReadObject(stream) as AgentJobRequestMessage;

// Assert
Assert.NotNull(recoveredMessage);
Assert.False(recoveredMessage.EnableDebugger, "EnableDebugger should default to false when JSON field is absent");
Expand All @@ -57,13 +57,13 @@ public void VerifyEnableDebuggerDeserialization_WithFalse()
// Arrange
var serializer = new DataContractJsonSerializer(typeof(AgentJobRequestMessage));
string jsonWithDisabledDebugger = DoubleQuotify("{'EnableDebugger': false}");

// Act
using var stream = new MemoryStream();
stream.Write(Encoding.UTF8.GetBytes(jsonWithDisabledDebugger));
stream.Position = 0;
var recoveredMessage = serializer.ReadObject(stream) as AgentJobRequestMessage;

// Assert
Assert.NotNull(recoveredMessage);
Assert.False(recoveredMessage.EnableDebugger, "EnableDebugger should be false when JSON contains 'EnableDebugger': false");
Expand Down Expand Up @@ -161,6 +161,26 @@ public void VerifyActionsDependenciesDeserialization_DefaultsToEmpty()
Assert.Empty(recoveredMessage.ActionsDependencies);
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void VerifyDebuggerWelcomeMessageRoundTrips()
{
// Arrange
var serializer = new DataContractJsonSerializer(typeof(AgentJobRequestMessage));
string json = DoubleQuotify("{'DebuggerWelcomeMessage': 'Welcome to debugging!'}");

// Act
using var stream = new MemoryStream();
stream.Write(Encoding.UTF8.GetBytes(json));
stream.Position = 0;
var recoveredMessage = serializer.ReadObject(stream) as AgentJobRequestMessage;

// Assert
Assert.NotNull(recoveredMessage);
Assert.Equal("Welcome to debugging!", recoveredMessage.DebuggerWelcomeMessage);
}

private static string DoubleQuotify(string text)
{
return text.Replace('\'', '"');
Expand Down
Loading
Loading