From 2c65db137a3b68eabb2b542a93df1379053a27e6 Mon Sep 17 00:00:00 2001 From: Francesco Renzi Date: Thu, 12 Mar 2026 07:01:49 +0000 Subject: [PATCH] Wire DapVariableProvider into DapDebugSession for scope inspection Replace the stub HandleScopes/HandleVariables implementations that returned empty lists with real delegation to DapVariableProvider. Changes: - DapDebugSession now creates a DapVariableProvider on Initialize() - HandleScopes() resolves the execution context for the requested frame and delegates to the provider - HandleVariables() delegates to the provider for both top-level scope references and nested dynamic references - GetExecutionContextForFrame() maps frame IDs to contexts: frame 1 = current step, frames 1000+ = completed (no live context) - Provider is reset on each new step to invalidate stale nested refs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Runner.Worker/Dap/DapDebugSession.cs | 66 +++++++++++++++++++++--- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/src/Runner.Worker/Dap/DapDebugSession.cs b/src/Runner.Worker/Dap/DapDebugSession.cs index edac98102..31bf70337 100644 --- a/src/Runner.Worker/Dap/DapDebugSession.cs +++ b/src/Runner.Worker/Dap/DapDebugSession.cs @@ -19,12 +19,13 @@ namespace GitHub.Runner.Worker.Dap } /// - /// Minimal production DAP debug session. + /// Production DAP debug session. /// Handles step-level breakpoints with next/continue flow control, - /// client reconnection, and cancellation signal propagation. + /// scope/variable inspection, client reconnection, and cancellation + /// signal propagation. /// - /// Scope inspection, REPL, step manipulation, and time-travel debugging - /// are intentionally deferred to future iterations. + /// REPL, step manipulation, and time-travel debugging are intentionally + /// deferred to future iterations. /// public sealed class DapDebugSession : RunnerService, IDapDebugSession { @@ -62,6 +63,9 @@ namespace GitHub.Runner.Worker.Dap // Client connection tracking for reconnection support private volatile bool _isClientConnected; + // Scope/variable inspection provider — reusable by future DAP features + private DapVariableProvider _variableProvider; + public bool IsActive => _state == DapSessionState.Ready || _state == DapSessionState.Paused || @@ -72,6 +76,7 @@ namespace GitHub.Runner.Worker.Dap public override void Initialize(IHostContext hostContext) { base.Initialize(hostContext); + _variableProvider = new DapVariableProvider(hostContext); Trace.Info("DapDebugSession initialized"); } @@ -321,19 +326,43 @@ namespace GitHub.Runner.Worker.Dap private Response HandleScopes(Request request) { - // MVP: return empty scopes — scope inspection deferred + var args = request.Arguments?.ToObject(); + var frameId = args?.FrameId ?? CurrentFrameId; + + var context = GetExecutionContextForFrame(frameId); + if (context == null) + { + return CreateResponse(request, true, body: new ScopesResponseBody + { + Scopes = new List() + }); + } + + var scopes = _variableProvider.GetScopes(context); return CreateResponse(request, true, body: new ScopesResponseBody { - Scopes = new List() + Scopes = scopes }); } private Response HandleVariables(Request request) { - // MVP: return empty variables — variable inspection deferred + var args = request.Arguments?.ToObject(); + var variablesRef = args?.VariablesReference ?? 0; + + var context = _currentStep?.ExecutionContext ?? _jobContext; + if (context == null) + { + return CreateResponse(request, true, body: new VariablesResponseBody + { + Variables = new List() + }); + } + + var variables = _variableProvider.GetVariables(context, variablesRef); return CreateResponse(request, true, body: new VariablesResponseBody { - Variables = new List() + Variables = variables }); } @@ -402,6 +431,10 @@ namespace GitHub.Runner.Worker.Dap _jobContext = jobContext; _currentStepIndex = _completedSteps.Count; + // Reset variable references so stale nested refs from the + // previous step are not served to the client. + _variableProvider?.Reset(); + // Determine if we should pause bool shouldPause = isFirstStep || _pauseOnNextStep; @@ -598,6 +631,23 @@ namespace GitHub.Runner.Worker.Dap } } + /// + /// Resolves the execution context for a given stack frame ID. + /// Frame 1 = current step; frames 1000+ = completed steps (no + /// context available — those steps have already finished). + /// Falls back to the job-level context when no step is active. + /// + private IExecutionContext GetExecutionContextForFrame(int frameId) + { + if (frameId == CurrentFrameId) + { + return _currentStep?.ExecutionContext ?? _jobContext; + } + + // Completed-step frames don't carry a live execution context. + return null; + } + /// /// Sends a stopped event to the connected client. /// Silently no-ops if no client is connected.