Version 2.15.0-30.0.dev

Merge commit '30a3b9c716273b4200eb2687d975fa50c1388d9e' into 'dev'
diff --git a/pkg/dds/lib/src/dap/adapters/dart.dart b/pkg/dds/lib/src/dap/adapters/dart.dart
index fc56f09..ed80060 100644
--- a/pkg/dds/lib/src/dap/adapters/dart.dart
+++ b/pkg/dds/lib/src/dap/adapters/dart.dart
@@ -48,6 +48,15 @@
 /// will work.
 const threadExceptionExpression = r'$_threadException';
 
+/// Pattern for extracting useful error messages from an evaluation exception.
+final _evalErrorMessagePattern = RegExp('Error: (.*)');
+
+/// Pattern for extracting useful error messages from an unhandled exception.
+final _exceptionMessagePattern = RegExp('Unhandled exception:\n(.*)');
+
+/// Pattern for a trailing semicolon.
+final _trailingSemicolonPattern = RegExp(r';$');
+
 /// A base DAP Debug Adapter implementation for running and debugging Dart-based
 /// applications (including Flutter and Tests).
 ///
@@ -170,6 +179,8 @@
   /// VM Service closing).
   bool _hasSentTerminatedEvent = false;
 
+  late final sendLogsToClient = args.sendLogsToClient ?? false;
+
   DartDebugAdapter(
     ByteStreamServerChannel channel, {
     this.ipv6 = false,
@@ -299,8 +310,7 @@
 
     logger?.call('Connecting to debugger at $uri');
     sendOutput('console', 'Connecting to VM Service at $uri\n');
-    final vmService =
-        await _vmServiceConnectUri(uri.toString(), logger: logger);
+    final vmService = await _vmServiceConnectUri(uri.toString());
     logger?.call('Connected to debugger at $uri!');
 
     // TODO(dantup): VS Code currently depends on a custom dart.debuggerUris
@@ -492,25 +502,49 @@
     // allows us to construct evaluateNames that evaluate to the fields down the
     // tree to support some of the debugger functionality (for example
     // "Copy Value", which re-evaluates).
-    final expression = args.expression.trim();
+    final expression = args.expression
+        .trim()
+        // Remove any trailing semicolon as the VM only evaluates expressions
+        // but a user may have highlighted a whole line/statement to send for
+        // evaluation.
+        .replaceFirst(_trailingSemicolonPattern, '');
     final exceptionReference = thread.exceptionReference;
     final isExceptionExpression = expression == threadExceptionExpression ||
         expression.startsWith('$threadExceptionExpression.');
 
     vm.Response? result;
-    if (exceptionReference != null && isExceptionExpression) {
-      result = await _evaluateExceptionExpression(
-        exceptionReference,
-        expression,
-        thread,
-      );
-    } else {
-      result = await vmService?.evaluateInFrame(
-        thread.isolate.id!,
-        frameIndex,
-        expression,
-        disableBreakpoints: true,
-      );
+    try {
+      if (exceptionReference != null && isExceptionExpression) {
+        result = await _evaluateExceptionExpression(
+          exceptionReference,
+          expression,
+          thread,
+        );
+      } else {
+        result = await vmService?.evaluateInFrame(
+          thread.isolate.id!,
+          frameIndex,
+          expression,
+          disableBreakpoints: true,
+        );
+      }
+    } catch (e) {
+      final rawMessage = '$e';
+
+      // Error messages can be quite verbose and don't fit well into a
+      // single-line watch window. For example:
+      //
+      //    evaluateInFrame: (113) Expression compilation error
+      //    org-dartlang-debug:synthetic_debug_expression:1:5: Error: A value of type 'String' can't be assigned to a variable of type 'num'.
+      //    1 + "a"
+      //        ^
+      //
+      // So in the case of a Watch context, try to extract the useful message.
+      if (args.context == 'watch') {
+        throw DebugAdapterException(extractEvaluationErrorMessage(rawMessage));
+      }
+
+      throw DebugAdapterException(rawMessage);
     }
 
     if (result is vm.ErrorRef) {
@@ -542,6 +576,24 @@
     }
   }
 
+  /// Tries to extract the useful part from an evaluation exception message.
+  ///
+  /// If no message could be extracted, returns the whole original error.
+  String extractEvaluationErrorMessage(String rawError) {
+    final match = _evalErrorMessagePattern.firstMatch(rawError);
+    final shortError = match != null ? match.group(1)! : null;
+    return shortError ?? rawError;
+  }
+
+  /// Tries to extract the useful part from an unhandled exception message.
+  ///
+  /// If no message could be extracted, returns the whole original error.
+  String extractUnhandledExceptionMessage(String rawError) {
+    final match = _exceptionMessagePattern.firstMatch(rawError);
+    final shortError = match != null ? match.group(1)! : null;
+    return shortError ?? rawError;
+  }
+
   /// Sends a [TerminatedEvent] if one has not already been sent.
   void handleSessionTerminate() {
     if (_hasSentTerminatedEvent) {
@@ -1280,6 +1332,13 @@
     }
   }
 
+  void _logTraffic(String data) {
+    logger?.call(data);
+    if (sendLogsToClient) {
+      sendEvent(RawEventBody(data), eventType: 'dart.log');
+    }
+  }
+
   /// Performs some setup that is common to both [launchRequest] and
   /// [attachRequest].
   Future<void> _prepareForLaunchOrAttach() async {
@@ -1319,17 +1378,15 @@
 
   /// A wrapper around the same name function from package:vm_service that
   /// allows logging all traffic over the VM Service.
-  Future<vm.VmService> _vmServiceConnectUri(
-    String wsUri, {
-    Logger? logger,
-  }) async {
+  Future<vm.VmService> _vmServiceConnectUri(String wsUri) async {
     final socket = await WebSocket.connect(wsUri);
     final controller = StreamController();
     final streamClosedCompleter = Completer();
+    final logger = this.logger;
 
     socket.listen(
       (data) {
-        logger?.call('<== [VM] $data');
+        _logTraffic('<== [VM] $data');
         controller.add(data);
       },
       onDone: () => streamClosedCompleter.complete(),
@@ -1339,6 +1396,7 @@
       controller.stream,
       (String message) {
         logger?.call('==> [VM] $message');
+        _logTraffic('==> [VM] $message');
         socket.add(message);
       },
       log: logger != null ? VmServiceLogger(logger) : null,
diff --git a/pkg/dds/lib/src/dap/base_debug_adapter.dart b/pkg/dds/lib/src/dap/base_debug_adapter.dart
index 6b15fac..1071fee 100644
--- a/pkg/dds/lib/src/dap/base_debug_adapter.dart
+++ b/pkg/dds/lib/src/dap/base_debug_adapter.dart
@@ -167,10 +167,10 @@
 
   /// Sends an event, lookup up the event type based on the runtimeType of
   /// [body].
-  void sendEvent(EventBody body) {
+  void sendEvent(EventBody body, {String? eventType}) {
     final event = Event(
       seq: _sequence++,
-      event: eventTypes[body.runtimeType]!,
+      event: eventType ?? eventTypes[body.runtimeType]!,
       body: body,
     );
     _channel.sendEvent(event);
diff --git a/pkg/dds/lib/src/dap/protocol_common.dart b/pkg/dds/lib/src/dap/protocol_common.dart
index f9c09f4..9a20311 100644
--- a/pkg/dds/lib/src/dap/protocol_common.dart
+++ b/pkg/dds/lib/src/dap/protocol_common.dart
@@ -2,12 +2,35 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
+
 /// A base class for (spec-generated) classes that represent the `body` of a an
 /// event.
 abstract class EventBody {
   static bool canParse(Object? obj) => obj is Map<String, Object?>?;
 }
 
+/// A generic event body class that just supplies an object directly.
+///
+/// Used to support custom events sent by the debug adapter such as 'dart.log'.
+///
+/// The supplied [body] must be convertable to JSON.
+class RawEventBody extends EventBody {
+  final Object body;
+
+  RawEventBody(this.body)
+      : assert(() {
+          try {
+            jsonEncode(body);
+            return true;
+          } catch (e) {
+            return false;
+          }
+        }(), 'body should be JSON encodable');
+
+  Object toJson() => body;
+}
+
 /// A generic arguments class that just supplies the arguments map directly.
 ///
 /// Used to support custom requests that may be provided by other implementing
diff --git a/pkg/dds/lib/src/dap/protocol_converter.dart b/pkg/dds/lib/src/dap/protocol_converter.dart
index 2d2f7240..a3e836e 100644
--- a/pkg/dds/lib/src/dap/protocol_converter.dart
+++ b/pkg/dds/lib/src/dap/protocol_converter.dart
@@ -99,7 +99,11 @@
           ref,
           includeQuotesAroundString: false,
         );
-        stringValue += ' ($toStringValue)';
+        // Include the toString() result only if it's not the default (which
+        // duplicates the type name we're already showing).
+        if (toStringValue != "Instance of '${ref.classRef?.name}'") {
+          stringValue += ' ($toStringValue)';
+        }
       }
       return stringValue;
     } else if (ref.kind == 'List') {
@@ -220,21 +224,29 @@
         /// Helper to evaluate each getter and convert the response to a
         /// variable.
         Future<dap.Variable> evaluate(int index, String getterName) async {
-          final response = await service.evaluate(
-            thread.isolate.id!,
-            instance.id!,
-            getterName,
-          );
-          // Convert results to variables.
-          return convertVmResponseToVariable(
-            thread,
-            response,
-            name: getterName,
-            evaluateName:
-                _adapter.combineEvaluateName(evaluateName, '.$getterName'),
-            allowCallingToString:
-                allowCallingToString && index <= maxToStringsPerEvaluation,
-          );
+          try {
+            final response = await service.evaluate(
+              thread.isolate.id!,
+              instance.id!,
+              getterName,
+            );
+            // Convert results to variables.
+            return convertVmResponseToVariable(
+              thread,
+              response,
+              name: getterName,
+              evaluateName:
+                  _adapter.combineEvaluateName(evaluateName, '.$getterName'),
+              allowCallingToString:
+                  allowCallingToString && index <= maxToStringsPerEvaluation,
+            );
+          } catch (e) {
+            return dap.Variable(
+              name: getterName,
+              value: _adapter.extractEvaluationErrorMessage('$e'),
+              variablesReference: 0,
+            );
+          }
         }
 
         variables.addAll(await Future.wait(getterNames.mapIndexed(evaluate)));
@@ -309,13 +321,21 @@
       );
     } else if (response is vm.Sentinel) {
       return dap.Variable(
-        name: '<sentinel>',
+        name: name ?? '<sentinel>',
         value: response.valueAsString.toString(),
         variablesReference: 0,
       );
+    } else if (response is vm.ErrorRef) {
+      final errorMessage = _adapter
+          .extractUnhandledExceptionMessage(response.message ?? '<error>');
+      return dap.Variable(
+        name: name ?? '<error>',
+        value: '<$errorMessage>',
+        variablesReference: 0,
+      );
     } else {
       return dap.Variable(
-        name: '<error>',
+        name: name ?? '<error>',
         value: response.runtimeType.toString(),
         variablesReference: 0,
       );
diff --git a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
index a9e8b85..c67890b 100644
--- a/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
+++ b/pkg/dds/test/dap/integration/debug_breakpoints_test.dart
@@ -19,7 +19,7 @@
     test('stops at a line breakpoint', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.hitBreakpoint(testFile, breakpointLine);
     });
@@ -27,7 +27,7 @@
     test('stops at a line breakpoint and can be resumed', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       // Hit the initial breakpoint.
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -40,14 +40,14 @@
     });
 
     test('stops at a line breakpoint and can step over (next)', () async {
-      final testFile = dap.createTestFile(r'''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) async {
-  print('Hello!'); // BREAKPOINT
-  print('Hello!'); // STEP
+  print('Hello!'); $breakpointMarker
+  print('Hello!'); $stepMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
-      final stepLine = lineWith(testFile, '// STEP');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+      final stepLine = lineWith(testFile, stepMarker);
 
       // Hit the initial breakpoint.
       final stop = await dap.client.hitBreakpoint(testFile, breakpointLine);
@@ -63,18 +63,18 @@
         'stops at a line breakpoint and can step over (next) an async boundary',
         () async {
       final client = dap.client;
-      final testFile = dap.createTestFile(r'''
+      final testFile = dap.createTestFile('''
 Future<void> main(List<String> args) async {
-  await asyncPrint('Hello!'); // BREAKPOINT
-  await asyncPrint('Hello!'); // STEP
+  await asyncPrint('Hello!'); $breakpointMarker
+  await asyncPrint('Hello!'); $stepMarker
 }
 
 Future<void> asyncPrint(String message) async {
   await Future.delayed(const Duration(milliseconds: 1));
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
-      final stepLine = lineWith(testFile, '// STEP');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+      final stepLine = lineWith(testFile, stepMarker);
 
       // Hit the initial breakpoint.
       final stop = await dap.client.hitBreakpoint(testFile, breakpointLine);
@@ -96,17 +96,17 @@
 
     test('stops at a line breakpoint and can step in', () async {
       final client = dap.client;
-      final testFile = dap.createTestFile(r'''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) async {
-  log('Hello!'); // BREAKPOINT
+  log('Hello!'); $breakpointMarker
 }
 
-void log(String message) { // STEP
+void log(String message) { $stepMarker
   print(message);
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
-      final stepLine = lineWith(testFile, '// STEP');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+      final stepLine = lineWith(testFile, stepMarker);
 
       // Hit the initial breakpoint.
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -120,18 +120,18 @@
 
     test('stops at a line breakpoint and can step out', () async {
       final client = dap.client;
-      final testFile = dap.createTestFile(r'''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) async {
   log('Hello!');
-  log('Hello!'); // STEP
+  log('Hello!'); $stepMarker
 }
 
 void log(String message) {
-  print(message); // BREAKPOINT
+  print(message); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
-      final stepLine = lineWith(testFile, '// STEP');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+      final stepLine = lineWith(testFile, stepMarker);
 
       // Hit the initial breakpoint.
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
@@ -145,14 +145,14 @@
 
     test('does not step into SDK code with debugSdkLibraries=false', () async {
       final client = dap.client;
-      final testFile = dap.createTestFile(r'''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) async {
-  print('Hello!'); // BREAKPOINT
-  print('Hello!'); // STEP
+  print('Hello!'); $breakpointMarker
+  print('Hello!'); $stepMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
-      final stepLine = lineWith(testFile, '// STEP');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+      final stepLine = lineWith(testFile, stepMarker);
 
       // Hit the initial breakpoint.
       final stop = await client.hitBreakpoint(
@@ -173,13 +173,13 @@
 
     test('steps into SDK code with debugSdkLibraries=true', () async {
       final client = dap.client;
-      final testFile = dap.createTestFile(r'''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) async {
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
   print('Hello!');
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       // Hit the initial breakpoint.
       final stop = await dap.client.hitBreakpoint(
@@ -207,12 +207,12 @@
 import '$otherPackageUri';
 
 void main(List<String> args) async {
-  foo(); // BREAKPOINT
-  foo(); // STEP
+  foo(); $breakpointMarker
+  foo(); $stepMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
-      final stepLine = lineWith(testFile, '// STEP');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+      final stepLine = lineWith(testFile, stepMarker);
 
       // Hit the initial breakpoint.
       final stop = await client.hitBreakpoint(
@@ -240,11 +240,11 @@
 import '$otherPackageUri';
 
 void main(List<String> args) async {
-  foo(); // BREAKPOINT
+  foo(); $breakpointMarker
   foo();
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       // Hit the initial breakpoint.
       final stop = await dap.client.hitBreakpoint(
@@ -272,11 +272,11 @@
 import '$otherPackageUri';
 
 void main(List<String> args) async {
-  foo(); // BREAKPOINT
+  foo(); $breakpointMarker
   foo();
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       // Hit the initial breakpoint.
       final stop = await client.hitBreakpoint(
@@ -300,14 +300,14 @@
 
     test('allows changing debug settings during session', () async {
       final client = dap.client;
-      final testFile = dap.createTestFile(r'''
+      final testFile = dap.createTestFile('''
 void main(List<String> args) async {
-  print('Hello!'); // BREAKPOINT
-  print('Hello!'); // STEP
+  print('Hello!'); $breakpointMarker
+  print('Hello!'); $stepMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
-      final stepLine = lineWith(testFile, '// STEP');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+      final stepLine = lineWith(testFile, stepMarker);
 
       // Start with debugSdkLibraryes _enabled_ and hit the breakpoint.
       final stop = await client.hitBreakpoint(
@@ -337,7 +337,7 @@
     test('stops with condition evaluating to true', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.hitBreakpoint(
         testFile,
@@ -349,7 +349,7 @@
     test('does not stop with condition evaluating to false', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.doNotHitBreakpoint(
         testFile,
@@ -361,7 +361,7 @@
     test('stops with condition evaluating to non-zero', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.hitBreakpoint(
         testFile,
@@ -373,7 +373,7 @@
     test('does not stop with condition evaluating to zero', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.doNotHitBreakpoint(
         testFile,
@@ -385,7 +385,7 @@
     test('reports evaluation errors for conditions', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final outputEventsFuture = client.outputEvents.toList();
 
@@ -422,7 +422,7 @@
     ) async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final outputEventsFuture = client.outputEvents.toList();
 
diff --git a/pkg/dds/test/dap/integration/debug_eval_test.dart b/pkg/dds/test/dap/integration/debug_eval_test.dart
index dc5b2a0..9d6ff1b 100644
--- a/pkg/dds/test/dap/integration/debug_eval_test.dart
+++ b/pkg/dds/test/dap/integration/debug_eval_test.dart
@@ -19,29 +19,31 @@
   group('debug mode evaluation', () {
     test('evaluates expressions with simple results', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   var a = 1;
   var b = 2;
   var c = 'test';
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
-      await client.expectTopFrameEvalResult(stop.threadId!, 'a', '1');
-      await client.expectTopFrameEvalResult(stop.threadId!, 'a * b', '2');
-      await client.expectTopFrameEvalResult(stop.threadId!, 'c', '"test"');
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      await client.expectEvalResult(topFrameId, 'a', '1');
+      await client.expectEvalResult(topFrameId, 'a * b', '2');
+      await client.expectEvalResult(topFrameId, 'c', '"test"');
     });
 
     test('evaluates expressions with complex results', () async {
       final client = dap.client;
       final testFile = await dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
-      final result = await client.expectTopFrameEvalResult(
-        stop.threadId!,
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      final result = await client.expectEvalResult(
+        topFrameId,
         'DateTime(2000, 1, 1)',
         'DateTime',
       );
@@ -56,12 +58,27 @@
       );
     });
 
+    test('evaluates expressions ending with semicolons', () async {
+      final client = dap.client;
+      final testFile = await dap.createTestFile('''
+void main(List<String> args) {
+  var a = 1;
+  var b = 2;
+  print('Hello!'); $breakpointMarker
+}''');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      final stop = await client.hitBreakpoint(testFile, breakpointLine);
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      await client.expectEvalResult(topFrameId, 'a + b;', '3');
+    });
+
     test(
         'evaluates complex expressions expressions with evaluateToStringInDebugViews=true',
         () async {
       final client = dap.client;
       final testFile = await dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(
         testFile,
@@ -70,8 +87,9 @@
             client.launch(testFile.path, evaluateToStringInDebugViews: true),
       );
 
-      await client.expectTopFrameEvalResult(
-        stop.threadId!,
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      await client.expectEvalResult(
+        topFrameId,
         'DateTime(2000, 1, 1)',
         'DateTime (2000-01-01 00:00:00.000)',
       );
@@ -87,9 +105,9 @@
 }''');
 
       final stop = await client.hitException(testFile);
-
-      final result = await client.expectTopFrameEvalResult(
-        stop.threadId!,
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      final result = await client.expectEvalResult(
+        topFrameId,
         threadExceptionExpression,
         '"my error"',
       );
@@ -106,8 +124,9 @@
 }''');
 
       final stop = await client.hitException(testFile);
-      final result = await client.expectTopFrameEvalResult(
-        stop.threadId!,
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      final result = await client.expectEvalResult(
+        topFrameId,
         threadExceptionExpression,
         '_Exception',
       );
@@ -125,8 +144,9 @@
     ''');
 
       final stop = await client.hitException(testFile);
-      await client.expectTopFrameEvalResult(
-        stop.threadId!,
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      await client.expectEvalResult(
+        topFrameId,
         '$threadExceptionExpression.message.length',
         '5',
       );
@@ -134,16 +154,16 @@
 
     test('can evaluate expressions in non-top frames', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   var a = 999;
   foo();
 }
 
 void foo() {
-  var a = 111; // BREAKPOINT
+  var a = 111; $breakpointMarker
 }''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       final stack = await client.getValidStack(stop.threadId!,
@@ -153,6 +173,48 @@
       await client.expectEvalResult(secondFrameId, 'a', '999');
     });
 
+    test('returns the full message for evaluation errors', () async {
+      final client = dap.client;
+      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      final stop = await client.hitBreakpoint(testFile, breakpointLine);
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      expectResponseError(
+        client.evaluate(
+          '1 + "a"',
+          frameId: topFrameId,
+        ),
+        allOf([
+          contains('evaluateInFrame: (113) Expression compilation error'),
+          contains("'String' can't be assigned to a variable of type 'num'."),
+          contains(
+            '1 + "a"\n'
+            '    ^',
+          )
+        ]),
+      );
+    });
+
+    test('returns short errors for evaluation in "watch" context', () async {
+      final client = dap.client;
+      final testFile = await dap.createTestFile(simpleBreakpointProgram);
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      final stop = await client.hitBreakpoint(testFile, breakpointLine);
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
+      expectResponseError(
+        client.evaluate(
+          '1 + "a"',
+          frameId: topFrameId,
+          context: 'watch',
+        ),
+        equals(
+          "A value of type 'String' can't be assigned "
+          "to a variable of type 'num'.",
+        ),
+      );
+    });
     // These tests can be slow due to starting up the external server process.
   }, timeout: Timeout.none);
 }
diff --git a/pkg/dds/test/dap/integration/debug_stack_test.dart b/pkg/dds/test/dap/integration/debug_stack_test.dart
index 2a348e8..72f2cb5 100644
--- a/pkg/dds/test/dap/integration/debug_stack_test.dart
+++ b/pkg/dds/test/dap/integration/debug_stack_test.dart
@@ -19,7 +19,7 @@
     test('includes expected names and async boundaries', () async {
       final client = dap.client;
       final testFile = await dap.createTestFile(simpleAsyncProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       final stack = await client.getValidStack(
@@ -61,7 +61,7 @@
     test('only sets canRestart where VM can rewind', () async {
       final client = dap.client;
       final testFile = await dap.createTestFile(simpleAsyncProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       final stack = await client.getValidStack(
@@ -92,7 +92,7 @@
     test('deemphasizes SDK frames when debugSdk=false', () async {
       final client = dap.client;
       final testFile = await dap.createTestFile(sdkStackFrameProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(
         testFile,
@@ -124,7 +124,7 @@
     test('does not deemphasize SDK frames when debugSdk=true', () async {
       final client = dap.client;
       final testFile = await dap.createTestFile(sdkStackFrameProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(
         testFile,
diff --git a/pkg/dds/test/dap/integration/debug_test.dart b/pkg/dds/test/dap/integration/debug_test.dart
index 3a58c37..8c6a42a 100644
--- a/pkg/dds/test/dap/integration/debug_test.dart
+++ b/pkg/dds/test/dap/integration/debug_test.dart
@@ -101,7 +101,7 @@
     test('provides a list of threads', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.hitBreakpoint(testFile, breakpointLine);
       final response = await client.getValidThreads();
@@ -113,7 +113,7 @@
     test('runs with DDS by default', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.hitBreakpoint(testFile, breakpointLine);
       expect(await client.ddsAvailable, isTrue);
@@ -130,7 +130,7 @@
     test('can download source code from the VM', () async {
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       // Hit the initial breakpoint.
       final stop = await dap.client.hitBreakpoint(
@@ -175,7 +175,7 @@
 
       final client = dap.client;
       final testFile = dap.createTestFile(simpleBreakpointProgram);
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       await client.hitBreakpoint(testFile, breakpointLine);
 
diff --git a/pkg/dds/test/dap/integration/debug_variables_test.dart b/pkg/dds/test/dap/integration/debug_variables_test.dart
index 8acecb6..86f4412 100644
--- a/pkg/dds/test/dap/integration/debug_variables_test.dart
+++ b/pkg/dds/test/dap/integration/debug_variables_test.dart
@@ -5,6 +5,7 @@
 import 'package:test/test.dart';
 
 import 'test_client.dart';
+import 'test_scripts.dart';
 import 'test_support.dart';
 
 main() {
@@ -17,7 +18,7 @@
   group('debug mode variables', () {
     test('provides variable list for frames', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = 1;
   foo();
@@ -25,10 +26,10 @@
 
 void foo() {
   final b = 2;
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       final stack = await client.getValidStack(
@@ -64,12 +65,7 @@
     ''');
 
       final stop = await client.hitException(testFile);
-      final stack = await client.getValidStack(
-        stop.threadId!,
-        startFrame: 0,
-        numFrames: 1,
-      );
-      final topFrameId = stack.stackFrames.first.id;
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
 
       // Check for an additional Scope named "Exceptions" that includes the
       // exception.
@@ -82,7 +78,7 @@
       );
     });
 
-    test('provides complex exception types frames', () async {
+    test('provides complex exception types for frames', () async {
       final client = dap.client;
       final testFile = await dap.createTestFile(r'''
 void main(List<String> args) {
@@ -91,12 +87,7 @@
     ''');
 
       final stop = await client.hitException(testFile);
-      final stack = await client.getValidStack(
-        stop.threadId!,
-        startFrame: 0,
-        numFrames: 1,
-      );
-      final topFrameId = stack.stackFrames.first.id;
+      final topFrameId = await client.getTopFrameId(stop.threadId!);
 
       // Check for an additional Scope named "Exceptions" that includes the
       // exception.
@@ -113,13 +104,13 @@
 
     test('includes simple variable fields', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = DateTime(2000, 1, 1);
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       await client.expectLocalVariable(
@@ -135,13 +126,13 @@
     test('includes variable getters when evaluateGettersInDebugViews=true',
         () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = DateTime(2000, 1, 1);
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(
         testFile,
@@ -181,13 +172,13 @@
 
     test('renders a simple list', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = ["first", "second", "third"];
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       await client.expectLocalVariable(
@@ -204,13 +195,13 @@
 
     test('renders a simple list subset', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = ["first", "second", "third"];
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       await client.expectLocalVariable(
@@ -227,17 +218,17 @@
 
     test('renders a simple map with keys/values', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = {
     'zero': 0,
     'one': 1,
     'two': 2
   };
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       final variables = await client.expectLocalVariable(
@@ -270,17 +261,17 @@
 
     test('renders a simple map subset', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = {
     'zero': 0,
     'one': 1,
     'two': 2
   };
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       await client.expectLocalVariable(
@@ -300,15 +291,15 @@
 
     test('renders a complex map with keys/values', () async {
       final client = dap.client;
-      final testFile = await dap.createTestFile(r'''
+      final testFile = await dap.createTestFile('''
 void main(List<String> args) {
   final myVariable = {
     DateTime(2000, 1, 1): Exception("my error")
   };
-  print('Hello!'); // BREAKPOINT
+  print('Hello!'); $breakpointMarker
 }
     ''');
-      final breakpointLine = lineWith(testFile, '// BREAKPOINT');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
 
       final stop = await client.hitBreakpoint(testFile, breakpointLine);
       final mapVariables = await client.expectLocalVariable(
@@ -355,6 +346,109 @@
         ''',
       );
     });
+
+    test('calls toString() on custom classes', () async {
+      final client = dap.client;
+      final testFile = await dap.createTestFile('''
+class Foo {
+  toString() => 'Bar!';
+}
+
+void main() {
+  final myVariable = Foo();
+  print('Hello!'); $breakpointMarker
+}
+    ''');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      final stop = await client.hitBreakpoint(
+        testFile,
+        breakpointLine,
+        launch: () => client.launch(
+          testFile.path,
+          evaluateToStringInDebugViews: true,
+        ),
+      );
+
+      await client.expectScopeVariables(
+        await client.getTopFrameId(stop.threadId!),
+        'Locals',
+        r'''
+            myVariable: Foo (Bar!), eval: myVariable
+        ''',
+      );
+    });
+
+    test('does not use toString() result if "Instance of Foo"', () async {
+      // When evaluateToStringInDebugViews=true, we should discard the result of
+      // caling toString() when it's just 'Instance of Foo' because we're already
+      // showing the type, and otherwise we show:
+      //
+      //     myVariable: Foo (Instance of Foo)
+      final client = dap.client;
+      final testFile = await dap.createTestFile('''
+class Foo {}
+
+void main() {
+  final myVariable = Foo();
+  print('Hello!'); $breakpointMarker
+}
+    ''');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      final stop = await client.hitBreakpoint(
+        testFile,
+        breakpointLine,
+        launch: () => client.launch(
+          testFile.path,
+          evaluateToStringInDebugViews: true,
+        ),
+      );
+
+      await client.expectScopeVariables(
+        await client.getTopFrameId(stop.threadId!),
+        'Locals',
+        r'''
+            myVariable: Foo, eval: myVariable
+        ''',
+      );
+    });
+
+    test('handles errors in getters', () async {
+      final client = dap.client;
+      final testFile = await dap.createTestFile('''
+class Foo {
+  String get doesNotThrow => "success";
+  String get throws => throw Exception('err');
+}
+
+void main() {
+  final myVariable = Foo();
+  print('Hello!'); $breakpointMarker
+}
+    ''');
+      final breakpointLine = lineWith(testFile, breakpointMarker);
+
+      final stop = await client.hitBreakpoint(
+        testFile,
+        breakpointLine,
+        launch: () => client.launch(
+          testFile.path,
+          evaluateGettersInDebugViews: true,
+        ),
+      );
+
+      await client.expectLocalVariable(
+        stop.threadId!,
+        expectedName: 'myVariable',
+        expectedDisplayString: 'Foo',
+        expectedVariables: '''
+            doesNotThrow: "success", eval: myVariable.doesNotThrow
+            throws: <Exception: err>
+        ''',
+        ignore: {'runtimeType'},
+      );
+    });
     // These tests can be slow due to starting up the external server process.
   }, timeout: Timeout.none);
 }
diff --git a/pkg/dds/test/dap/integration/test_client.dart b/pkg/dds/test/dap/integration/test_client.dart
index 0099e3f..150050e 100644
--- a/pkg/dds/test/dap/integration/test_client.dart
+++ b/pkg/dds/test/dap/integration/test_client.dart
@@ -346,14 +346,13 @@
   /// Returns [future].
   Future<T> _logIfSlow<T>(String name, Future<T> future) {
     var didComplete = false;
-    future.then((_) => didComplete = true);
     Future.delayed(_requestWarningDuration).then((_) {
       if (!didComplete) {
         print(
             '$name has taken longer than ${_requestWarningDuration.inSeconds}s');
       }
     });
-    return future;
+    return future.whenComplete(() => didComplete = true);
   }
 
   /// Creates a [DapTestClient] that connects the server listening on
@@ -745,17 +744,11 @@
     return variables;
   }
 
-  /// Evalutes [expression] in the top frame of thread [threadId] and expects a
-  /// specific [expectedResult].
-  Future<EvaluateResponseBody> expectTopFrameEvalResult(
+  Future<int> getTopFrameId(
     int threadId,
-    String expression,
-    String expectedResult,
   ) async {
     final stack = await getValidStack(threadId, startFrame: 0, numFrames: 1);
-    final topFrameId = stack.stackFrames.first.id;
-
-    return expectEvalResult(topFrameId, expression, expectedResult);
+    return stack.stackFrames.first.id;
   }
 
   /// Evalutes [expression] in frame [frameId] and expects a specific
diff --git a/pkg/dds/test/dap/integration/test_scripts.dart b/pkg/dds/test/dap/integration/test_scripts.dart
index 5582f34..df3ed5d 100644
--- a/pkg/dds/test/dap/integration/test_scripts.dart
+++ b/pkg/dds/test/dap/integration/test_scripts.dart
@@ -2,6 +2,9 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+/// A marker used in some test scripts/tests for where to set breakpoints.
+const breakpointMarker = '// BREAKPOINT';
+
 /// A simple empty Dart script that should run with no output and no errors.
 const emptyProgram = '''
   void main(List<String> args) {}
@@ -9,17 +12,17 @@
 
 /// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
 /// will contain SDK frames in the call stack.
-const sdkStackFrameProgram = r'''
+const sdkStackFrameProgram = '''
   void main() {
     [0].where((i) {
-      return i == 0; // BREAKPOINT
+      return i == 0; $breakpointMarker
     }).toList();
   }
 ''';
 
 /// A simple async Dart script that when stopped at the line of '// BREAKPOINT'
 /// will contain multiple stack frames across some async boundaries.
-const simpleAsyncProgram = r'''
+const simpleAsyncProgram = '''
   import 'dart:async';
 
   Future<void> main() async {
@@ -40,16 +43,16 @@
   }
 
   void four() {
-    print('!'); // BREAKPOINT
+    print('!'); $breakpointMarker
   }
 ''';
 
 /// A simple Dart script that should run with no errors and contains a comment
 /// marker '// BREAKPOINT' for use in tests that require stopping at a breakpoint
 /// but require no other context.
-const simpleBreakpointProgram = r'''
+const simpleBreakpointProgram = '''
   void main(List<String> args) async {
-    print('Hello!'); // BREAKPOINT
+    print('Hello!'); $breakpointMarker
   }
 ''';
 
@@ -70,3 +73,6 @@
     throw 'error';
   }
 ''';
+
+/// A marker used in some test scripts/tests for where to expected steps.
+const stepMarker = '// STEP';
diff --git a/pkg/dds/test/dap/integration/test_server.dart b/pkg/dds/test/dap/integration/test_server.dart
index ffaabf2..3e4cb72 100644
--- a/pkg/dds/test/dap/integration/test_server.dart
+++ b/pkg/dds/test/dap/integration/test_server.dart
@@ -53,7 +53,6 @@
   }) async {
     return InProcessDapTestServer._([
       ...?additionalArgs,
-      if (logger != null) '--verbose',
     ]);
   }
 }
@@ -107,12 +106,7 @@
 
     final _process = await Process.start(
       Platform.resolvedExecutable,
-      [
-        dapServerScript,
-        'dap',
-        ...?additionalArgs,
-        if (logger != null) '--verbose'
-      ],
+      [dapServerScript, 'dap', ...?additionalArgs],
     );
 
     return OutOfProcessDapTestServer._(_process, logger);
diff --git a/pkg/dds/test/dap/integration/test_support.dart b/pkg/dds/test/dap/integration/test_support.dart
index d5d8372..9fd5a88 100644
--- a/pkg/dds/test/dap/integration/test_support.dart
+++ b/pkg/dds/test/dap/integration/test_support.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:dds/src/dap/logging.dart';
+import 'package:dds/src/dap/protocol_generated.dart';
 import 'package:package_config/package_config.dart';
 import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
@@ -22,6 +23,13 @@
 /// simplified in VS Code by using a launch config with custom CodeLens links).
 final useInProcessDap = Platform.environment['DAP_TEST_INTERNAL'] == 'true';
 
+/// Whether to print all protocol traffic to stdout while running tests.
+///
+/// This is useful for debugging locally or on the bots and will include both
+/// DAP traffic (between the test DAP client and the DAP server) and the VM
+/// Service traffic (wrapped in a custom 'dart.log' event).
+final verboseLogging = Platform.environment['DAP_TEST_VERBOSE'] == 'true';
+
 /// A [RegExp] that matches the `path` part of a VM Service URI that contains
 /// an authentication token.
 final vmServiceAuthCodePathPattern = RegExp(r'^/[\w_\-=]{5,15}/ws$');
@@ -48,6 +56,18 @@
   );
 }
 
+/// Expects [response] to fail with a `message` matching [messageMatcher].
+expectResponseError<T>(Future<T> response, Matcher messageMatcher) {
+  expect(
+    response,
+    throwsA(
+      const TypeMatcher<Response>()
+          .having((r) => r.success, 'success', isFalse)
+          .having((r) => r.message, 'message', messageMatcher),
+    ),
+  );
+}
+
 /// Returns the 1-base line in [file] that contains [searchText].
 int lineWith(File file, String searchText) =>
     file.readAsLinesSync().indexWhere((line) => line.contains(searchText)) + 1;
@@ -128,8 +148,11 @@
 
   static Future<DapTestSession> setUp({List<String>? additionalArgs}) async {
     final server = await _startServer(additionalArgs: additionalArgs);
-    final client =
-        await DapTestClient.connect(server, captureVmServiceTraffic: true);
+    final client = await DapTestClient.connect(
+      server,
+      captureVmServiceTraffic: verboseLogging,
+      logger: verboseLogging ? print : null,
+    );
     return DapTestSession._(server, client);
   }
 
diff --git a/pkg/dds/tool/dap/run_server.dart b/pkg/dds/tool/dap/run_server.dart
index ba22d72..0178e85 100644
--- a/pkg/dds/tool/dap/run_server.dart
+++ b/pkg/dds/tool/dap/run_server.dart
@@ -26,7 +26,6 @@
   static const argIpv6 = 'ipv6';
   static const argDds = 'dds';
   static const argAuthCodes = 'auth-codes';
-  static const argVerbose = 'verbose';
 
   final Stream<List<int>> _inputStream;
   final StreamSink<List<int>> _outputSink;
diff --git a/tools/VERSION b/tools/VERSION
index 2fc2c05..088b16a 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 29
+PRERELEASE 30
 PRERELEASE_PATCH 0
\ No newline at end of file