Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 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,35 @@ private void SendOutput(string category, string text)
});
}

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

Comment thread
rentziass marked this conversation as resolved.
var welcomeMessage = _jobContext?.Global?.Debugger?.WelcomeMessage;

// null → default help text
// "" → no message
// other → custom message
if (welcomeMessage == null)
{
SendOutput("console", DapReplParser.GetGeneralHelp());
Trace.Info("Sent default welcome message");
}
else if (welcomeMessage.Length > 0)
{
SendOutput("console", welcomeMessage);
Trace.Info("Sent custom welcome message");
}
else
{
Trace.Info("Welcome message is empty, skipping");
}
}

internal async Task OnStepStartingAsync(IStep step, bool isFirstStep)
{
bool pauseOnNextStep;
Expand Down
11 changes: 9 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,11 @@ namespace GitHub.Runner.Worker.Dap
/// </summary>
public sealed class DebuggerConfig
{
public DebuggerConfig(bool enabled, DebuggerTunnelInfo tunnel)
public DebuggerConfig(bool enabled, DebuggerTunnelInfo tunnel, string welcomeMessage = null)
{
Enabled = enabled;
Tunnel = tunnel;
WelcomeMessage = welcomeMessage;
}

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

/// <summary>
/// Optional welcome message for the debugger console.
/// Null = show default help, empty = show nothing, non-empty = show as-is.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same question about the comment.

/// </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
2 changes: 1 addition & 1 deletion src/Runner.Worker/ExecutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,7 @@ 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);
Global.Debugger = new Dap.DebuggerConfig(message.EnableDebugger, message.DebuggerTunnel, message.DebuggerWelcomeMessage);

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

/// <summary>
/// Optional welcome message to show in the debugger console when a
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the comment about null/empty right?
after JSON deserialization, null and empty are the same, i think.

/// client connects. Null means "use the default help text", empty
/// string means "show nothing", any other value is displayed as-is.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public string DebuggerWelcomeMessage
{
get;
set;
}

/// <summary>
/// Gets the workflow-level action dependencies (lockfile entries)
/// </summary>
Expand Down
74 changes: 67 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,66 @@ public void VerifyActionsDependenciesDeserialization_DefaultsToEmpty()
Assert.Empty(recoveredMessage.ActionsDependencies);
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void VerifyDebuggerWelcomeMessageDeserialization_WhenAbsent()
{
// Arrange
var serializer = new DataContractJsonSerializer(typeof(AgentJobRequestMessage));
string json = DoubleQuotify("{'EnableDebugger': true}");

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

// Assert — absent key should deserialize as null
Assert.NotNull(recoveredMessage);
Assert.Null(recoveredMessage.DebuggerWelcomeMessage);
}

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

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

// Assert — empty string is preserved
Assert.NotNull(recoveredMessage);
Assert.Equal("", recoveredMessage.DebuggerWelcomeMessage);
}

[Fact]
[Trait("Level", "L0")]
[Trait("Category", "Common")]
public void VerifyDebuggerWelcomeMessageDeserialization_WithCustomMessage()
{
// 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 — custom message is preserved
Assert.NotNull(recoveredMessage);
Assert.Equal("Welcome to debugging!", recoveredMessage.DebuggerWelcomeMessage);
}

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