diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart index 39263841..3b799685 100644 --- a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart +++ b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart @@ -1136,7 +1136,7 @@ class _AppListener { }), ); - subscriptions.add( + subscriptions.addAll([ vmService.onServiceEvent.listen((Event e) { switch (e.kind) { case EventKind.kServiceRegistered: @@ -1145,10 +1145,17 @@ class _AppListener { registeredServices.remove(e.service!); } }), - ); + vmService.onIsolateEvent.listen((e) { + switch (e.kind) { + case EventKind.kServiceExtensionAdded: + registeredServices.add(e.extensionRPC!); + } + }), + ]); await [ vmService.streamListen(EventStreams.kExtension), + vmService.streamListen(EventStreams.kIsolate), vmService.streamListen(EventStreams.kStderr), vmService.streamListen(EventStreams.kService), ].wait; @@ -1176,6 +1183,7 @@ class _AppListener { await Future.wait(_subscriptions.map((s) => s.cancel())); try { await _vmService.streamCancel(EventStreams.kExtension); + await _vmService.streamCancel(EventStreams.kIsolate); await _vmService.streamCancel(EventStreams.kStderr); await _vmService.streamCancel(EventStreams.kService); } on RPCError catch (_) { diff --git a/pkgs/dart_mcp_server/lib/src/mixins/prompts.dart b/pkgs/dart_mcp_server/lib/src/mixins/prompts.dart index 2a2efd98..5914a45a 100644 --- a/pkgs/dart_mcp_server/lib/src/mixins/prompts.dart +++ b/pkgs/dart_mcp_server/lib/src/mixins/prompts.dart @@ -7,6 +7,8 @@ import 'dart:async'; import 'package:dart_mcp/server.dart'; import 'package:meta/meta.dart'; +import '../utils/constants.dart'; + /// A mixin which adds support for various dart and flutter specific prompts. base mixin DashPrompts on PromptsSupport { @override @@ -17,12 +19,19 @@ base mixin DashPrompts on PromptsSupport { /// Creates the flutter driver user journey prompt based on a request. GetPromptResult _flutterDriverUserJourneyPrompt(GetPromptRequest request) { + final userJourney = + request.arguments?[ParameterNames.userJourney] as String?; return GetPromptResult( messages: [ PromptMessage( role: Role.user, content: flutterDriverUserJourneyPromptContent, ), + if (userJourney != null) + PromptMessage( + role: Role.user, + content: Content.text(text: 'The user journey is:\n$userJourney'), + ), ], ); } @@ -36,6 +45,14 @@ Prompts the LLM to attempt to accomplish a user journey in the running app using flutter driver. If successful, it will then translate the steps it followed into a flutter driver test and write that to disk. ''', + arguments: [ + PromptArgument( + name: ParameterNames.userJourney, + title: 'User Journey', + description: 'The user journey to perform and write a test for.', + required: false, + ), + ], ); @visibleForTesting @@ -52,9 +69,18 @@ Perform the following tasks in order: reading in files to accomplish this task, just inspect the live state of the app and widget tree. If you get stuck, feel free to ask the user for help. - If you are able to successfully complete the journey, then create a flutter - driver based test with an appropriate name, which performs all the same - actions that you performed. Include the original user journey as a comment - in the test file. + driver based test with an appropriate name under the integration_test + directory. The test should perform all the successful actions that you + performed to complete the task and validate the result. Include the + original user journey as a comment above the test function, or reference the + file the user journey is defined in if it came from a file. Note that + flutter_driver tests are NOT allowed to import package:flutter_test, they MUST + use package:test. Importing package:flutter_test will cause very confusing + errors and waste my time. Also, when creating variables that you will assign + in a setUp or setUpAll function, they must be late (preferred) or nullable. +- After writing the test, first analyze the project for errors, and format it. +- Next, execute the test using the command `flutter drive --driver ` + and verify that it passes. ''', ); } diff --git a/pkgs/dart_mcp_server/lib/src/utils/constants.dart b/pkgs/dart_mcp_server/lib/src/utils/constants.dart index eda9da1c..b789475a 100644 --- a/pkgs/dart_mcp_server/lib/src/utils/constants.dart +++ b/pkgs/dart_mcp_server/lib/src/utils/constants.dart @@ -24,6 +24,7 @@ extension ParameterNames on Never { static const testRunnerArgs = 'testRunnerArgs'; static const uri = 'uri'; static const uris = 'uris'; + static const userJourney = 'user_journey'; } /// A shared success response for tools. diff --git a/pkgs/dart_mcp_server/test/tools/prompts_test.dart b/pkgs/dart_mcp_server/test/tools/prompts_test.dart index 3f4b6bca..484e3488 100644 --- a/pkgs/dart_mcp_server/test/tools/prompts_test.dart +++ b/pkgs/dart_mcp_server/test/tools/prompts_test.dart @@ -4,6 +4,7 @@ import 'package:dart_mcp/server.dart'; import 'package:dart_mcp_server/src/mixins/prompts.dart'; +import 'package:dart_mcp_server/src/utils/constants.dart'; import 'package:test/test.dart'; import '../test_harness.dart'; @@ -23,29 +24,75 @@ void main() { expect( promptsResult.prompts, equals([ - isA().having( - (p) => p.name, - 'name', - DashPrompts.flutterDriverUserJourneyTest.name, - ), + isA() + .having( + (p) => p.name, + 'name', + DashPrompts.flutterDriverUserJourneyTest.name, + ) + .having( + (p) => p.arguments, + 'arguments', + equals([ + isA() + .having( + (arg) => arg.name, + 'name', + ParameterNames.userJourney, + ) + .having((arg) => arg.required, 'required', false), + ]), + ), ]), ); }); - test('can get the flutter driver user journey prompt', () async { - final server = testHarness.mcpServerConnection; - final prompt = await server.getPrompt( - GetPromptRequest(name: DashPrompts.flutterDriverUserJourneyTest.name), - ); - expect( - prompt.messages.single, - isA() - .having((m) => m.role, 'role', Role.user) - .having( - (m) => m.content, - 'content', - equals(DashPrompts.flutterDriverUserJourneyPromptContent), - ), - ); + group('Can get the flutter driver user journey prompt ', () { + test(' with no arguments', () async { + final server = testHarness.mcpServerConnection; + final prompt = await server.getPrompt( + GetPromptRequest(name: DashPrompts.flutterDriverUserJourneyTest.name), + ); + expect( + prompt.messages.single, + isA() + .having((m) => m.role, 'role', Role.user) + .having( + (m) => m.content, + 'content', + equals(DashPrompts.flutterDriverUserJourneyPromptContent), + ), + ); + }); + + test('with a user journey arguments', () async { + final server = testHarness.mcpServerConnection; + final userJourney = 'A really sick user journey'; + final prompt = await server.getPrompt( + GetPromptRequest( + name: DashPrompts.flutterDriverUserJourneyTest.name, + arguments: {ParameterNames.userJourney: userJourney}, + ), + ); + expect( + prompt.messages, + equals([ + isA() + .having((m) => m.role, 'role', Role.user) + .having( + (m) => m.content, + 'content', + equals(DashPrompts.flutterDriverUserJourneyPromptContent), + ), + isA() + .having((m) => m.role, 'role', Role.user) + .having( + (m) => (m.content as TextContent).text, + 'content.text', + contains(userJourney), + ), + ]), + ); + }); }); }