diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bdf7e0a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,113 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - uses: actions/cache@v4 + with: + path: | + ~/.pub-cache + key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} + restore-keys: pub-${{ runner.os }}- + + - run: flutter pub get + + - run: dart format --set-exit-if-changed . + + - run: dart analyze --fatal-infos + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - uses: actions/cache@v4 + with: + path: | + ~/.pub-cache + key: pub-${{ runner.os }}-${{ hashFiles('pubspec.lock') }} + restore-keys: pub-${{ runner.os }}- + + - run: flutter pub get + + - run: flutter test + + build-android: + name: Build Android + runs-on: ubuntu-latest + needs: [analyze, test] + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - uses: actions/cache@v4 + with: + path: | + ~/.pub-cache + ~/.gradle/caches + ~/.gradle/wrapper + key: android-${{ runner.os }}-${{ hashFiles('pubspec.lock', 'android/build.gradle*', 'android/app/build.gradle*') }} + restore-keys: android-${{ runner.os }}- + + - run: flutter pub get + + - run: flutter build apk --debug + + build-ios: + name: Build iOS + runs-on: macos-latest + needs: [analyze, test] + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + steps: + - uses: actions/checkout@v4 + + - uses: subosito/flutter-action@v2 + with: + channel: stable + cache: true + + - uses: actions/cache@v4 + with: + path: | + ~/.pub-cache + ios/Pods + key: ios-${{ runner.os }}-${{ hashFiles('pubspec.lock', 'ios/Podfile.lock') }} + restore-keys: ios-${{ runner.os }}- + + - run: flutter pub get + + - run: flutter build ios --debug --no-codesign diff --git a/.gitignore b/.gitignore index a7638ff..9b3600a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,14 +25,26 @@ migrate_working_dir/ # Flutter/Dart/Pub related **/doc/api/ -**/ios/Flutter/.last_build_id .dart_tool/ +.flutter-plugins .flutter-plugins-dependencies .pub-cache/ .pub/ /build/ /coverage/ +# iOS generated & dependencies +ios/Flutter/.last_build_id +ios/Flutter/ephemeral/ +ios/Flutter/flutter_export_environment.sh +ios/Flutter/Generated.xcconfig +ios/Pods/ + +# macOS generated & dependencies +macos/Flutter/ephemeral/ +macos/Flutter/GeneratedPluginRegistrant.swift +macos/Pods/ + # Symbolication related app.*.symbols diff --git a/CLAUDE.md b/CLAUDE.md index 6db96a3..f70f238 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -164,7 +164,7 @@ All user-initiated actions include idempotency keys (UUIDv7) to handle duplicate | Platform | Transport | Secure Storage | Background Sync | Push | |----------|-----------|---------------|-----------------|------| | Android (SDK 24+) | OkHttp | Keystore | WorkManager | FCM | -| iOS (15+) | URLSession | Keychain | BGTaskScheduler | APNs | +| iOS (16+) | URLSession | Keychain | BGTaskScheduler | APNs | ## Coding Conventions diff --git a/docs/plans/2026-02-17-notification-service.md b/docs/plans/2026-02-17-notification-service.md new file mode 100644 index 0000000..72af8cf --- /dev/null +++ b/docs/plans/2026-02-17-notification-service.md @@ -0,0 +1,233 @@ +# Notification Service — Push Notification Device Registration + +**Date**: 2026-02-17 +**Status**: Draft + +## Overview + +Push notification device registration for mobile clients. The app registers FCM (Android) / APNs (iOS) device tokens with the betcode-daemon via the `NotificationService` gRPC API so the daemon can push permission requests, task completions, and session status changes to idle devices. + +## Proto API + +Defined in `proto/betcode/v1/notification.proto`: + +```protobuf +service NotificationService { + rpc RegisterDevice(RegisterDeviceRequest) returns (RegisterDeviceResponse); + rpc UnregisterDevice(UnregisterDeviceRequest) returns (UnregisterDeviceResponse); +} + +message RegisterDeviceRequest { + string device_token = 1; // FCM/APNs token + DevicePlatform platform = 2; // ANDROID or IOS + string user_id = 3; // Authenticated user ID +} + +enum DevicePlatform { + DEVICE_PLATFORM_UNSPECIFIED = 0; + DEVICE_PLATFORM_ANDROID = 1; + DEVICE_PLATFORM_IOS = 2; +} + +message RegisterDeviceResponse { bool success = 1; } +message UnregisterDeviceRequest { string device_token = 1; } +message UnregisterDeviceResponse { bool success = 1; } +``` + +## Dependencies + +### New packages + +| Package | Purpose | +|---------|---------| +| `firebase_messaging` | FCM token acquisition, push receipt, token refresh stream | +| `firebase_core` | Firebase initialization (required by `firebase_messaging`) | + +### Platform setup + +- **Android**: Add `google-services.json` to `android/app/`, apply `com.google.gms:google-services` plugin in `android/app/build.gradle`. +- **iOS**: Add APNs capability in Xcode, upload APNs auth key to Firebase console. `firebase_messaging` handles the APNs-to-FCM token bridging automatically. + +## Architecture + +### File layout + +``` +lib/features/notifications/ + notification_notifier.dart # NotificationNotifier (registration state machine) + notification_providers.dart # Riverpod providers + notifications.dart # Barrel export +``` + +### Providers + +Add to `lib/core/grpc/service_providers.dart`: + +```dart +/// Provides the [NotificationServiceClient] for device registration. +final notificationServiceProvider = Provider((ref) { + final manager = ref.watch(grpcClientManagerProvider); + return NotificationServiceClient( + manager.channel, + interceptors: manager.interceptors, + ); +}); +``` + +This follows the exact pattern used by all other service providers (e.g. `agentServiceProvider`, `machineServiceProvider`). + +### NotificationNotifier + +A `Notifier` managing the device registration lifecycle: + +```dart +@freezed +sealed class NotificationState with _$NotificationState { + const factory NotificationState.unregistered() = _Unregistered; + const factory NotificationState.registering() = _Registering; + const factory NotificationState.registered({required String deviceToken}) = _Registered; + const factory NotificationState.error(String message) = _Error; +} +``` + +Key methods: + +- `register(String userId)` -- Requests FCM token, sends `RegisterDevice` RPC, stores token in secure storage, transitions to `registered`. +- `unregister()` -- Reads stored token, sends `UnregisterDevice` RPC, clears stored token, transitions to `unregistered`. +- `_onTokenRefresh(String newToken)` -- Re-registers with the new token (FCM tokens can rotate at any time). + +### Secure storage + +Add a `_keyDeviceToken` constant to `SecureStorageService` with matching `readDeviceToken()` / `writeDeviceToken()` / `deleteDeviceToken()` methods, following the existing pattern for `_keyAccessToken`. + +## Registration Flow + +### On login (`setTokens()` in `auth_notifier.dart`) + +``` +setTokens() called + --> AuthState transitions to authenticated + --> Riverpod listener on authNotifierProvider detects authenticated state + --> NotificationNotifier.register(userId) fires: + 1. FirebaseMessaging.instance.getToken() --> deviceToken + 2. notificationServiceClient.registerDevice( + RegisterDeviceRequest( + deviceToken: deviceToken, + platform: _detectPlatform(), + userId: userId, + ) + ) + 3. secureStorage.writeDeviceToken(deviceToken) + 4. State --> NotificationState.registered(deviceToken: deviceToken) +``` + +The registration is triggered by a Riverpod listener (not by modifying `setTokens()` directly), keeping `AuthNotifier` free of notification concerns. + +### On logout (`logout()` in `auth_notifier.dart`) + +``` +logout() called + --> AuthState transitions to unauthenticated + --> Riverpod listener detects unauthenticated state + --> NotificationNotifier.unregister() fires: + 1. Read stored device token from secure storage + 2. notificationServiceClient.unregisterDevice( + UnregisterDeviceRequest(deviceToken: storedToken) + ) + 3. secureStorage.deleteDeviceToken() + 4. State --> NotificationState.unregistered() +``` + +If the `UnregisterDevice` RPC fails (e.g. already offline), the token is still cleared locally. The daemon will eventually expire stale tokens server-side. + +## Token Refresh + +FCM tokens can rotate at any time (app update, cache clear, etc.). The notifier subscribes to the refresh stream: + +```dart +FirebaseMessaging.instance.onTokenRefresh.listen((newToken) { + // Re-register with the daemon using the new token + _onTokenRefresh(newToken); +}); +``` + +`_onTokenRefresh` sends a new `RegisterDevice` RPC with the updated token. The daemon is expected to handle this idempotently (upsert by user_id + platform). + +## Platform Detection + +Map `dart:io` `Platform` to the proto enum: + +```dart +DevicePlatform _detectPlatform() { + if (Platform.isAndroid) return DevicePlatform.DEVICE_PLATFORM_ANDROID; + if (Platform.isIOS) return DevicePlatform.DEVICE_PLATFORM_IOS; + return DevicePlatform.DEVICE_PLATFORM_UNSPECIFIED; +} +``` + +Desktop platforms (macOS) return `UNSPECIFIED` and skip registration entirely. + +## Existing Infrastructure + +### NotificationCache drift table (`database.dart:146-155`) + +```dart +class NotificationCache extends Table { + TextColumn get notificationId => text()(); + IntColumn get receivedAt => integer()(); + + @override + Set get primaryKey => {notificationId}; +} +``` + +Already included in the `@DriftDatabase` tables list. Used for **incoming** notification deduplication -- when a push arrives, insert `notificationId`; if it already exists, drop the duplicate. This is separate from the registration flow but part of the same feature area. + +### SecureStorageService (`secure_storage.dart`) + +Will be extended with `readDeviceToken()`, `writeDeviceToken()`, and `deleteDeviceToken()` methods using a new `_keyDeviceToken = 'device_token'` constant. Follows the existing pattern for access tokens and relay config. + +## Error Handling + +- **Registration failure**: Transition to `NotificationState.error(message)`. Retry on next app foreground or next auth state change. +- **Unregistration failure**: Log and proceed with local cleanup. Daemon handles stale token expiry. +- **FCM unavailable**: On platforms where `FirebaseMessaging` is not available (desktop), skip registration silently. +- **No permission**: On iOS, `FirebaseMessaging.instance.requestPermission()` must be called before `getToken()`. If the user denies, state stays `unregistered`. + +## Testing Strategy + +### Unit tests (`test/features/notifications/`) + +1. **Registration flow**: Mock `NotificationServiceClient` and `FirebaseMessaging`. Verify `RegisterDevice` RPC is called with correct token, platform, and userId. Verify state transitions: `unregistered -> registering -> registered`. + +2. **Unregistration flow**: Verify `UnregisterDevice` RPC is called with stored token. Verify secure storage is cleared. Verify state transitions: `registered -> unregistered`. + +3. **Token refresh**: Simulate `onTokenRefresh` stream emission. Verify re-registration RPC with new token. + +4. **Error handling**: Simulate gRPC errors. Verify state transitions to `error`. Verify unregistration proceeds despite RPC failure. + +5. **Auth state listener**: Verify registration triggers on `authenticated` state, unregistration triggers on `unauthenticated` state. + +### Mocking approach + +```dart +class MockNotificationServiceClient extends Mock + implements NotificationServiceClient {} + +class MockFirebaseMessaging extends Mock implements FirebaseMessaging {} +``` + +Use `FakeResponseFuture` from the shared test helpers (see deduplication plan Task 8) to mock gRPC responses. + +## Implementation Order + +1. Generate Dart code from `notification.proto` (`protoc --dart_out=grpc:lib/generated/`) +2. Add `notificationServiceProvider` to `service_providers.dart` +3. Add `readDeviceToken` / `writeDeviceToken` / `deleteDeviceToken` to `SecureStorageService` +4. Create `lib/features/notifications/notification_notifier.dart` with state and logic +5. Create `lib/features/notifications/notification_providers.dart` with Riverpod providers +6. Wire auth state listener in notification providers (listen to `authNotifierProvider`) +7. Add `firebase_messaging` and `firebase_core` to `pubspec.yaml` +8. Platform setup (Android `google-services.json`, iOS APNs capability) +9. Write unit tests +10. Integration test on physical device diff --git a/docs/plans/2026-02-18-error-handling.md b/docs/plans/2026-02-18-error-handling.md new file mode 100644 index 0000000..c376096 --- /dev/null +++ b/docs/plans/2026-02-18-error-handling.md @@ -0,0 +1,1087 @@ +# Error Handling Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Replace raw gRPC error display with typed exceptions, human-readable messages, and a global connectivity banner. + +**Architecture:** A new `ErrorMappingInterceptor` at the end of the gRPC interceptor chain catches `GrpcError` and rethrows as typed `AppException` subclasses. A global `ConnectivityBanner` widget wraps the app for persistent network/relay status. Per-feature code catches typed exceptions for context-appropriate UI responses. + +**Tech Stack:** Dart sealed classes, gRPC `ClientInterceptor`, Riverpod `StreamProvider`, Flutter `AnimatedSize`/`AnimatedOpacity`. + +--- + +## Task 1: Exception Hierarchy + +**Files:** +- Create: `lib/core/grpc/app_exceptions.dart` +- Modify: `lib/core/grpc/grpc.dart` (add barrel export) +- Create: `test/core/grpc/app_exceptions_test.dart` + +### Step 1: Write the failing test + +```dart +// test/core/grpc/app_exceptions_test.dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('AppException hierarchy', () { + test('NetworkError carries message and cause', () { + final cause = Exception('timeout'); + final error = NetworkError('No internet connection', cause: cause); + + expect(error.message, 'No internet connection'); + expect(error.cause, cause); + expect(error.toString(), contains('No internet connection')); + }); + + test('SessionNotFoundError includes sessionId', () { + final error = SessionNotFoundError( + 'Session no longer exists', + sessionId: 'abc-123', + ); + expect(error.sessionId, 'abc-123'); + }); + + test('all exception types are AppException', () { + expect(NetworkError('msg'), isA()); + expect(RelayUnavailableError('msg'), isA()); + expect( + SessionNotFoundError('msg', sessionId: 's'), + isA(), + ); + expect(SessionInvalidError('msg'), isA()); + expect(AuthExpiredError('msg'), isA()); + expect(PermissionDeniedError('msg'), isA()); + expect(ServerError('msg'), isA()); + expect(RateLimitError('msg'), isA()); + }); + + test('AppException is sealed — switch is exhaustive', () { + // This test verifies the sealed class is exhaustive at compile time. + // If a new subclass is added without updating this switch, it will + // fail to compile. + AppException error = NetworkError('test'); + final result = switch (error) { + NetworkError() => 'network', + RelayUnavailableError() => 'relay', + SessionNotFoundError() => 'session_not_found', + SessionInvalidError() => 'session_invalid', + AuthExpiredError() => 'auth', + PermissionDeniedError() => 'denied', + ServerError() => 'server', + RateLimitError() => 'rate_limit', + }; + expect(result, 'network'); + }); + }); +} +``` + +### Step 2: Run test to verify it fails + +Run: `flutter test test/core/grpc/app_exceptions_test.dart` +Expected: FAIL — cannot resolve `app_exceptions.dart` + +### Step 3: Write the exception hierarchy + +```dart +// lib/core/grpc/app_exceptions.dart + +/// Typed exception hierarchy for gRPC and network errors. +/// +/// Each subclass carries a user-facing [message] and an optional [cause] +/// (typically the original [GrpcError]) for debug logging. +/// +/// Sealed so `switch` expressions are exhaustive — the compiler enforces +/// that every UI error handler covers all cases. +sealed class AppException implements Exception { + const AppException(this.message, {this.cause}); + + /// Human-readable message safe to display in the UI. + final String message; + + /// The underlying error (typically [GrpcError]) for debug logging. + final Object? cause; + + @override + String toString() => message; +} + +/// Device has no network, DNS failure, connection refused, or timeout. +class NetworkError extends AppException { + const NetworkError(super.message, {super.cause}); +} + +/// gRPC channel to relay is down (TLS handshake failure, channel shutting +/// down, relay process not running). +class RelayUnavailableError extends AppException { + const RelayUnavailableError(super.message, {super.cause}); +} + +/// The requested session does not exist (deleted or never created). +class SessionNotFoundError extends AppException { + const SessionNotFoundError(super.message, {super.cause, required this.sessionId}); + + /// The session ID that was not found. + final String sessionId; +} + +/// Session operation failed due to invalid arguments or precondition +/// violation (e.g. empty name, session in wrong state). +class SessionInvalidError extends AppException { + const SessionInvalidError(super.message, {super.cause}); +} + +/// Authentication token expired or was revoked. Must re-login. +class AuthExpiredError extends AppException { + const AuthExpiredError(super.message, {super.cause}); +} + +/// The authenticated user does not have permission for this operation. +class PermissionDeniedError extends AppException { + const PermissionDeniedError(super.message, {super.cause}); +} + +/// Unexpected server-side error (INTERNAL, UNKNOWN, DATA_LOSS, etc.). +class ServerError extends AppException { + const ServerError(super.message, {super.cause}); +} + +/// Too many requests — back off and retry. +class RateLimitError extends AppException { + const RateLimitError(super.message, {super.cause}); +} +``` + +### Step 4: Add barrel export + +In `lib/core/grpc/grpc.dart`, add: +```dart +export 'app_exceptions.dart'; +``` + +### Step 5: Run test to verify it passes + +Run: `flutter test test/core/grpc/app_exceptions_test.dart` +Expected: PASS (all 4 tests) + +### Step 6: Commit + +```bash +git add lib/core/grpc/app_exceptions.dart lib/core/grpc/grpc.dart test/core/grpc/app_exceptions_test.dart +git commit -m "feat: add sealed AppException hierarchy for typed gRPC error handling" +``` + +--- + +## Task 2: Error Mapping Function + +**Files:** +- Create: `lib/core/grpc/error_mapping.dart` +- Modify: `lib/core/grpc/grpc.dart` (add barrel export) +- Create: `test/core/grpc/error_mapping_test.dart` + +### Step 1: Write the failing tests + +```dart +// test/core/grpc/error_mapping_test.dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:betcode_app/core/grpc/error_mapping.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; + +void main() { + group('mapGrpcError', () { + test('UNAVAILABLE with handshake error → RelayUnavailableError', () { + final grpcError = GrpcError.custom( + StatusCode.unavailable, + 'Error connecting: HandshakeException: WRONG_VERSION_NUMBER', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + expect(result.cause, grpcError); + }); + + test('UNAVAILABLE with channel shutting down → RelayUnavailableError', () { + final grpcError = GrpcError.custom( + StatusCode.unavailable, + 'Channel shutting down.', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + }); + + test('UNAVAILABLE generic → NetworkError', () { + final grpcError = GrpcError.custom( + StatusCode.unavailable, + 'Connection timed out', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + }); + + test('NOT_FOUND on session RPC → SessionNotFoundError', () { + final grpcError = const GrpcError.notFound('session not found'); + final result = mapGrpcError( + grpcError, + method: '/betcode.v1.AgentService/DeleteSession', + ); + expect(result, isA()); + }); + + test('NOT_FOUND on non-session RPC → ServerError', () { + final grpcError = const GrpcError.notFound('machine not found'); + final result = mapGrpcError( + grpcError, + method: '/betcode.v1.MachineService/ListMachines', + ); + expect(result, isA()); + }); + + test('UNAUTHENTICATED → AuthExpiredError', () { + final result = mapGrpcError(const GrpcError.unauthenticated()); + expect(result, isA()); + }); + + test('PERMISSION_DENIED → PermissionDeniedError', () { + final result = mapGrpcError( + GrpcError.custom(StatusCode.permissionDenied, 'not owner'), + ); + expect(result, isA()); + }); + + test('RESOURCE_EXHAUSTED → RateLimitError', () { + final result = mapGrpcError(const GrpcError.resourceExhausted()); + expect(result, isA()); + }); + + test('INVALID_ARGUMENT → SessionInvalidError', () { + final result = mapGrpcError( + GrpcError.custom(StatusCode.invalidArgument, 'name too long'), + ); + expect(result, isA()); + }); + + test('FAILED_PRECONDITION → SessionInvalidError', () { + final result = mapGrpcError( + GrpcError.custom(StatusCode.failedPrecondition, 'session locked'), + ); + expect(result, isA()); + }); + + test('INTERNAL → ServerError', () { + final result = mapGrpcError(const GrpcError.internal()); + expect(result, isA()); + }); + + test('DEADLINE_EXCEEDED → NetworkError', () { + final result = mapGrpcError(const GrpcError.deadlineExceeded()); + expect(result, isA()); + }); + + test('UNKNOWN → ServerError', () { + final result = mapGrpcError( + GrpcError.custom(StatusCode.unknown, 'something broke'), + ); + expect(result, isA()); + }); + + test('preserves original GrpcError as cause', () { + final grpcError = const GrpcError.internal('db crashed'); + final result = mapGrpcError(grpcError); + expect(result.cause, grpcError); + }); + }); +} +``` + +### Step 2: Run test to verify it fails + +Run: `flutter test test/core/grpc/error_mapping_test.dart` +Expected: FAIL — cannot resolve `error_mapping.dart` + +### Step 3: Write the mapping function + +```dart +// lib/core/grpc/error_mapping.dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:grpc/grpc.dart'; + +/// RPC method substrings that indicate session-related operations. +const _sessionMethods = [ + 'Session', // DeleteSession, RenameSession, CompactSession, ResumeSession + 'Converse', // The bidi conversation stream +]; + +/// Maps a [GrpcError] to the appropriate [AppException] subclass. +/// +/// The optional [method] parameter is the gRPC method path +/// (e.g. `/betcode.v1.AgentService/DeleteSession`) and is used to +/// distinguish session-specific NOT_FOUND from generic NOT_FOUND. +AppException mapGrpcError(GrpcError error, {String? method}) { + return switch (error.code) { + StatusCode.unavailable => _mapUnavailable(error), + StatusCode.deadlineExceeded => NetworkError( + 'Request timed out. Check your connection and try again.', + cause: error, + ), + StatusCode.notFound => _isSessionMethod(method) + ? SessionNotFoundError( + 'Session no longer exists.', + cause: error, + sessionId: '', + ) + : ServerError( + 'The requested resource was not found.', + cause: error, + ), + StatusCode.unauthenticated => AuthExpiredError( + 'Your session has expired. Please log in again.', + cause: error, + ), + StatusCode.permissionDenied => PermissionDeniedError( + 'You don\'t have permission for this action.', + cause: error, + ), + StatusCode.resourceExhausted => RateLimitError( + 'Too many requests. Please wait a moment and try again.', + cause: error, + ), + StatusCode.invalidArgument || StatusCode.failedPrecondition => + SessionInvalidError( + error.message ?? 'Invalid request.', + cause: error, + ), + _ => ServerError( + 'Something went wrong. Please try again.', + cause: error, + ), + }; +} + +AppException _mapUnavailable(GrpcError error) { + final msg = error.message ?? ''; + if (msg.contains('HandshakeException') || + msg.contains('WRONG_VERSION_NUMBER') || + msg.contains('Channel shutting down') || + msg.contains('TLS') || + msg.contains('CERTIFICATE')) { + return RelayUnavailableError( + 'Unable to reach the relay server.', + cause: error, + ); + } + return NetworkError( + 'Connection lost. Retrying...', + cause: error, + ); +} + +bool _isSessionMethod(String? method) { + if (method == null) return false; + return _sessionMethods.any(method.contains); +} +``` + +### Step 4: Add barrel export + +In `lib/core/grpc/grpc.dart`, add: +```dart +export 'error_mapping.dart'; +``` + +### Step 5: Run test to verify it passes + +Run: `flutter test test/core/grpc/error_mapping_test.dart` +Expected: PASS (all 14 tests) + +### Step 6: Commit + +```bash +git add lib/core/grpc/error_mapping.dart lib/core/grpc/grpc.dart test/core/grpc/error_mapping_test.dart +git commit -m "feat: add mapGrpcError function mapping status codes to typed AppExceptions" +``` + +--- + +## Task 3: Error Mapping Interceptor + +**Files:** +- Modify: `lib/core/grpc/interceptors.dart` (add `ErrorMappingInterceptor`) +- Modify: `lib/core/grpc/grpc_providers.dart:27-37` (add interceptor to chain) +- Create: `test/core/grpc/error_mapping_interceptor_test.dart` + +### Step 1: Write the failing tests + +```dart +// test/core/grpc/error_mapping_interceptor_test.dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:betcode_app/core/grpc/interceptors.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; + +import '../../core/interceptor_test_helpers.dart'; + +void main() { + group('ErrorMappingInterceptor', () { + late ErrorMappingInterceptor interceptor; + + setUp(() { + interceptor = ErrorMappingInterceptor(); + }); + + test('passes through successful unary responses unchanged', () async { + final response = interceptor.interceptUnary( + FakeClientMethod('/test/Ok'), + 'request', + CallOptions(), + (method, request, options) => MockResponseFuture.value('success'), + ); + expect(await response, 'success'); + }); + + test('maps GrpcError to AppException on unary failure', () async { + final response = interceptor.interceptUnary( + FakeClientMethod('/betcode.v1.AgentService/DeleteSession'), + 'request', + CallOptions(), + (method, request, options) => + MockResponseFuture.error(const GrpcError.notFound()), + ); + expect( + () => response, + throwsA(isA()), + ); + }); + + test('maps UNAVAILABLE to NetworkError on unary', () async { + final response = interceptor.interceptUnary( + FakeClientMethod('/test/Rpc'), + 'request', + CallOptions(), + (method, request, options) => + MockResponseFuture.error(const GrpcError.unavailable()), + ); + expect( + () => response, + throwsA(isA()), + ); + }); + + test('non-GrpcError passes through unmapped', () async { + final response = interceptor.interceptUnary( + FakeClientMethod('/test/Rpc'), + 'request', + CallOptions(), + (method, request, options) => + MockResponseFuture.error(Exception('not grpc')), + ); + expect( + () => response, + throwsA(isA()), + ); + }); + }); +} +``` + +Note: this test depends on `test/core/interceptor_test_helpers.dart` which should already contain `FakeClientMethod` and `MockResponseFuture` helpers. Check the existing file and add any missing helpers. If `MockResponseFuture.error` doesn't exist, add it following the existing pattern. + +### Step 2: Run test to verify it fails + +Run: `flutter test test/core/grpc/error_mapping_interceptor_test.dart` +Expected: FAIL — `ErrorMappingInterceptor` not found + +### Step 3: Add the interceptor class + +Append to `lib/core/grpc/interceptors.dart`: + +```dart +/// Maps [GrpcError] responses to typed [AppException] subclasses. +/// +/// Must be the **last** interceptor in the chain so it wraps errors from +/// all preceding interceptors (auth refresh, logging, etc.). +class ErrorMappingInterceptor extends ClientInterceptor { + @override + ResponseFuture interceptUnary( + ClientMethod method, + Q request, + CallOptions options, + ClientUnaryInvoker invoker, + ) { + final response = invoker(method, request, options); + return response.catchError( + (Object error) => throw mapGrpcError(error as GrpcError, method: method.path), + test: (error) => error is GrpcError, + ) as ResponseFuture; + } + + @override + ResponseStream interceptStreaming( + ClientMethod method, + Stream requests, + CallOptions options, + ClientStreamingInvoker invoker, + ) { + final stream = invoker(method, requests, options); + return stream.handleError( + (Object error) => throw mapGrpcError(error as GrpcError, method: method.path), + test: (error) => error is GrpcError, + ) as ResponseStream; + } +} +``` + +Add import at top of `interceptors.dart`: +```dart +import 'package:betcode_app/core/grpc/error_mapping.dart'; +``` + +### Step 4: Wire into interceptor chain + +In `lib/core/grpc/grpc_providers.dart:27-37`, add `ErrorMappingInterceptor()` as the **last** item in the interceptors list: + +```dart +interceptors: [ + TokenRefreshInterceptor( + authNotifier: authNotifier, + authClientFactory: () => AuthServiceClient(manager.channel), + ), + AuthInterceptor(tokenProvider: () async => authNotifier.accessToken), + MachineIdInterceptor( + machineIdProvider: () async => ref.read(selectedMachineIdProvider), + ), + LoggingInterceptor(), + ErrorMappingInterceptor(), // <-- ADD: must be last +], +``` + +### Step 5: Run test to verify it passes + +Run: `flutter test test/core/grpc/error_mapping_interceptor_test.dart` +Expected: PASS + +Also run existing interceptor tests to verify no regressions: +Run: `flutter test test/core/grpc/interceptors_test.dart test/core/grpc/token_refresh_interceptor_test.dart` +Expected: PASS + +### Step 6: Commit + +```bash +git add lib/core/grpc/interceptors.dart lib/core/grpc/grpc_providers.dart test/core/grpc/error_mapping_interceptor_test.dart +git commit -m "feat: add ErrorMappingInterceptor to convert GrpcError into typed AppExceptions" +``` + +--- + +## Task 4: Global Connectivity Banner + +**Files:** +- Create: `lib/shared/widgets/connectivity_banner.dart` +- Modify: `lib/shared/widgets/widgets.dart` (add barrel export, if exists) +- Modify: `lib/app.dart:11-22` (wrap MaterialApp.router) +- Create: `test/shared/widgets/connectivity_banner_test.dart` + +### Step 1: Write the failing test + +```dart +// test/shared/widgets/connectivity_banner_test.dart +import 'package:betcode_app/core/grpc/connection_state.dart'; +import 'package:betcode_app/core/grpc/grpc_providers.dart'; +import 'package:betcode_app/core/sync/connectivity.dart'; +import 'package:betcode_app/shared/widgets/connectivity_banner.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ConnectivityBanner', () { + Widget buildApp({ + AsyncValue connectionStatus = + const AsyncData(GrpcConnectionStatus.connected), + AsyncValue networkStatus = + const AsyncData(NetworkStatus.online), + }) { + return ProviderScope( + overrides: [ + connectionStatusProvider.overrideWith((_) => connectionStatus.when( + data: (d) => Stream.value(d), + loading: () => const Stream.empty(), + error: (e, s) => Stream.error(e, s), + )), + networkStatusProvider.overrideWith((_) => networkStatus.when( + data: (d) => Stream.value(d), + loading: () => const Stream.empty(), + error: (e, s) => Stream.error(e, s), + )), + ], + child: const MaterialApp( + home: Scaffold( + body: Column( + children: [ + ConnectivityBanner(), + Expanded(child: Placeholder()), + ], + ), + ), + ), + ); + } + + testWidgets('hidden when online and connected', (tester) async { + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + expect(find.text('No internet connection'), findsNothing); + expect(find.text('Relay unreachable'), findsNothing); + }); + + testWidgets('shows offline banner when network is offline', (tester) async { + await tester.pumpWidget(buildApp( + networkStatus: const AsyncData(NetworkStatus.offline), + )); + await tester.pumpAndSettle(); + expect(find.textContaining('No internet'), findsOneWidget); + }); + + testWidgets('shows relay banner when disconnected but online', + (tester) async { + await tester.pumpWidget(buildApp( + connectionStatus: + const AsyncData(GrpcConnectionStatus.reconnecting), + )); + await tester.pumpAndSettle(); + expect(find.textContaining('reconnecting'), findsOneWidget); + }); + }); +} +``` + +### Step 2: Run test to verify it fails + +Run: `flutter test test/shared/widgets/connectivity_banner_test.dart` +Expected: FAIL — cannot resolve `connectivity_banner.dart` + +### Step 3: Write the banner widget + +```dart +// lib/shared/widgets/connectivity_banner.dart +import 'package:betcode_app/core/grpc/connection_state.dart'; +import 'package:betcode_app/core/grpc/grpc_providers.dart'; +import 'package:betcode_app/core/sync/connectivity.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/// A persistent banner shown at the top of the app when there are +/// connectivity issues. +/// +/// Watches [networkStatusProvider] and [connectionStatusProvider] to +/// determine what to display. Hidden (zero height) when everything is fine. +class ConnectivityBanner extends ConsumerWidget { + const ConnectivityBanner({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final networkAsync = ref.watch(networkStatusProvider); + final connectionAsync = ref.watch(connectionStatusProvider); + + final isOffline = + networkAsync.valueOrNull == NetworkStatus.offline; + final connectionStatus = connectionAsync.valueOrNull; + final isRelayDown = !isOffline && + (connectionStatus == GrpcConnectionStatus.disconnected || + connectionStatus == GrpcConnectionStatus.reconnecting); + + final String? message; + final Color? backgroundColor; + final IconData? icon; + + if (isOffline) { + message = 'No internet connection'; + backgroundColor = Theme.of(context).colorScheme.error; + icon = Icons.wifi_off; + } else if (isRelayDown) { + message = connectionStatus == GrpcConnectionStatus.reconnecting + ? 'Relay unreachable — reconnecting...' + : 'Relay unreachable'; + backgroundColor = Theme.of(context).colorScheme.tertiary; + icon = Icons.cloud_off; + } else { + message = null; + backgroundColor = null; + icon = null; + } + + return AnimatedSize( + duration: const Duration(milliseconds: 200), + child: message != null + ? MaterialBanner( + content: Row( + children: [ + Icon(icon, color: Colors.white, size: 18), + const SizedBox(width: 8), + Expanded( + child: Text( + message, + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + backgroundColor: backgroundColor, + actions: const [SizedBox.shrink()], + ) + : const SizedBox.shrink(), + ); + } +} +``` + +### Step 4: Wrap `MaterialApp.router` in `app.dart` + +Replace `lib/app.dart:11-22` build method: + +```dart +@override +Widget build(BuildContext context, WidgetRef ref) { + ref.watch(relayAutoReconnectProvider); + final router = ref.watch(routerProvider); + + return Column( + children: [ + const ConnectivityBanner(), + Expanded( + child: MaterialApp.router( + title: 'BetCode', + debugShowCheckedModeBanner: false, + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + routerConfig: router, + ), + ), + ], + ); +} +``` + +Add import: +```dart +import 'package:betcode_app/shared/widgets/connectivity_banner.dart'; +``` + +### Step 5: Run test to verify it passes + +Run: `flutter test test/shared/widgets/connectivity_banner_test.dart` +Expected: PASS + +### Step 6: Commit + +```bash +git add lib/shared/widgets/connectivity_banner.dart lib/app.dart test/shared/widgets/connectivity_banner_test.dart +git commit -m "feat: add global ConnectivityBanner for persistent network/relay status" +``` + +--- + +## Task 5: Update Conversation Notifier Error Handling + +**Files:** +- Modify: `lib/features/conversation/notifiers/conversation_notifier.dart` + - `_isFatalError()` (lines 326-333) + - `_handleStreamError()` (lines 290-324) + - `startConversation()` (lines 141-143) +- Modify: `test/features/conversation/notifiers/conversation_notifier_test.dart` (add new test cases) + +### Step 1: Write the failing tests + +Add to `test/features/conversation/notifiers/conversation_notifier_test.dart` in an appropriate test group: + +```dart +group('typed error handling', () { + test('SessionNotFoundError transitions to error with sessionExpired', () async { + // Setup: start a conversation, then simulate stream error with NOT_FOUND + // The conversation state should transition to ConversationState.error() + // and the error message should be human-readable. + }); + + test('NetworkError triggers reconnection, not fatal', () async { + // Setup: start conversation, simulate NetworkError on stream + // Should attempt reconnection, not go to error state. + }); + + test('AuthExpiredError is fatal, no reconnect', () async { + // Setup: start conversation, simulate AuthExpiredError + // Should go directly to error state, no reconnect attempt. + }); + + test('history load failure sets errorMessage on active state', () async { + // Setup: start conversation with sessionId, mock resumeSession to throw + // Conversation should be ConversationActive with + // errorMessage = "Couldn't load message history" + }); +}); +``` + +Note: follow the existing test patterns in `conversation_notifier_test.dart` and `conversation_notifier_helpers.dart` for mocking. These are skeleton tests — fill in based on the existing mock setup. + +### Step 2: Run tests to verify they fail + +Run: `flutter test test/features/conversation/notifiers/conversation_notifier_test.dart` +Expected: New tests FAIL + +### Step 3: Update `_isFatalError` to use typed exceptions + +Replace `_isFatalError` (lines 326-333): + +```dart +bool _isFatalError(Object error) { + return error is AuthExpiredError || + error is PermissionDeniedError || + error is SessionNotFoundError; +} +``` + +Add import: +```dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +``` + +### Step 4: Update `_handleStreamError` to use human-readable messages + +In `_handleStreamError` (lines 290-324), replace the raw error string in `ConversationState.error(...)`: + +```dart +void _handleStreamError(Object error) { + debugPrint('[Conversation] Stream error: $error'); + unawaited(_eventSubscription?.cancel()); + _eventSubscription = null; + + if (_isFatalError(error)) { + _isReconnecting = false; + final message = error is AppException + ? error.message + : 'Stream error: $error'; + state = AsyncData(ConversationState.error(message)); + unawaited(_requestController?.close()); + _requestController = null; + return; + } + + // ... rest of reconnection logic stays the same, but update error messages: + // Replace 'Stream error: $error' with human-readable message from AppException +} +``` + +### Step 5: Update `_loadHistory` to set `errorMessage` + +Replace the catch block in `_loadHistory` (lines 171-174): + +```dart +} on GrpcError catch (e) { + debugPrint('[Conversation] History load failed: $e'); + final current = state.value; + if (current is ConversationActive) { + state = AsyncData( + current.copyWith( + errorMessage: "Couldn't load message history.", + ), + ); + } +} +``` + +### Step 6: Update `startConversation` catch block + +Replace the catch block in `startConversation` (lines 141-143): + +```dart +} on Exception catch (e) { + final message = e is AppException ? e.message : 'Failed to start conversation: $e'; + state = AsyncData(ConversationState.error(message)); +} +``` + +### Step 7: Run tests to verify they pass + +Run: `flutter test test/features/conversation/notifiers/conversation_notifier_test.dart` +Expected: PASS (all tests including new ones) + +### Step 8: Commit + +```bash +git add lib/features/conversation/notifiers/conversation_notifier.dart test/features/conversation/notifiers/conversation_notifier_test.dart +git commit -m "feat: conversation notifier uses typed AppExceptions for error handling" +``` + +--- + +## Task 6: Update Sessions Screen Error Display + +**Files:** +- Modify: `lib/features/sessions/screens/sessions_screen.dart:56-78` +- Modify: `lib/shared/widgets/error_display.dart:41-47` + +### Step 1: Update SnackBar error messages in sessions screen + +Replace `_onRename` catch block (lines 56-61): + +```dart +} on AppException catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.message)), + ); +} on Exception catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Rename failed: $e')), + ); +} +``` + +Replace `_onDelete` catch block (lines 73-78): + +```dart +} on SessionNotFoundError catch (_) { + // Session already gone — just refresh the list. + ref.read(sessionsProvider.notifier).refresh(); +} on AppException catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.message)), + ); +} on Exception catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Delete failed: $e')), + ); +} +``` + +Add import: +```dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +``` + +### Step 2: Update `ErrorDisplay` to use `AppException.message` + +In `lib/shared/widgets/error_display.dart:41-47`, replace: + +```dart +Text( + error.toString(), +``` + +with: + +```dart +Text( + error is AppException ? error.message : error.toString(), +``` + +Add import: +```dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +``` + +### Step 3: Run all tests + +Run: `flutter test` +Expected: PASS — no regressions + +### Step 4: Commit + +```bash +git add lib/features/sessions/screens/sessions_screen.dart lib/shared/widgets/error_display.dart +git commit -m "feat: sessions screen and ErrorDisplay show human-readable AppException messages" +``` + +--- + +## Task 7: Update Auth Notifier Error Handling + +**Files:** +- Modify: `lib/core/auth/auth_notifier.dart:114-120` + +### Step 1: Update `_isAuthError` to use typed exceptions + +Replace `_isAuthError` (lines 114-120): + +```dart +static bool _isAuthError(Object error) { + return error is AuthExpiredError || error is PermissionDeniedError; +} +``` + +This is a simplification — the `ErrorMappingInterceptor` now converts `GrpcError` with `unauthenticated`/`permissionDenied` codes into these typed exceptions before they reach the auth notifier. + +However, we must also keep the old `GrpcError` check as a fallback since `_isAuthError` may be called from code paths where the interceptor hasn't run (e.g. direct `GrpcError` from health check or token refresh): + +```dart +static bool _isAuthError(Object error) { + if (error is AuthExpiredError || error is PermissionDeniedError) { + return true; + } + if (error is GrpcError) { + return error.code == StatusCode.unauthenticated || + error.code == StatusCode.permissionDenied; + } + return false; +} +``` + +Add import: +```dart +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +``` + +### Step 2: Run auth tests + +Run: `flutter test test/core/auth/` +Expected: PASS + +### Step 3: Commit + +```bash +git add lib/core/auth/auth_notifier.dart +git commit -m "feat: auth notifier recognizes typed AppExceptions for auth error detection" +``` + +--- + +## Task 8: Final Integration Test + +**Files:** +- No new files — verification only + +### Step 1: Run `dart analyze` + +Run: `dart analyze lib/ test/` +Expected: Zero issues + +### Step 2: Run full test suite + +Run: `flutter test` +Expected: All tests pass + +### Step 3: Check lint + +Run: `dart format --set-exit-if-changed lib/ test/` +Expected: No formatting changes needed (or fix any issues) + +### Step 4: Manual testing checklist + +On a device or emulator, verify: +- [ ] Kill the relay → orange "Relay unreachable" banner appears, app does NOT logout +- [ ] Turn on airplane mode → red "No internet" banner appears +- [ ] Restore network → banner disappears smoothly +- [ ] Open a deleted session via deep link → redirects to sessions list with "Session no longer exists" toast +- [ ] Rename with empty name → SnackBar shows human message, not raw GrpcError +- [ ] Normal operations (list sessions, start conversation, send message) work unchanged + +### Step 5: Final commit (if any fixups needed) + +```bash +git add -A +git commit -m "fix: integration fixups for error handling" +``` diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index b5586f2..f598305 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 13.0 + 16.0 diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 0b2d479..dfd2626 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 0b2d479..a97381a 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/ios/Podfile.lock b/ios/Podfile.lock new file mode 100644 index 0000000..724bbd5 --- /dev/null +++ b/ios/Podfile.lock @@ -0,0 +1,69 @@ +PODS: + - connectivity_plus (0.0.1): + - Flutter + - Flutter (1.0.0) + - flutter_secure_storage_darwin (10.0.0): + - Flutter + - FlutterMacOS + - integration_test (0.0.1): + - Flutter + - sqlite3 (3.51.1): + - sqlite3/common (= 3.51.1) + - sqlite3/common (3.51.1) + - sqlite3/dbstatvtab (3.51.1): + - sqlite3/common + - sqlite3/fts5 (3.51.1): + - sqlite3/common + - sqlite3/math (3.51.1): + - sqlite3/common + - sqlite3/perf-threadsafe (3.51.1): + - sqlite3/common + - sqlite3/rtree (3.51.1): + - sqlite3/common + - sqlite3/session (3.51.1): + - sqlite3/common + - sqlite3_flutter_libs (0.0.1): + - Flutter + - FlutterMacOS + - sqlite3 (~> 3.51.1) + - sqlite3/dbstatvtab + - sqlite3/fts5 + - sqlite3/math + - sqlite3/perf-threadsafe + - sqlite3/rtree + - sqlite3/session + +DEPENDENCIES: + - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - Flutter (from `Flutter`) + - flutter_secure_storage_darwin (from `.symlinks/plugins/flutter_secure_storage_darwin/darwin`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + - sqlite3_flutter_libs (from `.symlinks/plugins/sqlite3_flutter_libs/darwin`) + +SPEC REPOS: + trunk: + - sqlite3 + +EXTERNAL SOURCES: + connectivity_plus: + :path: ".symlinks/plugins/connectivity_plus/ios" + Flutter: + :path: Flutter + flutter_secure_storage_darwin: + :path: ".symlinks/plugins/flutter_secure_storage_darwin/darwin" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + sqlite3_flutter_libs: + :path: ".symlinks/plugins/sqlite3_flutter_libs/darwin" + +SPEC CHECKSUMS: + connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + flutter_secure_storage_darwin: acdb3f316ed05a3e68f856e0353b133eec373a23 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b + sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41 + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 6d1a5aa..3df1f77 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -1,616 +1,732 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 54; - objects = { - -/* Begin PBXBuildFile section */ - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 9705A1C41CF9048500538489 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; - 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; - 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; - 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 97C146EB1CF9000F007C117D /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 331C8082294A63A400263BE5 /* RunnerTests */ = { - isa = PBXGroup; - children = ( - 331C807B294A618700263BE5 /* RunnerTests.swift */, - ); - path = RunnerTests; - sourceTree = ""; - }; - 9740EEB11CF90186004384FC /* Flutter */ = { - isa = PBXGroup; - children = ( - 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEB21CF90195004384FC /* Debug.xcconfig */, - 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, - 9740EEB31CF90195004384FC /* Generated.xcconfig */, - ); - name = Flutter; - sourceTree = ""; - }; - 97C146E51CF9000F007C117D = { - isa = PBXGroup; - children = ( - 9740EEB11CF90186004384FC /* Flutter */, - 97C146F01CF9000F007C117D /* Runner */, - 97C146EF1CF9000F007C117D /* Products */, - 331C8082294A63A400263BE5 /* RunnerTests */, - ); - sourceTree = ""; - }; - 97C146EF1CF9000F007C117D /* Products */ = { - isa = PBXGroup; - children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, - 331C8081294A63A400263BE5 /* RunnerTests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { - isa = PBXGroup; - children = ( - 97C146FA1CF9000F007C117D /* Main.storyboard */, - 97C146FD1CF9000F007C117D /* Assets.xcassets */, - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, - 97C147021CF9000F007C117D /* Info.plist */, - 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, - 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, - 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, - 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, - ); - path = Runner; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 331C8080294A63A400263BE5 /* RunnerTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; - buildPhases = ( - 331C807D294A63A400263BE5 /* Sources */, - 331C807F294A63A400263BE5 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 331C8086294A63A400263BE5 /* PBXTargetDependency */, - ); - name = RunnerTests; - productName = RunnerTests; - productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 97C146ED1CF9000F007C117D /* Runner */ = { - isa = PBXNativeTarget; - buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; - buildPhases = ( - 9740EEB61CF901F6004384FC /* Run Script */, - 97C146EA1CF9000F007C117D /* Sources */, - 97C146EB1CF9000F007C117D /* Frameworks */, - 97C146EC1CF9000F007C117D /* Resources */, - 9705A1C41CF9048500538489 /* Embed Frameworks */, - 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = Runner; - productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; - productType = "com.apple.product-type.application"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 97C146E61CF9000F007C117D /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = YES; - LastUpgradeCheck = 1510; - ORGANIZATIONNAME = ""; - TargetAttributes = { - 331C8080294A63A400263BE5 = { - CreatedOnToolsVersion = 14.0; - TestTargetID = 97C146ED1CF9000F007C117D; - }; - 97C146ED1CF9000F007C117D = { - CreatedOnToolsVersion = 7.3.1; - LastSwiftMigration = 1100; - }; - }; - }; - buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 9.3"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 97C146E51CF9000F007C117D; - productRefGroup = 97C146EF1CF9000F007C117D /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 97C146ED1CF9000F007C117D /* Runner */, - 331C8080294A63A400263BE5 /* RunnerTests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 331C807F294A63A400263BE5 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EC1CF9000F007C117D /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, - 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 331C807D294A63A400263BE5 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 97C146EA1CF9000F007C117D /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, - 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin PBXVariantGroup section */ - 97C146FA1CF9000F007C117D /* Main.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C146FB1CF9000F007C117D /* Base */, - ); - name = Main.storyboard; - sourceTree = ""; - }; - 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 97C147001CF9000F007C117D /* Base */, - ); - name = LaunchScreen.storyboard; - sourceTree = ""; - }; -/* End PBXVariantGroup section */ - -/* Begin XCBuildConfiguration section */ - 249021D3217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Profile; - }; - 249021D4217E4FDB00AE95B9 /* Profile */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Profile; - }; - 331C8088294A63A400263BE5 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Debug; - }; - 331C8089294A63A400263BE5 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Release; - }; - 331C808A294A63A400263BE5 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp.RunnerTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 5.0; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; - }; - name = Profile; - }; - 97C147031CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 97C147041CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; - SUPPORTED_PLATFORMS = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - SWIFT_OPTIMIZATION_LEVEL = "-O"; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 97C147061CF9000F007C117D /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Debug; - }; - 97C147071CF9000F007C117D /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - ENABLE_BITCODE = NO; - INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; - SWIFT_VERSION = 5.0; - VERSIONING_SYSTEM = "apple-generic"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 331C8088294A63A400263BE5 /* Debug */, - 331C8089294A63A400263BE5 /* Release */, - 331C808A294A63A400263BE5 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147031CF9000F007C117D /* Debug */, - 97C147041CF9000F007C117D /* Release */, - 249021D3217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 97C147061CF9000F007C117D /* Debug */, - 97C147071CF9000F007C117D /* Release */, - 249021D4217E4FDB00AE95B9 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 97C146E61CF9000F007C117D /* Project object */; -} +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 5D047C6C4EF47E19EEE443BC /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E1AC62DB6A5A840090AF17D /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + EB4C202DA96D5450FBC06ADB /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7C450E4C41ECB1A1D6B15537 /* Pods_Runner.framework */; }; + F1A2B3C51ED2DC5600515810 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A2B3C41ED2DC5600515810 /* SceneDelegate.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0E1AC62DB6A5A840090AF17D /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 1851A2292E885E7F7723B997 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 22DCF7218B6E86C0FD3CD542 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 24CE31D94497CB2FEF229A47 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 30406E0CBF89D1B8EF64AB3F /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 5A25F8957B81DBF896C47999 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7C450E4C41ECB1A1D6B15537 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7CBABE939792D9926A297C91 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F1A2B3C41ED2DC5600515810 /* SceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7EF05B9695710C9D8071CEDE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5D047C6C4EF47E19EEE443BC /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EB4C202DA96D5450FBC06ADB /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 047F59B8B71EC0608564A43C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7C450E4C41ECB1A1D6B15537 /* Pods_Runner.framework */, + 0E1AC62DB6A5A840090AF17D /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 8F1240ABECAE23A528193361 /* Pods */ = { + isa = PBXGroup; + children = ( + 22DCF7218B6E86C0FD3CD542 /* Pods-Runner.debug.xcconfig */, + 7CBABE939792D9926A297C91 /* Pods-Runner.release.xcconfig */, + 1851A2292E885E7F7723B997 /* Pods-Runner.profile.xcconfig */, + 30406E0CBF89D1B8EF64AB3F /* Pods-RunnerTests.debug.xcconfig */, + 5A25F8957B81DBF896C47999 /* Pods-RunnerTests.release.xcconfig */, + 24CE31D94497CB2FEF229A47 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 8F1240ABECAE23A528193361 /* Pods */, + 047F59B8B71EC0608564A43C /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + F1A2B3C41ED2DC5600515810 /* SceneDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + A53E9055CC399A8BA94790CC /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 7EF05B9695710C9D8071CEDE /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 57E42E1A65C9A88D472BD761 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 67446E1DA34FCBBC13A74DF4 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 57E42E1A65C9A88D472BD761 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 67446E1DA34FCBBC13A74DF4 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + A53E9055CC399A8BA94790CC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + F1A2B3C51ED2DC5600515810 /* SceneDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 30406E0CBF89D1B8EF64AB3F /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5A25F8957B81DBF896C47999 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 24CE31D94497CB2FEF229A47 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = dev.sakost.betcodeApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata index 59c6d39..21a3cc1 100644 --- a/ios/Runner.xcworkspace/contents.xcworkspacedata +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -1,7 +1,10 @@ - - - - - + + + + + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 8be1cec..ada6779 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -7,7 +7,26 @@ import UIKit _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) + // Plugin registration is deferred to SceneDelegate.scene(_:willConnectTo:options:) + // because the window (and FlutterViewController) is not available yet + // under the UIScene lifecycle. return super.application(application, didFinishLaunchingWithOptions: launchOptions) } + + override func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + return UISceneConfiguration( + name: "Default Configuration", + sessionRole: connectingSceneSession.role + ) + } + + override func application( + _ application: UIApplication, + didDiscardSceneSessions sceneSessions: Set + ) { + } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 54bd098..8330fd1 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -26,8 +26,23 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/ios/Runner/SceneDelegate.swift b/ios/Runner/SceneDelegate.swift new file mode 100644 index 0000000..5596312 --- /dev/null +++ b/ios/Runner/SceneDelegate.swift @@ -0,0 +1,26 @@ +import Flutter +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene, + let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } + + let controller = FlutterViewController(project: nil, nibName: nil, bundle: nil) + + let window = UIWindow(windowScene: windowScene) + window.rootViewController = controller + window.makeKeyAndVisible() + self.window = window + + // Sync with FlutterAppDelegate so plugins can locate the engine. + appDelegate.window = window + GeneratedPluginRegistrant.register(with: appDelegate) + } +} diff --git a/lib/app.dart b/lib/app.dart index 357b6a5..1421248 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,6 +1,7 @@ import 'package:betcode_app/core/grpc/relay_reconnect_provider.dart'; import 'package:betcode_app/core/router.dart'; import 'package:betcode_app/shared/theme/theme.dart'; +import 'package:betcode_app/shared/widgets/connectivity_banner.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -18,6 +19,12 @@ class BetCodeApp extends ConsumerWidget { theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, routerConfig: router, + builder: (context, child) => Column( + children: [ + const ConnectivityBanner(), + Expanded(child: child ?? const SizedBox.shrink()), + ], + ), ); } } diff --git a/lib/core/auth/auth_notifier.dart b/lib/core/auth/auth_notifier.dart index 5f6e277..571e07d 100644 --- a/lib/core/auth/auth_notifier.dart +++ b/lib/core/auth/auth_notifier.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:betcode_app/core/auth/auth_state.dart'; +import 'package:betcode_app/core/grpc/app_exceptions.dart'; import 'package:betcode_app/core/storage/storage.dart'; import 'package:betcode_app/generated/betcode/v1/auth.pbgrpc.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -112,6 +113,11 @@ class AuthNotifier extends Notifier { /// failure (invalid or revoked token). Network and transient errors /// return false — we keep the session alive so reconnection can retry. static bool _isAuthError(Object error) { + if (error is AuthExpiredError || error is PermissionDeniedError) { + return true; + } + // Fallback: _isAuthError may be called from code paths where the + // ErrorMappingInterceptor hasn't run (e.g. health check, token refresh). if (error is GrpcError) { return error.code == StatusCode.unauthenticated || error.code == StatusCode.permissionDenied; diff --git a/lib/core/grpc/app_exceptions.dart b/lib/core/grpc/app_exceptions.dart new file mode 100644 index 0000000..9cb20f9 --- /dev/null +++ b/lib/core/grpc/app_exceptions.dart @@ -0,0 +1,72 @@ +/// Structured exceptions for gRPC and application-level errors. +/// +/// [AppException] is a sealed class so that `switch` on its subtypes is +/// exhaustive, enabling the compiler to verify every error case is handled. +sealed class AppException implements Exception { + /// Creates an [AppException] with the given [message] and optional [cause]. + const AppException({required this.message, this.cause}); + + /// Human-readable description of what went wrong. + final String message; + + /// The underlying error that triggered this exception, if any. + final Object? cause; + + @override + String toString() => message; +} + +/// A network-level failure (DNS, TCP, TLS, timeout). +final class NetworkError extends AppException { + /// Creates a [NetworkError] with the given [message] and optional [cause]. + const NetworkError({required super.message, super.cause}); +} + +/// The relay server could not be reached. +final class RelayUnavailableError extends AppException { + /// Creates a [RelayUnavailableError]. + const RelayUnavailableError({required super.message, super.cause}); +} + +/// The requested session does not exist on the daemon. +final class SessionNotFoundError extends AppException { + /// Creates a [SessionNotFoundError] for the given [sessionId]. + const SessionNotFoundError({ + required super.message, + this.sessionId, + super.cause, + }); + + /// The ID of the session that was not found, if available. + final String? sessionId; +} + +/// The session exists but is in an invalid state (e.g. corrupted, expired). +final class SessionInvalidError extends AppException { + /// Creates a [SessionInvalidError]. + const SessionInvalidError({required super.message, super.cause}); +} + +/// The JWT has expired and the client must re-authenticate. +final class AuthExpiredError extends AppException { + /// Creates an [AuthExpiredError]. + const AuthExpiredError({required super.message, super.cause}); +} + +/// The authenticated user lacks permission for the requested action. +final class PermissionDeniedError extends AppException { + /// Creates a [PermissionDeniedError]. + const PermissionDeniedError({required super.message, super.cause}); +} + +/// An unexpected error on the daemon side (gRPC INTERNAL / UNKNOWN). +final class ServerError extends AppException { + /// Creates a [ServerError]. + const ServerError({required super.message, super.cause}); +} + +/// The client has been rate-limited by the daemon or relay. +final class RateLimitError extends AppException { + /// Creates a [RateLimitError]. + const RateLimitError({required super.message, super.cause}); +} diff --git a/lib/core/grpc/client_manager.dart b/lib/core/grpc/client_manager.dart index 1b7a025..4db347d 100644 --- a/lib/core/grpc/client_manager.dart +++ b/lib/core/grpc/client_manager.dart @@ -86,6 +86,19 @@ class GrpcClientManager { /// Whether a health check function has been configured. bool get hasHealthCheck => _healthCheckFn != null; + /// Runs the configured health check against the current channel. + /// + /// Throws [StateError] if no health check is configured or no channel + /// is available. Re-throws whatever the health check function throws + /// on failure (typically [GrpcError]). + Future healthCheck() async { + final fn = _healthCheckFn; + if (fn == null) { + throw StateError('No health check function configured'); + } + await fn(channel); + } + /// The host from the last [connect] call, or null if never connected. String? get host => _host; @@ -162,6 +175,10 @@ class GrpcClientManager { ), ); + // Health check is best-effort during initial connect: we don't block + // the connection on it because the relay may be up while the daemon + // behind it is still starting. Callers can use healthCheck() explicitly + // for stricter verification (e.g. GrpcLifecycleBridge on app resume). if (_healthCheckFn != null) { try { await _healthCheckFn(_channel!); diff --git a/lib/core/grpc/error_mapping.dart b/lib/core/grpc/error_mapping.dart new file mode 100644 index 0000000..266bad0 --- /dev/null +++ b/lib/core/grpc/error_mapping.dart @@ -0,0 +1,92 @@ +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:grpc/grpc.dart'; + +/// RPC method substrings that indicate session-related operations. +const _sessionMethods = [ + 'Session', // DeleteSession, RenameSession, CompactSession, ResumeSession + 'Converse', // The bidi conversation stream +]; + +/// Maps a [GrpcError] to the appropriate [AppException] subclass. +/// +/// The optional [method] parameter is the gRPC method path +/// (e.g. `/betcode.v1.AgentService/DeleteSession`) and is used to +/// distinguish session-specific NOT_FOUND from generic NOT_FOUND. +AppException mapGrpcError(GrpcError error, {String? method}) { + return switch (error.code) { + StatusCode.cancelled => NetworkError( + message: 'Connection lost. Retrying...', + cause: error, + ), + StatusCode.unavailable => _mapUnavailable(error), + StatusCode.deadlineExceeded => NetworkError( + message: 'Request timed out. Check your connection and try again.', + cause: error, + ), + StatusCode.notFound => + _isSessionMethod(method) + ? SessionNotFoundError( + message: 'Session no longer exists.', + cause: error, + ) + : ServerError( + message: 'The requested resource was not found.', + cause: error, + ), + StatusCode.unauthenticated => AuthExpiredError( + message: 'Your session has expired. Please log in again.', + cause: error, + ), + StatusCode.permissionDenied => PermissionDeniedError( + message: "You don't have permission for this action.", + cause: error, + ), + StatusCode.resourceExhausted => RateLimitError( + message: 'Too many requests. Please wait a moment and try again.', + cause: error, + ), + StatusCode.invalidArgument || + StatusCode.failedPrecondition => SessionInvalidError( + message: error.message ?? 'Invalid request.', + cause: error, + ), + _ => ServerError( + message: 'Something went wrong. Please try again.', + cause: error, + ), + }; +} + +AppException _mapUnavailable(GrpcError error) { + final msg = error.message ?? ''; + + // "Channel shutting down" is a transient local error that occurs when the + // ClientChannel is replaced during reconnection. It is NOT a TLS or relay + // issue — the new channel may work fine. Treat it as a retryable network + // error so callers don't display a scary "relay unreachable" banner. + if (msg.contains('Channel shutting down')) { + return NetworkError( + message: 'Connection lost. Retrying...', + cause: error, + ); + } + + if (msg.contains('HandshakeException') || + msg.contains('WRONG_VERSION_NUMBER') || + msg.contains('TLS') || + msg.contains('CERTIFICATE')) { + return RelayUnavailableError( + message: 'Unable to reach the relay server.', + cause: error, + ); + } + return NetworkError( + message: 'Connection lost. Retrying...', + cause: error, + ); +} + +bool _isSessionMethod(String? method) { + if (method == null) return false; + return _sessionMethods.any(method.contains); +} diff --git a/lib/core/grpc/grpc.dart b/lib/core/grpc/grpc.dart index 257fb93..12a15df 100644 --- a/lib/core/grpc/grpc.dart +++ b/lib/core/grpc/grpc.dart @@ -1,5 +1,7 @@ +export 'app_exceptions.dart'; export 'client_manager.dart'; export 'connection_state.dart'; +export 'error_mapping.dart'; export 'grpc_providers.dart'; export 'interceptors.dart'; export 'relay_config.dart'; diff --git a/lib/core/grpc/grpc_providers.dart b/lib/core/grpc/grpc_providers.dart index fa9dcb4..c104fdd 100644 --- a/lib/core/grpc/grpc_providers.dart +++ b/lib/core/grpc/grpc_providers.dart @@ -34,13 +34,22 @@ final grpcClientManagerProvider = Provider((ref) { machineIdProvider: () async => ref.read(selectedMachineIdProvider), ), LoggingInterceptor(), + ErrorMappingInterceptor(), ], healthCheckFn: (channel) async { final client = HealthClient(channel); - await client.check( - HealthCheckRequest(), - options: CallOptions(timeout: const Duration(seconds: 5)), - ); + try { + await client.check( + HealthCheckRequest(), + options: CallOptions(timeout: const Duration(seconds: 5)), + ); + } on GrpcError catch (e) { + if (e.code == StatusCode.unimplemented) { + // Server responded — connection is alive, just no Health service. + return; + } + rethrow; + } }, ); diff --git a/lib/core/grpc/interceptors.dart b/lib/core/grpc/interceptors.dart index 1b8b9ca..a46d213 100644 --- a/lib/core/grpc/interceptors.dart +++ b/lib/core/grpc/interceptors.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'package:betcode_app/core/auth/auth_notifier.dart'; +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:betcode_app/core/grpc/error_mapping.dart'; import 'package:betcode_app/generated/betcode/v1/auth.pbgrpc.dart'; import 'package:flutter/foundation.dart'; import 'package:grpc/grpc.dart'; @@ -150,9 +152,76 @@ class LoggingInterceptor extends ClientInterceptor { CallOptions options, ClientStreamingInvoker invoker, ) { + final stopwatch = Stopwatch()..start(); debugPrint('$_tag -> ${method.path} (stream)'); - return invoker(method, requests, options); + return _LoggingResponseStream( + invoker(method, requests, options), + method.path, + stopwatch, + ); + } +} + +/// Wraps a [ResponseStream] to log errors and completion. +class _LoggingResponseStream extends StreamView + implements ResponseStream { + _LoggingResponseStream(this._delegate, this._method, this._stopwatch) + : super(_delegate); + + final ResponseStream _delegate; + final String _method; + final Stopwatch _stopwatch; + + @override + StreamSubscription listen( + void Function(R)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + return super.listen( + onData, + onError: (Object error, StackTrace stackTrace) { + _stopwatch.stop(); + debugPrint( + '[gRPC] <- $_method stream ERROR ' + '(${_stopwatch.elapsedMilliseconds}ms): $error', + ); + if (onError != null) { + if (onError is void Function(Object, StackTrace)) { + onError(error, stackTrace); + } else if (onError is void Function(Object)) { + onError(error); + } else { + (onError as dynamic)(error, stackTrace); + } + } else { + Error.throwWithStackTrace(error, stackTrace); + } + }, + onDone: () { + _stopwatch.stop(); + debugPrint( + '[gRPC] <- $_method stream DONE ' + '(${_stopwatch.elapsedMilliseconds}ms)', + ); + onDone?.call(); + }, + cancelOnError: cancelOnError, + ); } + + @override + ResponseFuture get single => _delegate.single; + + @override + Future> get headers => _delegate.headers; + + @override + Future> get trailers => _delegate.trailers; + + @override + Future cancel() => _delegate.cancel(); } /// Checks token expiry before each RPC and triggers a refresh if needed. @@ -233,3 +302,151 @@ class TokenRefreshInterceptor extends ClientInterceptor { return invoker(method, requests, opts); } } + +/// Maps [GrpcError] responses to typed [AppException] subclasses. +/// +/// Must be the **last** interceptor in the chain so it wraps errors from +/// all preceding interceptors (auth refresh, logging, etc.). +class ErrorMappingInterceptor extends ClientInterceptor { + @override + ResponseFuture interceptUnary( + ClientMethod method, + Q request, + CallOptions options, + ClientUnaryInvoker invoker, + ) { + final response = invoker(method, request, options); + return _ErrorMappingResponseFuture(response, method.path); + } + + @override + ResponseStream interceptStreaming( + ClientMethod method, + Stream requests, + CallOptions options, + ClientStreamingInvoker invoker, + ) { + final response = invoker(method, requests, options); + return _ErrorMappingResponseStream(response, method.path); + } +} + +/// Wraps a [ResponseFuture] and maps [GrpcError]s to [AppException]s. +/// +/// Delegates all [Future] and [Response] methods to the underlying +/// [_delegate], intercepting errors in [then] and [catchError] so that +/// any [GrpcError] is replaced with the typed exception from +/// [mapGrpcError]. +class _ErrorMappingResponseFuture implements ResponseFuture { + _ErrorMappingResponseFuture(this._delegate, this._method); + + final ResponseFuture _delegate; + final String _method; + + /// Transforms a [GrpcError] into an [AppException]. Non-[GrpcError] + /// exceptions pass through unchanged. + Object _mapError(Object error) { + if (error is GrpcError) return mapGrpcError(error, method: _method); + return error; + } + + /// The mapped future: maps errors once, then reuses the result. + late final Future _mapped = _delegate.then( + (v) => v, + onError: (Object error, StackTrace stack) => + Error.throwWithStackTrace(_mapError(error), stack), + ); + + @override + Future then( + FutureOr Function(R) onValue, { + Function? onError, + }) => _mapped.then(onValue, onError: onError); + + @override + Future catchError(Function onError, {bool Function(Object)? test}) => + _mapped.catchError(onError, test: test); + + @override + Future whenComplete(FutureOr Function() action) => + _mapped.whenComplete(action); + + @override + Future timeout( + Duration timeLimit, { + FutureOr Function()? onTimeout, + }) => _mapped.timeout(timeLimit, onTimeout: onTimeout); + + @override + Stream asStream() => _mapped.asStream(); + + @override + Future> get headers => _delegate.headers; + + @override + Future> get trailers => _delegate.trailers; + + @override + Future cancel() => _delegate.cancel(); +} + +/// Wraps a [ResponseStream] and maps [GrpcError]s to [AppException]s. +/// +/// Overrides [listen] to intercept errors from the underlying stream, +/// mapping [GrpcError]s to typed [AppException]s. All other [Stream] +/// methods (e.g. [map], [where], [toList]) ultimately go through +/// [listen], so they also benefit from the mapping. +class _ErrorMappingResponseStream extends StreamView + implements ResponseStream { + _ErrorMappingResponseStream(this._delegate, this._method) : super(_delegate); + + final ResponseStream _delegate; + final String _method; + + @override + StreamSubscription listen( + void Function(R)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + return super.listen( + onData, + onError: (Object error, StackTrace stackTrace) { + final mapped = error is GrpcError + ? mapGrpcError(error, method: _method) + : error; + if (onError != null) { + if (onError is void Function(Object, StackTrace)) { + onError(mapped, stackTrace); + } else if (onError is void Function(Object)) { + onError(mapped); + } else { + // Best-effort: call with both arguments. + (onError as dynamic)(mapped, stackTrace); + } + } else { + // No onError callback — rethrow via the zone's error handler + // so the subscription can propagate it. + Error.throwWithStackTrace(mapped, stackTrace); + } + }, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } + + /// [ResponseStream] narrows the return type of [single] from [Future] to + /// [ResponseFuture]. We delegate to the original stream's [single]. + @override + ResponseFuture get single => _delegate.single; + + @override + Future> get headers => _delegate.headers; + + @override + Future> get trailers => _delegate.trailers; + + @override + Future cancel() => _delegate.cancel(); +} diff --git a/lib/core/grpc/lifecycle_bridge.dart b/lib/core/grpc/lifecycle_bridge.dart index fbeb794..ee045bb 100644 --- a/lib/core/grpc/lifecycle_bridge.dart +++ b/lib/core/grpc/lifecycle_bridge.dart @@ -40,8 +40,9 @@ class GrpcLifecycleBridge { /// Called when the app returns to foreground (resumed). /// /// Cancels the teardown timer if it hasn't fired yet. If the channel was - /// torn down during extended background, reconnects using the stored - /// connection parameters. + /// torn down during extended background, reconnects immediately. Otherwise, + /// runs a health check to detect zombie channels (TCP connection killed by + /// the OS during sleep) and reconnects if the channel is dead. void onResumed() { _teardownTimer?.cancel(); _teardownTimer = null; @@ -50,6 +51,24 @@ class GrpcLifecycleBridge { if (_tornDown) { _tornDown = false; _reconnect(); + } else if (_manager.channelOrNull != null && _manager.hasHealthCheck) { + // Channel exists but may be a zombie — OS often kills idle TCP sockets + // while the app is backgrounded. Fire a health check to find out. + unawaited(_verifyOrReconnect()); + } + } + + /// Runs a health check against the existing channel. If it fails, tears + /// down the dead channel and reconnects. + Future _verifyOrReconnect() async { + try { + await _manager.healthCheck(); + } on Object catch (e) { + debugPrint( + '[GrpcLifecycleBridge] Health check failed after resume, ' + 'reconnecting: $e', + ); + _reconnect(); } } diff --git a/lib/core/grpc/service_providers.dart b/lib/core/grpc/service_providers.dart index bdf9b79..6e96ea8 100644 --- a/lib/core/grpc/service_providers.dart +++ b/lib/core/grpc/service_providers.dart @@ -13,8 +13,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; /// Provides the [AgentServiceClient] for conversation streaming, session /// management, and input lock operations. +/// +/// Watches `connectionStatusProvider` so the client is recreated with the +/// current `ClientChannel` whenever `GrpcClientManager.connect` replaces +/// the channel (e.g. during reconnection). Without this, stale clients +/// would hold a reference to the old (shut-down) channel and every RPC +/// would fail with "Channel shutting down". final agentServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return AgentServiceClient( manager.channel, interceptors: manager.interceptors, @@ -26,12 +33,14 @@ final agentServiceProvider = Provider((ref) { /// auth calls themselves are unauthenticated. final authServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return AuthServiceClient(manager.channel); }); /// Provides the [MachineServiceClient] for listing and switching machines. final machineServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return MachineServiceClient( manager.channel, interceptors: manager.interceptors, @@ -41,6 +50,7 @@ final machineServiceProvider = Provider((ref) { /// Provides the [WorktreeServiceClient] for worktree CRUD per machine. final worktreeServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return WorktreeServiceClient( manager.channel, interceptors: manager.interceptors, @@ -51,6 +61,7 @@ final worktreeServiceProvider = Provider((ref) { /// configuration management. final gitRepoServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return GitRepoServiceClient( manager.channel, interceptors: manager.interceptors, @@ -61,6 +72,7 @@ final gitRepoServiceProvider = Provider((ref) { /// server listing. final configServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return ConfigServiceClient( manager.channel, interceptors: manager.interceptors, @@ -70,6 +82,7 @@ final configServiceProvider = Provider((ref) { /// Provides the [GitLabServiceClient] for MR, pipeline, and issue views. final gitlabServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return GitLabServiceClient( manager.channel, interceptors: manager.interceptors, @@ -79,12 +92,14 @@ final gitlabServiceProvider = Provider((ref) { /// Provides the [HealthClient] for daemon health checks. final healthServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return HealthClient(manager.channel, interceptors: manager.interceptors); }); /// Provides the [BetCodeHealthClient] for relay health checks. final betcodeHealthServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return BetCodeHealthClient( manager.channel, interceptors: manager.interceptors, @@ -95,6 +110,7 @@ final betcodeHealthServiceProvider = Provider((ref) { /// discovery. final versionServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return VersionServiceClient( manager.channel, interceptors: manager.interceptors, @@ -105,6 +121,7 @@ final versionServiceProvider = Provider((ref) { /// service command execution, and plugin management. final commandServiceProvider = Provider((ref) { final manager = ref.watch(grpcClientManagerProvider); + ref.watch(connectionStatusProvider); return CommandServiceClient( manager.channel, interceptors: manager.interceptors, diff --git a/lib/features/commands/notifiers/commands_providers.dart b/lib/features/commands/notifiers/commands_providers.dart index d4c3b2d..de3e84c 100644 --- a/lib/features/commands/notifiers/commands_providers.dart +++ b/lib/features/commands/notifiers/commands_providers.dart @@ -9,11 +9,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; /// global commands. Use `ref.watch(commandsProvider(sessionId))` in /// widgets to reactively rebuild on loading / data / error transitions. // ignore: specify_nonobvious_property_types, the family provider type is not publicly exported -final commandsProvider = AsyncNotifierProvider.family< - CommandsNotifier, - List, - String? ->((sessionId) { - final notifier = CommandsNotifier()..sessionId = sessionId; - return notifier; -}); +final commandsProvider = + AsyncNotifierProvider.family, String?>( + (sessionId) { + final notifier = CommandsNotifier()..sessionId = sessionId; + return notifier; + }, + ); diff --git a/lib/features/conversation/notifiers/conversation_event_handler.dart b/lib/features/conversation/notifiers/conversation_event_handler.dart index 4b82118..5eeaaae 100644 --- a/lib/features/conversation/notifiers/conversation_event_handler.dart +++ b/lib/features/conversation/notifiers/conversation_event_handler.dart @@ -1,12 +1,8 @@ import 'dart:convert'; -import 'package:betcode_app/features/conversation/conversation.dart' - show ConversationNotifier; import 'package:betcode_app/features/conversation/models/conversation_state.dart'; import 'package:betcode_app/features/conversation/notifiers/conversation_notifier.dart' show ConversationNotifier; -import 'package:betcode_app/features/conversation/notifiers/notifiers.dart' - show ConversationNotifier; import 'package:betcode_app/generated/betcode/v1/agent.pb.dart' as pb; import 'package:betcode_app/generated/betcode/v1/common.pb.dart'; import 'package:flutter/foundation.dart'; @@ -18,6 +14,12 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; /// Separated from [ConversationNotifier] to keep file sizes manageable /// and isolate event-processing logic from stream lifecycle management. mixin ConversationEventHandler on AsyncNotifier { + /// True while replaying historical events via history load. + /// + /// Fatal errors from history (e.g. a previous session crash) must NOT + /// kill the current conversation state — they happened in the past. + bool isReplayingHistory = false; + /// Dispatches a single [pb.AgentEvent] to the appropriate handler. void handleEvent(pb.AgentEvent event) { debugPrint( @@ -437,6 +439,16 @@ mixin ConversationEventHandler on AsyncNotifier { } void _onError(pb.ErrorEvent error, int seq) { + // During history replay, fatal errors are from a past session run. + // Just update the sequence counter — don't show a banner or kill + // the conversation. The user already knows the previous run failed. + if (isReplayingHistory) { + _updateActive((active) { + return active.copyWith(lastSequence: seq); + }); + return; + } + if (error.isFatal) { state = AsyncData( ConversationState.error('[${error.code}] ${error.message}'), diff --git a/lib/features/conversation/notifiers/conversation_notifier.dart b/lib/features/conversation/notifiers/conversation_notifier.dart index 35ce7a0..31813ec 100644 --- a/lib/features/conversation/notifiers/conversation_notifier.dart +++ b/lib/features/conversation/notifiers/conversation_notifier.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math' show min; +import 'package:betcode_app/core/grpc/app_exceptions.dart'; import 'package:betcode_app/core/grpc/service_providers.dart'; import 'package:betcode_app/core/lifecycle/lifecycle.dart'; import 'package:betcode_app/features/conversation/models/conversation_state.dart'; @@ -29,6 +30,9 @@ class ConversationNotifier extends AsyncNotifier Duration(seconds: 30), ]; + /// Default model used for new sessions when no model is explicitly chosen. + static const _defaultModel = 'claude-sonnet-4'; + StreamController? _requestController; StreamSubscription? _eventSubscription; int _reconnectAttempt = 0; @@ -94,6 +98,12 @@ class ConversationNotifier extends AsyncNotifier state = const AsyncData(ConversationState.connecting()); try { + // Close any existing streams from a previous attempt (e.g. retry + // after error) to prevent leaked subscriptions. + unawaited(_eventSubscription?.cancel()); + _eventSubscription = null; + unawaited(_requestController?.close()); + _requestController = StreamController(); final responseStream = _client.converse(_requestController!.stream); @@ -112,6 +122,7 @@ class ConversationNotifier extends AsyncNotifier start: pb.StartConversation( sessionId: sessionId ?? '', workingDirectory: workingDirectory, + model: _defaultModel, ), ), ); @@ -131,11 +142,21 @@ class ConversationNotifier extends AsyncNotifier // When resuming an existing session, load conversation history from // the daemon's event store via the ResumeSession RPC. Events are // processed through the same handler and deduplicated by sequence. + // History replay uses a flag so _onError treats past fatal errors + // as non-fatal banners instead of killing the conversation state. if (sessionId != null && sessionId!.isNotEmpty) { - await _loadHistory(sessionId!); + isReplayingHistory = true; + try { + await _loadHistory(sessionId!); + } finally { + isReplayingHistory = false; + } } } on Exception catch (e) { - state = AsyncData(ConversationState.error(e.toString())); + final message = e is AppException + ? e.message + : 'Failed to start conversation: $e'; + state = AsyncData(ConversationState.error(message)); } } @@ -164,9 +185,22 @@ class ConversationNotifier extends AsyncNotifier final current = state.value; final seq = current is ConversationActive ? current.lastSequence : 0; debugPrint('[Conversation] History loaded, lastSequence=$seq'); + } on AppException catch (e) { + debugPrint('[Conversation] History load failed: $e'); + final current = state.value; + if (current is ConversationActive) { + state = AsyncData( + current.copyWith(errorMessage: "Couldn't load message history."), + ); + } } on GrpcError catch (e) { - // Non-fatal: continue with empty history if replay fails. debugPrint('[Conversation] History load failed: $e'); + final current = state.value; + if (current is ConversationActive) { + state = AsyncData( + current.copyWith(errorMessage: "Couldn't load message history."), + ); + } } } @@ -284,16 +318,20 @@ class ConversationNotifier extends AsyncNotifier // --------------------------------------------------------------------------- void _handleStreamError(Object error) { - debugPrint('[Conversation] Stream error: $error'); + final causeInfo = error is AppException && error.cause != null + ? error.cause + : ''; + debugPrint('[Conversation] Stream error: $error | cause: $causeInfo'); unawaited(_eventSubscription?.cancel()); _eventSubscription = null; // Don't retry fatal errors. if (_isFatalError(error)) { _isReconnecting = false; - state = AsyncData( - ConversationState.error('Stream error: $error'), - ); + final message = error is AppException + ? error.message + : 'Stream error: $error'; + state = AsyncData(ConversationState.error(message)); unawaited(_requestController?.close()); _requestController = null; return; @@ -311,21 +349,19 @@ class ConversationNotifier extends AsyncNotifier _attemptReconnection(current); } else { _isReconnecting = false; - state = AsyncData( - ConversationState.error('Stream error: $error'), - ); + final message = error is AppException + ? error.message + : 'Stream error: $error'; + state = AsyncData(ConversationState.error(message)); unawaited(_requestController?.close()); _requestController = null; } } bool _isFatalError(Object error) { - if (error is GrpcError) { - return error.code == StatusCode.unauthenticated || - error.code == StatusCode.permissionDenied || - error.code == StatusCode.notFound; - } - return false; + return error is AuthExpiredError || + error is PermissionDeniedError || + error is SessionNotFoundError; } void _attemptReconnection(ConversationActive active) { @@ -373,8 +409,7 @@ class ConversationNotifier extends AsyncNotifier // used resumeSession (server-streaming, read-only) which left // _requestController null — silently dropping all user messages. _requestController = StreamController(); - final responseStream = - _client.converse(_requestController!.stream); + final responseStream = _client.converse(_requestController!.stream); _eventSubscription = responseStream.listen( _onReconnectEvent, diff --git a/lib/features/conversation/widgets/command_palette.dart b/lib/features/conversation/widgets/command_palette.dart index 9c22fbe..0cf3d12 100644 --- a/lib/features/conversation/widgets/command_palette.dart +++ b/lib/features/conversation/widgets/command_palette.dart @@ -27,13 +27,14 @@ class CommandPalette extends ConsumerWidget { final lower = query.toLowerCase(); // Local static commands filtered by query. - final localCommands = InputCommand.filter(query) - .map(CommandItem.fromLocal) - .toList(); + final localCommands = InputCommand.filter( + query, + ).map(CommandItem.fromLocal).toList(); // Dynamic daemon commands filtered by query. final daemonCommandsAsync = ref.watch(commandsProvider(sessionId)); - final daemonCommands = daemonCommandsAsync.value + final daemonCommands = + daemonCommandsAsync.value ?.where( (e) => e.name.toLowerCase().contains(lower) || diff --git a/lib/features/sessions/notifiers/sessions_notifier.dart b/lib/features/sessions/notifiers/sessions_notifier.dart index 2348632..97b4a7f 100644 --- a/lib/features/sessions/notifiers/sessions_notifier.dart +++ b/lib/features/sessions/notifiers/sessions_notifier.dart @@ -76,6 +76,26 @@ class SessionsNotifier extends AsyncNotifier> { await refresh(); } + /// Permanently deletes a session and all its messages. + /// + /// Throws on gRPC/timeout errors so callers can display feedback. + Future deleteSession(String sessionId) async { + final client = ref.read(agentServiceProvider); + await client + .deleteSession(DeleteSessionRequest(sessionId: sessionId)) + .timeout(_mutationTimeout); + await _removeFromCache(sessionId); + await refresh(); + } + + /// Removes a deleted session from the local drift cache. + Future _removeFromCache(String sessionId) async { + final db = ref.read(appDatabaseProvider); + await db.batch((batch) { + batch.deleteWhere(db.cachedSessions, (t) => t.id.equals(sessionId)); + }); + } + /// Upserts each [SessionSummary] into the local [CachedSessions] table so /// the data is available when offline. Future _cacheToDb(Iterable sessions) async { diff --git a/lib/features/sessions/screens/sessions_screen.dart b/lib/features/sessions/screens/sessions_screen.dart index 021a0eb..50cfd00 100644 --- a/lib/features/sessions/screens/sessions_screen.dart +++ b/lib/features/sessions/screens/sessions_screen.dart @@ -1,4 +1,8 @@ +import 'dart:async'; + +import 'package:betcode_app/core/grpc/app_exceptions.dart'; import 'package:betcode_app/features/sessions/notifiers/sessions_providers.dart'; +import 'package:betcode_app/features/sessions/widgets/confirm_delete_dialog.dart'; import 'package:betcode_app/features/sessions/widgets/rename_session_dialog.dart'; import 'package:betcode_app/features/sessions/widgets/session_card.dart'; import 'package:betcode_app/generated/betcode/v1/agent.pb.dart'; @@ -31,7 +35,7 @@ class SessionsScreen extends ConsumerWidget { onTap: () => context.go('/sessions/${session.id}'), onRename: (currentName) => _onRename(context, ref, session.id, currentName), - onDelete: () => _onDelete(context), + onDelete: () => _onDelete(context, ref, session.id), ), ), ); @@ -52,17 +56,41 @@ class SessionsScreen extends ConsumerWidget { await ref .read(sessionsProvider.notifier) .renameSession(sessionId: sessionId, name: newName); + } on AppException catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.message)), + ); } on Exception catch (e) { if (!context.mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('Rename failed: $e'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Rename failed: $e')), + ); } } - void _onDelete(BuildContext context) { - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Delete coming soon'))); + Future _onDelete( + BuildContext context, + WidgetRef ref, + String sessionId, + ) async { + final confirmed = await ConfirmDeleteDialog.show(context); + if (confirmed != true) return; + try { + await ref.read(sessionsProvider.notifier).deleteSession(sessionId); + } on SessionNotFoundError catch (_) { + // Session already gone — just refresh the list. + unawaited(ref.read(sessionsProvider.notifier).refresh()); + } on AppException catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.message)), + ); + } on Exception catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Delete failed: $e')), + ); + } } } diff --git a/lib/features/sessions/widgets/confirm_delete_dialog.dart b/lib/features/sessions/widgets/confirm_delete_dialog.dart new file mode 100644 index 0000000..11196ea --- /dev/null +++ b/lib/features/sessions/widgets/confirm_delete_dialog.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +/// A confirmation dialog shown before permanently deleting a session. +/// +/// Returns `true` on confirm, `null` on cancel. +class ConfirmDeleteDialog extends StatelessWidget { + const ConfirmDeleteDialog({super.key}); + + /// Convenience method to show the dialog and return the result. + static Future show(BuildContext context) { + return showDialog( + context: context, + builder: (_) => const ConfirmDeleteDialog(), + ); + } + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return AlertDialog( + title: const Text('Delete Session'), + content: const Text( + 'This will permanently delete the session and all its messages. ' + 'This action cannot be undone.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () => Navigator.of(context).pop(true), + style: FilledButton.styleFrom( + backgroundColor: colorScheme.error, + foregroundColor: colorScheme.onError, + ), + child: const Text('Delete'), + ), + ], + ); + } +} diff --git a/lib/features/sessions/widgets/widgets.dart b/lib/features/sessions/widgets/widgets.dart new file mode 100644 index 0000000..10c0244 --- /dev/null +++ b/lib/features/sessions/widgets/widgets.dart @@ -0,0 +1,3 @@ +export 'confirm_delete_dialog.dart'; +export 'rename_session_dialog.dart'; +export 'session_card.dart'; diff --git a/lib/generated/betcode/v1/agent.pb.dart b/lib/generated/betcode/v1/agent.pb.dart index 897f8cd..b8640a3 100644 --- a/lib/generated/betcode/v1/agent.pb.dart +++ b/lib/generated/betcode/v1/agent.pb.dart @@ -3415,6 +3415,115 @@ class RenameSessionResponse extends $pb.GeneratedMessage { static RenameSessionResponse? _defaultInstance; } +class DeleteSessionRequest extends $pb.GeneratedMessage { + factory DeleteSessionRequest({ + $core.String? sessionId, + }) { + final result = create(); + if (sessionId != null) result.sessionId = sessionId; + return result; + } + + DeleteSessionRequest._(); + + factory DeleteSessionRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory DeleteSessionRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'DeleteSessionRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'sessionId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DeleteSessionRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DeleteSessionRequest copyWith(void Function(DeleteSessionRequest) updates) => + super.copyWith((message) => updates(message as DeleteSessionRequest)) + as DeleteSessionRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static DeleteSessionRequest create() => DeleteSessionRequest._(); + @$core.override + DeleteSessionRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static DeleteSessionRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static DeleteSessionRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get sessionId => $_getSZ(0); + @$pb.TagNumber(1) + set sessionId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSessionId() => $_has(0); + @$pb.TagNumber(1) + void clearSessionId() => $_clearField(1); +} + +class DeleteSessionResponse extends $pb.GeneratedMessage { + factory DeleteSessionResponse({ + $core.bool? deleted, + }) { + final result = create(); + if (deleted != null) result.deleted = deleted; + return result; + } + + DeleteSessionResponse._(); + + factory DeleteSessionResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory DeleteSessionResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'DeleteSessionResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'deleted') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DeleteSessionResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DeleteSessionResponse copyWith( + void Function(DeleteSessionResponse) updates) => + super.copyWith((message) => updates(message as DeleteSessionResponse)) + as DeleteSessionResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static DeleteSessionResponse create() => DeleteSessionResponse._(); + @$core.override + DeleteSessionResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static DeleteSessionResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static DeleteSessionResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get deleted => $_getBF(0); + @$pb.TagNumber(1) + set deleted($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasDeleted() => $_has(0); + @$pb.TagNumber(1) + void clearDeleted() => $_clearField(1); +} + const $core.bool _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const $core.bool _omitMessageNames = diff --git a/lib/generated/betcode/v1/agent.pbgrpc.dart b/lib/generated/betcode/v1/agent.pbgrpc.dart index d8fcb4b..92ec3f1 100644 --- a/lib/generated/betcode/v1/agent.pbgrpc.dart +++ b/lib/generated/betcode/v1/agent.pbgrpc.dart @@ -124,6 +124,14 @@ class AgentServiceClient extends $grpc.Client { return $createUnaryCall(_$renameSession, request, options: options); } + /// Delete a session and all its messages. + $grpc.ResponseFuture<$0.DeleteSessionResponse> deleteSession( + $0.DeleteSessionRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$deleteSession, request, options: options); + } + // method descriptors static final _$converse = $grpc.ClientMethod<$0.AgentRequest, $0.AgentEvent>( @@ -180,6 +188,11 @@ class AgentServiceClient extends $grpc.Client { '/betcode.v1.AgentService/RenameSession', ($0.RenameSessionRequest value) => value.writeToBuffer(), $0.RenameSessionResponse.fromBuffer); + static final _$deleteSession = + $grpc.ClientMethod<$0.DeleteSessionRequest, $0.DeleteSessionResponse>( + '/betcode.v1.AgentService/DeleteSession', + ($0.DeleteSessionRequest value) => value.writeToBuffer(), + $0.DeleteSessionResponse.fromBuffer); } @$pb.GrpcServiceName('betcode.v1.AgentService') @@ -279,6 +292,15 @@ abstract class AgentServiceBase extends $grpc.Service { ($core.List<$core.int> value) => $0.RenameSessionRequest.fromBuffer(value), ($0.RenameSessionResponse value) => value.writeToBuffer())); + $addMethod( + $grpc.ServiceMethod<$0.DeleteSessionRequest, $0.DeleteSessionResponse>( + 'DeleteSession', + deleteSession_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.DeleteSessionRequest.fromBuffer(value), + ($0.DeleteSessionResponse value) => value.writeToBuffer())); } $async.Stream<$0.AgentEvent> converse( @@ -371,4 +393,13 @@ abstract class AgentServiceBase extends $grpc.Service { $async.Future<$0.RenameSessionResponse> renameSession( $grpc.ServiceCall call, $0.RenameSessionRequest request); + + $async.Future<$0.DeleteSessionResponse> deleteSession_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.DeleteSessionRequest> $request) async { + return deleteSession($call, await $request); + } + + $async.Future<$0.DeleteSessionResponse> deleteSession( + $grpc.ServiceCall call, $0.DeleteSessionRequest request); } diff --git a/lib/generated/betcode/v1/agent.pbjson.dart b/lib/generated/betcode/v1/agent.pbjson.dart index 55d27a0..d79d49b 100644 --- a/lib/generated/betcode/v1/agent.pbjson.dart +++ b/lib/generated/betcode/v1/agent.pbjson.dart @@ -1042,3 +1042,28 @@ const RenameSessionResponse$json = { /// Descriptor for `RenameSessionResponse`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List renameSessionResponseDescriptor = $convert.base64Decode('ChVSZW5hbWVTZXNzaW9uUmVzcG9uc2U='); + +@$core.Deprecated('Use deleteSessionRequestDescriptor instead') +const DeleteSessionRequest$json = { + '1': 'DeleteSessionRequest', + '2': [ + {'1': 'session_id', '3': 1, '4': 1, '5': 9, '10': 'sessionId'}, + ], +}; + +/// Descriptor for `DeleteSessionRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List deleteSessionRequestDescriptor = $convert.base64Decode( + 'ChREZWxldGVTZXNzaW9uUmVxdWVzdBIdCgpzZXNzaW9uX2lkGAEgASgJUglzZXNzaW9uSWQ='); + +@$core.Deprecated('Use deleteSessionResponseDescriptor instead') +const DeleteSessionResponse$json = { + '1': 'DeleteSessionResponse', + '2': [ + {'1': 'deleted', '3': 1, '4': 1, '5': 8, '10': 'deleted'}, + ], +}; + +/// Descriptor for `DeleteSessionResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List deleteSessionResponseDescriptor = + $convert.base64Decode( + 'ChVEZWxldGVTZXNzaW9uUmVzcG9uc2USGAoHZGVsZXRlZBgBIAEoCFIHZGVsZXRlZA=='); diff --git a/lib/generated/betcode/v1/notification.pb.dart b/lib/generated/betcode/v1/notification.pb.dart new file mode 100644 index 0000000..e4515e6 --- /dev/null +++ b/lib/generated/betcode/v1/notification.pb.dart @@ -0,0 +1,271 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/notification.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +import 'notification.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'notification.pbenum.dart'; + +class RegisterDeviceRequest extends $pb.GeneratedMessage { + factory RegisterDeviceRequest({ + $core.String? deviceToken, + DevicePlatform? platform, + $core.String? userId, + }) { + final result = create(); + if (deviceToken != null) result.deviceToken = deviceToken; + if (platform != null) result.platform = platform; + if (userId != null) result.userId = userId; + return result; + } + + RegisterDeviceRequest._(); + + factory RegisterDeviceRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RegisterDeviceRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RegisterDeviceRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'deviceToken') + ..aE(2, _omitFieldNames ? '' : 'platform', + enumValues: DevicePlatform.values) + ..aOS(3, _omitFieldNames ? '' : 'userId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterDeviceRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterDeviceRequest copyWith( + void Function(RegisterDeviceRequest) updates) => + super.copyWith((message) => updates(message as RegisterDeviceRequest)) + as RegisterDeviceRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RegisterDeviceRequest create() => RegisterDeviceRequest._(); + @$core.override + RegisterDeviceRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RegisterDeviceRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RegisterDeviceRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get deviceToken => $_getSZ(0); + @$pb.TagNumber(1) + set deviceToken($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasDeviceToken() => $_has(0); + @$pb.TagNumber(1) + void clearDeviceToken() => $_clearField(1); + + @$pb.TagNumber(2) + DevicePlatform get platform => $_getN(1); + @$pb.TagNumber(2) + set platform(DevicePlatform value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasPlatform() => $_has(1); + @$pb.TagNumber(2) + void clearPlatform() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get userId => $_getSZ(2); + @$pb.TagNumber(3) + set userId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasUserId() => $_has(2); + @$pb.TagNumber(3) + void clearUserId() => $_clearField(3); +} + +class RegisterDeviceResponse extends $pb.GeneratedMessage { + factory RegisterDeviceResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + RegisterDeviceResponse._(); + + factory RegisterDeviceResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RegisterDeviceResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RegisterDeviceResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterDeviceResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterDeviceResponse copyWith( + void Function(RegisterDeviceResponse) updates) => + super.copyWith((message) => updates(message as RegisterDeviceResponse)) + as RegisterDeviceResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RegisterDeviceResponse create() => RegisterDeviceResponse._(); + @$core.override + RegisterDeviceResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RegisterDeviceResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RegisterDeviceResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +class UnregisterDeviceRequest extends $pb.GeneratedMessage { + factory UnregisterDeviceRequest({ + $core.String? deviceToken, + }) { + final result = create(); + if (deviceToken != null) result.deviceToken = deviceToken; + return result; + } + + UnregisterDeviceRequest._(); + + factory UnregisterDeviceRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UnregisterDeviceRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UnregisterDeviceRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'deviceToken') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterDeviceRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterDeviceRequest copyWith( + void Function(UnregisterDeviceRequest) updates) => + super.copyWith((message) => updates(message as UnregisterDeviceRequest)) + as UnregisterDeviceRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UnregisterDeviceRequest create() => UnregisterDeviceRequest._(); + @$core.override + UnregisterDeviceRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UnregisterDeviceRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UnregisterDeviceRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get deviceToken => $_getSZ(0); + @$pb.TagNumber(1) + set deviceToken($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasDeviceToken() => $_has(0); + @$pb.TagNumber(1) + void clearDeviceToken() => $_clearField(1); +} + +class UnregisterDeviceResponse extends $pb.GeneratedMessage { + factory UnregisterDeviceResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + UnregisterDeviceResponse._(); + + factory UnregisterDeviceResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UnregisterDeviceResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UnregisterDeviceResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterDeviceResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterDeviceResponse copyWith( + void Function(UnregisterDeviceResponse) updates) => + super.copyWith((message) => updates(message as UnregisterDeviceResponse)) + as UnregisterDeviceResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UnregisterDeviceResponse create() => UnregisterDeviceResponse._(); + @$core.override + UnregisterDeviceResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UnregisterDeviceResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UnregisterDeviceResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/generated/betcode/v1/notification.pbenum.dart b/lib/generated/betcode/v1/notification.pbenum.dart new file mode 100644 index 0000000..6b45ec7 --- /dev/null +++ b/lib/generated/betcode/v1/notification.pbenum.dart @@ -0,0 +1,40 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/notification.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +class DevicePlatform extends $pb.ProtobufEnum { + static const DevicePlatform DEVICE_PLATFORM_UNSPECIFIED = + DevicePlatform._(0, _omitEnumNames ? '' : 'DEVICE_PLATFORM_UNSPECIFIED'); + static const DevicePlatform DEVICE_PLATFORM_ANDROID = + DevicePlatform._(1, _omitEnumNames ? '' : 'DEVICE_PLATFORM_ANDROID'); + static const DevicePlatform DEVICE_PLATFORM_IOS = + DevicePlatform._(2, _omitEnumNames ? '' : 'DEVICE_PLATFORM_IOS'); + + static const $core.List values = [ + DEVICE_PLATFORM_UNSPECIFIED, + DEVICE_PLATFORM_ANDROID, + DEVICE_PLATFORM_IOS, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 2); + static DevicePlatform? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const DevicePlatform._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/generated/betcode/v1/notification.pbgrpc.dart b/lib/generated/betcode/v1/notification.pbgrpc.dart new file mode 100644 index 0000000..3c8e31b --- /dev/null +++ b/lib/generated/betcode/v1/notification.pbgrpc.dart @@ -0,0 +1,108 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/notification.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'notification.pb.dart' as $0; + +export 'notification.pb.dart'; + +/// NotificationService manages device registrations for push notifications. +@$pb.GrpcServiceName('betcode.v1.NotificationService') +class NotificationServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + NotificationServiceClient(super.channel, {super.options, super.interceptors}); + + /// Register a device token for push notifications. + $grpc.ResponseFuture<$0.RegisterDeviceResponse> registerDevice( + $0.RegisterDeviceRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$registerDevice, request, options: options); + } + + /// Unregister a device token to stop receiving push notifications. + $grpc.ResponseFuture<$0.UnregisterDeviceResponse> unregisterDevice( + $0.UnregisterDeviceRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$unregisterDevice, request, options: options); + } + + // method descriptors + + static final _$registerDevice = + $grpc.ClientMethod<$0.RegisterDeviceRequest, $0.RegisterDeviceResponse>( + '/betcode.v1.NotificationService/RegisterDevice', + ($0.RegisterDeviceRequest value) => value.writeToBuffer(), + $0.RegisterDeviceResponse.fromBuffer); + static final _$unregisterDevice = $grpc.ClientMethod< + $0.UnregisterDeviceRequest, $0.UnregisterDeviceResponse>( + '/betcode.v1.NotificationService/UnregisterDevice', + ($0.UnregisterDeviceRequest value) => value.writeToBuffer(), + $0.UnregisterDeviceResponse.fromBuffer); +} + +@$pb.GrpcServiceName('betcode.v1.NotificationService') +abstract class NotificationServiceBase extends $grpc.Service { + $core.String get $name => 'betcode.v1.NotificationService'; + + NotificationServiceBase() { + $addMethod($grpc.ServiceMethod<$0.RegisterDeviceRequest, + $0.RegisterDeviceResponse>( + 'RegisterDevice', + registerDevice_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.RegisterDeviceRequest.fromBuffer(value), + ($0.RegisterDeviceResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.UnregisterDeviceRequest, + $0.UnregisterDeviceResponse>( + 'UnregisterDevice', + unregisterDevice_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.UnregisterDeviceRequest.fromBuffer(value), + ($0.UnregisterDeviceResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.RegisterDeviceResponse> registerDevice_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.RegisterDeviceRequest> $request) async { + return registerDevice($call, await $request); + } + + $async.Future<$0.RegisterDeviceResponse> registerDevice( + $grpc.ServiceCall call, $0.RegisterDeviceRequest request); + + $async.Future<$0.UnregisterDeviceResponse> unregisterDevice_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.UnregisterDeviceRequest> $request) async { + return unregisterDevice($call, await $request); + } + + $async.Future<$0.UnregisterDeviceResponse> unregisterDevice( + $grpc.ServiceCall call, $0.UnregisterDeviceRequest request); +} diff --git a/lib/generated/betcode/v1/notification.pbjson.dart b/lib/generated/betcode/v1/notification.pbjson.dart new file mode 100644 index 0000000..d447d37 --- /dev/null +++ b/lib/generated/betcode/v1/notification.pbjson.dart @@ -0,0 +1,94 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/notification.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use devicePlatformDescriptor instead') +const DevicePlatform$json = { + '1': 'DevicePlatform', + '2': [ + {'1': 'DEVICE_PLATFORM_UNSPECIFIED', '2': 0}, + {'1': 'DEVICE_PLATFORM_ANDROID', '2': 1}, + {'1': 'DEVICE_PLATFORM_IOS', '2': 2}, + ], +}; + +/// Descriptor for `DevicePlatform`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List devicePlatformDescriptor = $convert.base64Decode( + 'Cg5EZXZpY2VQbGF0Zm9ybRIfChtERVZJQ0VfUExBVEZPUk1fVU5TUEVDSUZJRUQQABIbChdERV' + 'ZJQ0VfUExBVEZPUk1fQU5EUk9JRBABEhcKE0RFVklDRV9QTEFURk9STV9JT1MQAg=='); + +@$core.Deprecated('Use registerDeviceRequestDescriptor instead') +const RegisterDeviceRequest$json = { + '1': 'RegisterDeviceRequest', + '2': [ + {'1': 'device_token', '3': 1, '4': 1, '5': 9, '10': 'deviceToken'}, + { + '1': 'platform', + '3': 2, + '4': 1, + '5': 14, + '6': '.betcode.v1.DevicePlatform', + '10': 'platform' + }, + {'1': 'user_id', '3': 3, '4': 1, '5': 9, '10': 'userId'}, + ], +}; + +/// Descriptor for `RegisterDeviceRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List registerDeviceRequestDescriptor = $convert.base64Decode( + 'ChVSZWdpc3RlckRldmljZVJlcXVlc3QSIQoMZGV2aWNlX3Rva2VuGAEgASgJUgtkZXZpY2VUb2' + 'tlbhI2CghwbGF0Zm9ybRgCIAEoDjIaLmJldGNvZGUudjEuRGV2aWNlUGxhdGZvcm1SCHBsYXRm' + 'b3JtEhcKB3VzZXJfaWQYAyABKAlSBnVzZXJJZA=='); + +@$core.Deprecated('Use registerDeviceResponseDescriptor instead') +const RegisterDeviceResponse$json = { + '1': 'RegisterDeviceResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `RegisterDeviceResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List registerDeviceResponseDescriptor = + $convert.base64Decode( + 'ChZSZWdpc3RlckRldmljZVJlc3BvbnNlEhgKB3N1Y2Nlc3MYASABKAhSB3N1Y2Nlc3M='); + +@$core.Deprecated('Use unregisterDeviceRequestDescriptor instead') +const UnregisterDeviceRequest$json = { + '1': 'UnregisterDeviceRequest', + '2': [ + {'1': 'device_token', '3': 1, '4': 1, '5': 9, '10': 'deviceToken'}, + ], +}; + +/// Descriptor for `UnregisterDeviceRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List unregisterDeviceRequestDescriptor = + $convert.base64Decode( + 'ChdVbnJlZ2lzdGVyRGV2aWNlUmVxdWVzdBIhCgxkZXZpY2VfdG9rZW4YASABKAlSC2RldmljZV' + 'Rva2Vu'); + +@$core.Deprecated('Use unregisterDeviceResponseDescriptor instead') +const UnregisterDeviceResponse$json = { + '1': 'UnregisterDeviceResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `UnregisterDeviceResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List unregisterDeviceResponseDescriptor = + $convert.base64Decode( + 'ChhVbnJlZ2lzdGVyRGV2aWNlUmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2Vzcw=='); diff --git a/lib/generated/betcode/v1/subagent.pb.dart b/lib/generated/betcode/v1/subagent.pb.dart new file mode 100644 index 0000000..b33d925 --- /dev/null +++ b/lib/generated/betcode/v1/subagent.pb.dart @@ -0,0 +1,2730 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/subagent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; +import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart' + as $1; + +import 'subagent.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'subagent.pbenum.dart'; + +/// SpawnSubagentRequest spawns a new Claude subprocess as a subagent. +class SpawnSubagentRequest extends $pb.GeneratedMessage { + factory SpawnSubagentRequest({ + $core.String? parentSessionId, + $core.String? prompt, + $core.String? model, + $core.String? workingDirectory, + $core.Iterable<$core.String>? allowedTools, + $core.int? maxTurns, + $core.Iterable<$core.MapEntry<$core.String, $core.String>>? env, + $core.String? name, + $core.bool? autoApprove, + }) { + final result = create(); + if (parentSessionId != null) result.parentSessionId = parentSessionId; + if (prompt != null) result.prompt = prompt; + if (model != null) result.model = model; + if (workingDirectory != null) result.workingDirectory = workingDirectory; + if (allowedTools != null) result.allowedTools.addAll(allowedTools); + if (maxTurns != null) result.maxTurns = maxTurns; + if (env != null) result.env.addEntries(env); + if (name != null) result.name = name; + if (autoApprove != null) result.autoApprove = autoApprove; + return result; + } + + SpawnSubagentRequest._(); + + factory SpawnSubagentRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SpawnSubagentRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SpawnSubagentRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'parentSessionId') + ..aOS(2, _omitFieldNames ? '' : 'prompt') + ..aOS(3, _omitFieldNames ? '' : 'model') + ..aOS(4, _omitFieldNames ? '' : 'workingDirectory') + ..pPS(5, _omitFieldNames ? '' : 'allowedTools') + ..aI(6, _omitFieldNames ? '' : 'maxTurns') + ..m<$core.String, $core.String>(7, _omitFieldNames ? '' : 'env', + entryClassName: 'SpawnSubagentRequest.EnvEntry', + keyFieldType: $pb.PbFieldType.OS, + valueFieldType: $pb.PbFieldType.OS, + packageName: const $pb.PackageName('betcode.v1')) + ..aOS(8, _omitFieldNames ? '' : 'name') + ..aOB(9, _omitFieldNames ? '' : 'autoApprove') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpawnSubagentRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpawnSubagentRequest copyWith(void Function(SpawnSubagentRequest) updates) => + super.copyWith((message) => updates(message as SpawnSubagentRequest)) + as SpawnSubagentRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SpawnSubagentRequest create() => SpawnSubagentRequest._(); + @$core.override + SpawnSubagentRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SpawnSubagentRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SpawnSubagentRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get parentSessionId => $_getSZ(0); + @$pb.TagNumber(1) + set parentSessionId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasParentSessionId() => $_has(0); + @$pb.TagNumber(1) + void clearParentSessionId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get prompt => $_getSZ(1); + @$pb.TagNumber(2) + set prompt($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasPrompt() => $_has(1); + @$pb.TagNumber(2) + void clearPrompt() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get model => $_getSZ(2); + @$pb.TagNumber(3) + set model($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasModel() => $_has(2); + @$pb.TagNumber(3) + void clearModel() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get workingDirectory => $_getSZ(3); + @$pb.TagNumber(4) + set workingDirectory($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasWorkingDirectory() => $_has(3); + @$pb.TagNumber(4) + void clearWorkingDirectory() => $_clearField(4); + + @$pb.TagNumber(5) + $pb.PbList<$core.String> get allowedTools => $_getList(4); + + @$pb.TagNumber(6) + $core.int get maxTurns => $_getIZ(5); + @$pb.TagNumber(6) + set maxTurns($core.int value) => $_setSignedInt32(5, value); + @$pb.TagNumber(6) + $core.bool hasMaxTurns() => $_has(5); + @$pb.TagNumber(6) + void clearMaxTurns() => $_clearField(6); + + @$pb.TagNumber(7) + $pb.PbMap<$core.String, $core.String> get env => $_getMap(6); + + @$pb.TagNumber(8) + $core.String get name => $_getSZ(7); + @$pb.TagNumber(8) + set name($core.String value) => $_setString(7, value); + @$pb.TagNumber(8) + $core.bool hasName() => $_has(7); + @$pb.TagNumber(8) + void clearName() => $_clearField(8); + + @$pb.TagNumber(9) + $core.bool get autoApprove => $_getBF(8); + @$pb.TagNumber(9) + set autoApprove($core.bool value) => $_setBool(8, value); + @$pb.TagNumber(9) + $core.bool hasAutoApprove() => $_has(8); + @$pb.TagNumber(9) + void clearAutoApprove() => $_clearField(9); +} + +/// SpawnSubagentResponse returns the IDs of the spawned subagent. +class SpawnSubagentResponse extends $pb.GeneratedMessage { + factory SpawnSubagentResponse({ + $core.String? subagentId, + $core.String? sessionId, + }) { + final result = create(); + if (subagentId != null) result.subagentId = subagentId; + if (sessionId != null) result.sessionId = sessionId; + return result; + } + + SpawnSubagentResponse._(); + + factory SpawnSubagentResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SpawnSubagentResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SpawnSubagentResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'subagentId') + ..aOS(2, _omitFieldNames ? '' : 'sessionId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpawnSubagentResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SpawnSubagentResponse copyWith( + void Function(SpawnSubagentResponse) updates) => + super.copyWith((message) => updates(message as SpawnSubagentResponse)) + as SpawnSubagentResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SpawnSubagentResponse create() => SpawnSubagentResponse._(); + @$core.override + SpawnSubagentResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SpawnSubagentResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SpawnSubagentResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get subagentId => $_getSZ(0); + @$pb.TagNumber(1) + set subagentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSubagentId() => $_has(0); + @$pb.TagNumber(1) + void clearSubagentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get sessionId => $_getSZ(1); + @$pb.TagNumber(2) + set sessionId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasSessionId() => $_has(1); + @$pb.TagNumber(2) + void clearSessionId() => $_clearField(2); +} + +/// WatchSubagentRequest subscribes to a subagent's event stream. +class WatchSubagentRequest extends $pb.GeneratedMessage { + factory WatchSubagentRequest({ + $core.String? subagentId, + $fixnum.Int64? fromSequence, + }) { + final result = create(); + if (subagentId != null) result.subagentId = subagentId; + if (fromSequence != null) result.fromSequence = fromSequence; + return result; + } + + WatchSubagentRequest._(); + + factory WatchSubagentRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory WatchSubagentRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WatchSubagentRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'subagentId') + ..a<$fixnum.Int64>( + 2, _omitFieldNames ? '' : 'fromSequence', $pb.PbFieldType.OU6, + defaultOrMaker: $fixnum.Int64.ZERO) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchSubagentRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchSubagentRequest copyWith(void Function(WatchSubagentRequest) updates) => + super.copyWith((message) => updates(message as WatchSubagentRequest)) + as WatchSubagentRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static WatchSubagentRequest create() => WatchSubagentRequest._(); + @$core.override + WatchSubagentRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static WatchSubagentRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static WatchSubagentRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get subagentId => $_getSZ(0); + @$pb.TagNumber(1) + set subagentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSubagentId() => $_has(0); + @$pb.TagNumber(1) + void clearSubagentId() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get fromSequence => $_getI64(1); + @$pb.TagNumber(2) + set fromSequence($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasFromSequence() => $_has(1); + @$pb.TagNumber(2) + void clearFromSequence() => $_clearField(2); +} + +/// SendToSubagentRequest sends input to a running subagent. +class SendToSubagentRequest extends $pb.GeneratedMessage { + factory SendToSubagentRequest({ + $core.String? subagentId, + $core.String? content, + }) { + final result = create(); + if (subagentId != null) result.subagentId = subagentId; + if (content != null) result.content = content; + return result; + } + + SendToSubagentRequest._(); + + factory SendToSubagentRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SendToSubagentRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SendToSubagentRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'subagentId') + ..aOS(2, _omitFieldNames ? '' : 'content') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendToSubagentRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendToSubagentRequest copyWith( + void Function(SendToSubagentRequest) updates) => + super.copyWith((message) => updates(message as SendToSubagentRequest)) + as SendToSubagentRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SendToSubagentRequest create() => SendToSubagentRequest._(); + @$core.override + SendToSubagentRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SendToSubagentRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SendToSubagentRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get subagentId => $_getSZ(0); + @$pb.TagNumber(1) + set subagentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSubagentId() => $_has(0); + @$pb.TagNumber(1) + void clearSubagentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get content => $_getSZ(1); + @$pb.TagNumber(2) + set content($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasContent() => $_has(1); + @$pb.TagNumber(2) + void clearContent() => $_clearField(2); +} + +/// SendToSubagentResponse acknowledges input delivery. +class SendToSubagentResponse extends $pb.GeneratedMessage { + factory SendToSubagentResponse({ + $core.bool? delivered, + }) { + final result = create(); + if (delivered != null) result.delivered = delivered; + return result; + } + + SendToSubagentResponse._(); + + factory SendToSubagentResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SendToSubagentResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SendToSubagentResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'delivered') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendToSubagentResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendToSubagentResponse copyWith( + void Function(SendToSubagentResponse) updates) => + super.copyWith((message) => updates(message as SendToSubagentResponse)) + as SendToSubagentResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SendToSubagentResponse create() => SendToSubagentResponse._(); + @$core.override + SendToSubagentResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SendToSubagentResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SendToSubagentResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get delivered => $_getBF(0); + @$pb.TagNumber(1) + set delivered($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasDelivered() => $_has(0); + @$pb.TagNumber(1) + void clearDelivered() => $_clearField(1); +} + +/// CancelSubagentRequest cancels a running subagent. +class CancelSubagentRequest extends $pb.GeneratedMessage { + factory CancelSubagentRequest({ + $core.String? subagentId, + $core.String? reason, + $core.bool? force, + $core.bool? cleanupWorktree, + }) { + final result = create(); + if (subagentId != null) result.subagentId = subagentId; + if (reason != null) result.reason = reason; + if (force != null) result.force = force; + if (cleanupWorktree != null) result.cleanupWorktree = cleanupWorktree; + return result; + } + + CancelSubagentRequest._(); + + factory CancelSubagentRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory CancelSubagentRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CancelSubagentRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'subagentId') + ..aOS(2, _omitFieldNames ? '' : 'reason') + ..aOB(3, _omitFieldNames ? '' : 'force') + ..aOB(4, _omitFieldNames ? '' : 'cleanupWorktree') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CancelSubagentRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CancelSubagentRequest copyWith( + void Function(CancelSubagentRequest) updates) => + super.copyWith((message) => updates(message as CancelSubagentRequest)) + as CancelSubagentRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CancelSubagentRequest create() => CancelSubagentRequest._(); + @$core.override + CancelSubagentRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static CancelSubagentRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static CancelSubagentRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get subagentId => $_getSZ(0); + @$pb.TagNumber(1) + set subagentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSubagentId() => $_has(0); + @$pb.TagNumber(1) + void clearSubagentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get reason => $_getSZ(1); + @$pb.TagNumber(2) + set reason($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasReason() => $_has(1); + @$pb.TagNumber(2) + void clearReason() => $_clearField(2); + + @$pb.TagNumber(3) + $core.bool get force => $_getBF(2); + @$pb.TagNumber(3) + set force($core.bool value) => $_setBool(2, value); + @$pb.TagNumber(3) + $core.bool hasForce() => $_has(2); + @$pb.TagNumber(3) + void clearForce() => $_clearField(3); + + @$pb.TagNumber(4) + $core.bool get cleanupWorktree => $_getBF(3); + @$pb.TagNumber(4) + set cleanupWorktree($core.bool value) => $_setBool(3, value); + @$pb.TagNumber(4) + $core.bool hasCleanupWorktree() => $_has(3); + @$pb.TagNumber(4) + void clearCleanupWorktree() => $_clearField(4); +} + +/// CancelSubagentResponse returns the result of cancellation. +class CancelSubagentResponse extends $pb.GeneratedMessage { + factory CancelSubagentResponse({ + $core.bool? cancelled, + $core.String? finalStatus, + $core.int? toolCallsExecuted, + $core.int? toolCallsAutoApproved, + }) { + final result = create(); + if (cancelled != null) result.cancelled = cancelled; + if (finalStatus != null) result.finalStatus = finalStatus; + if (toolCallsExecuted != null) result.toolCallsExecuted = toolCallsExecuted; + if (toolCallsAutoApproved != null) + result.toolCallsAutoApproved = toolCallsAutoApproved; + return result; + } + + CancelSubagentResponse._(); + + factory CancelSubagentResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory CancelSubagentResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CancelSubagentResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'cancelled') + ..aOS(2, _omitFieldNames ? '' : 'finalStatus') + ..aI(3, _omitFieldNames ? '' : 'toolCallsExecuted') + ..aI(4, _omitFieldNames ? '' : 'toolCallsAutoApproved') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CancelSubagentResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CancelSubagentResponse copyWith( + void Function(CancelSubagentResponse) updates) => + super.copyWith((message) => updates(message as CancelSubagentResponse)) + as CancelSubagentResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CancelSubagentResponse create() => CancelSubagentResponse._(); + @$core.override + CancelSubagentResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static CancelSubagentResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static CancelSubagentResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get cancelled => $_getBF(0); + @$pb.TagNumber(1) + set cancelled($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasCancelled() => $_has(0); + @$pb.TagNumber(1) + void clearCancelled() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get finalStatus => $_getSZ(1); + @$pb.TagNumber(2) + set finalStatus($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasFinalStatus() => $_has(1); + @$pb.TagNumber(2) + void clearFinalStatus() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get toolCallsExecuted => $_getIZ(2); + @$pb.TagNumber(3) + set toolCallsExecuted($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasToolCallsExecuted() => $_has(2); + @$pb.TagNumber(3) + void clearToolCallsExecuted() => $_clearField(3); + + @$pb.TagNumber(4) + $core.int get toolCallsAutoApproved => $_getIZ(3); + @$pb.TagNumber(4) + set toolCallsAutoApproved($core.int value) => $_setSignedInt32(3, value); + @$pb.TagNumber(4) + $core.bool hasToolCallsAutoApproved() => $_has(3); + @$pb.TagNumber(4) + void clearToolCallsAutoApproved() => $_clearField(4); +} + +/// ListSubagentsRequest lists subagents under a parent session. +class ListSubagentsRequest extends $pb.GeneratedMessage { + factory ListSubagentsRequest({ + $core.String? parentSessionId, + $core.String? statusFilter, + }) { + final result = create(); + if (parentSessionId != null) result.parentSessionId = parentSessionId; + if (statusFilter != null) result.statusFilter = statusFilter; + return result; + } + + ListSubagentsRequest._(); + + factory ListSubagentsRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ListSubagentsRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ListSubagentsRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'parentSessionId') + ..aOS(2, _omitFieldNames ? '' : 'statusFilter') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListSubagentsRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListSubagentsRequest copyWith(void Function(ListSubagentsRequest) updates) => + super.copyWith((message) => updates(message as ListSubagentsRequest)) + as ListSubagentsRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ListSubagentsRequest create() => ListSubagentsRequest._(); + @$core.override + ListSubagentsRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ListSubagentsRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ListSubagentsRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get parentSessionId => $_getSZ(0); + @$pb.TagNumber(1) + set parentSessionId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasParentSessionId() => $_has(0); + @$pb.TagNumber(1) + void clearParentSessionId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get statusFilter => $_getSZ(1); + @$pb.TagNumber(2) + set statusFilter($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasStatusFilter() => $_has(1); + @$pb.TagNumber(2) + void clearStatusFilter() => $_clearField(2); +} + +/// ListSubagentsResponse returns matching subagents. +class ListSubagentsResponse extends $pb.GeneratedMessage { + factory ListSubagentsResponse({ + $core.Iterable? subagents, + }) { + final result = create(); + if (subagents != null) result.subagents.addAll(subagents); + return result; + } + + ListSubagentsResponse._(); + + factory ListSubagentsResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ListSubagentsResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ListSubagentsResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'subagents', + subBuilder: SubagentInfo.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListSubagentsResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListSubagentsResponse copyWith( + void Function(ListSubagentsResponse) updates) => + super.copyWith((message) => updates(message as ListSubagentsResponse)) + as ListSubagentsResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ListSubagentsResponse create() => ListSubagentsResponse._(); + @$core.override + ListSubagentsResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ListSubagentsResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ListSubagentsResponse? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get subagents => $_getList(0); +} + +/// SubagentInfo describes a subagent's current state. +class SubagentInfo extends $pb.GeneratedMessage { + factory SubagentInfo({ + $core.String? id, + $core.String? parentSessionId, + $core.String? sessionId, + $core.String? name, + $core.String? prompt, + $core.String? model, + $core.String? workingDirectory, + SubagentStatus? status, + $core.bool? autoApprove, + $core.int? maxTurns, + $core.Iterable<$core.String>? allowedTools, + $core.String? resultSummary, + $1.Timestamp? createdAt, + $1.Timestamp? completedAt, + }) { + final result = create(); + if (id != null) result.id = id; + if (parentSessionId != null) result.parentSessionId = parentSessionId; + if (sessionId != null) result.sessionId = sessionId; + if (name != null) result.name = name; + if (prompt != null) result.prompt = prompt; + if (model != null) result.model = model; + if (workingDirectory != null) result.workingDirectory = workingDirectory; + if (status != null) result.status = status; + if (autoApprove != null) result.autoApprove = autoApprove; + if (maxTurns != null) result.maxTurns = maxTurns; + if (allowedTools != null) result.allowedTools.addAll(allowedTools); + if (resultSummary != null) result.resultSummary = resultSummary; + if (createdAt != null) result.createdAt = createdAt; + if (completedAt != null) result.completedAt = completedAt; + return result; + } + + SubagentInfo._(); + + factory SubagentInfo.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentInfo.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentInfo', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'parentSessionId') + ..aOS(3, _omitFieldNames ? '' : 'sessionId') + ..aOS(4, _omitFieldNames ? '' : 'name') + ..aOS(5, _omitFieldNames ? '' : 'prompt') + ..aOS(6, _omitFieldNames ? '' : 'model') + ..aOS(7, _omitFieldNames ? '' : 'workingDirectory') + ..aE(8, _omitFieldNames ? '' : 'status', + enumValues: SubagentStatus.values) + ..aOB(9, _omitFieldNames ? '' : 'autoApprove') + ..aI(10, _omitFieldNames ? '' : 'maxTurns') + ..pPS(11, _omitFieldNames ? '' : 'allowedTools') + ..aOS(12, _omitFieldNames ? '' : 'resultSummary') + ..aOM<$1.Timestamp>(13, _omitFieldNames ? '' : 'createdAt', + subBuilder: $1.Timestamp.create) + ..aOM<$1.Timestamp>(14, _omitFieldNames ? '' : 'completedAt', + subBuilder: $1.Timestamp.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentInfo clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentInfo copyWith(void Function(SubagentInfo) updates) => + super.copyWith((message) => updates(message as SubagentInfo)) + as SubagentInfo; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentInfo create() => SubagentInfo._(); + @$core.override + SubagentInfo createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentInfo getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get parentSessionId => $_getSZ(1); + @$pb.TagNumber(2) + set parentSessionId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasParentSessionId() => $_has(1); + @$pb.TagNumber(2) + void clearParentSessionId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get sessionId => $_getSZ(2); + @$pb.TagNumber(3) + set sessionId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasSessionId() => $_has(2); + @$pb.TagNumber(3) + void clearSessionId() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get name => $_getSZ(3); + @$pb.TagNumber(4) + set name($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasName() => $_has(3); + @$pb.TagNumber(4) + void clearName() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get prompt => $_getSZ(4); + @$pb.TagNumber(5) + set prompt($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasPrompt() => $_has(4); + @$pb.TagNumber(5) + void clearPrompt() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get model => $_getSZ(5); + @$pb.TagNumber(6) + set model($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasModel() => $_has(5); + @$pb.TagNumber(6) + void clearModel() => $_clearField(6); + + @$pb.TagNumber(7) + $core.String get workingDirectory => $_getSZ(6); + @$pb.TagNumber(7) + set workingDirectory($core.String value) => $_setString(6, value); + @$pb.TagNumber(7) + $core.bool hasWorkingDirectory() => $_has(6); + @$pb.TagNumber(7) + void clearWorkingDirectory() => $_clearField(7); + + @$pb.TagNumber(8) + SubagentStatus get status => $_getN(7); + @$pb.TagNumber(8) + set status(SubagentStatus value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasStatus() => $_has(7); + @$pb.TagNumber(8) + void clearStatus() => $_clearField(8); + + @$pb.TagNumber(9) + $core.bool get autoApprove => $_getBF(8); + @$pb.TagNumber(9) + set autoApprove($core.bool value) => $_setBool(8, value); + @$pb.TagNumber(9) + $core.bool hasAutoApprove() => $_has(8); + @$pb.TagNumber(9) + void clearAutoApprove() => $_clearField(9); + + @$pb.TagNumber(10) + $core.int get maxTurns => $_getIZ(9); + @$pb.TagNumber(10) + set maxTurns($core.int value) => $_setSignedInt32(9, value); + @$pb.TagNumber(10) + $core.bool hasMaxTurns() => $_has(9); + @$pb.TagNumber(10) + void clearMaxTurns() => $_clearField(10); + + @$pb.TagNumber(11) + $pb.PbList<$core.String> get allowedTools => $_getList(10); + + @$pb.TagNumber(12) + $core.String get resultSummary => $_getSZ(11); + @$pb.TagNumber(12) + set resultSummary($core.String value) => $_setString(11, value); + @$pb.TagNumber(12) + $core.bool hasResultSummary() => $_has(11); + @$pb.TagNumber(12) + void clearResultSummary() => $_clearField(12); + + @$pb.TagNumber(13) + $1.Timestamp get createdAt => $_getN(12); + @$pb.TagNumber(13) + set createdAt($1.Timestamp value) => $_setField(13, value); + @$pb.TagNumber(13) + $core.bool hasCreatedAt() => $_has(12); + @$pb.TagNumber(13) + void clearCreatedAt() => $_clearField(13); + @$pb.TagNumber(13) + $1.Timestamp ensureCreatedAt() => $_ensure(12); + + @$pb.TagNumber(14) + $1.Timestamp get completedAt => $_getN(13); + @$pb.TagNumber(14) + set completedAt($1.Timestamp value) => $_setField(14, value); + @$pb.TagNumber(14) + $core.bool hasCompletedAt() => $_has(13); + @$pb.TagNumber(14) + void clearCompletedAt() => $_clearField(14); + @$pb.TagNumber(14) + $1.Timestamp ensureCompletedAt() => $_ensure(13); +} + +enum SubagentEvent_Event { + started, + output, + toolUse, + permissionRequest, + completed, + failed, + cancelled, + notSet +} + +/// SubagentEvent wraps all subagent-to-client event types. +class SubagentEvent extends $pb.GeneratedMessage { + factory SubagentEvent({ + $core.String? subagentId, + $1.Timestamp? timestamp, + SubagentStarted? started, + SubagentOutput? output, + SubagentToolUse? toolUse, + SubagentPermissionRequest? permissionRequest, + SubagentCompleted? completed, + SubagentFailed? failed, + SubagentCancelled? cancelled, + }) { + final result = create(); + if (subagentId != null) result.subagentId = subagentId; + if (timestamp != null) result.timestamp = timestamp; + if (started != null) result.started = started; + if (output != null) result.output = output; + if (toolUse != null) result.toolUse = toolUse; + if (permissionRequest != null) result.permissionRequest = permissionRequest; + if (completed != null) result.completed = completed; + if (failed != null) result.failed = failed; + if (cancelled != null) result.cancelled = cancelled; + return result; + } + + SubagentEvent._(); + + factory SubagentEvent.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentEvent.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, SubagentEvent_Event> + _SubagentEvent_EventByTag = { + 3: SubagentEvent_Event.started, + 4: SubagentEvent_Event.output, + 5: SubagentEvent_Event.toolUse, + 6: SubagentEvent_Event.permissionRequest, + 7: SubagentEvent_Event.completed, + 8: SubagentEvent_Event.failed, + 9: SubagentEvent_Event.cancelled, + 0: SubagentEvent_Event.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentEvent', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..oo(0, [3, 4, 5, 6, 7, 8, 9]) + ..aOS(1, _omitFieldNames ? '' : 'subagentId') + ..aOM<$1.Timestamp>(2, _omitFieldNames ? '' : 'timestamp', + subBuilder: $1.Timestamp.create) + ..aOM(3, _omitFieldNames ? '' : 'started', + subBuilder: SubagentStarted.create) + ..aOM(4, _omitFieldNames ? '' : 'output', + subBuilder: SubagentOutput.create) + ..aOM(5, _omitFieldNames ? '' : 'toolUse', + subBuilder: SubagentToolUse.create) + ..aOM( + 6, _omitFieldNames ? '' : 'permissionRequest', + subBuilder: SubagentPermissionRequest.create) + ..aOM(7, _omitFieldNames ? '' : 'completed', + subBuilder: SubagentCompleted.create) + ..aOM(8, _omitFieldNames ? '' : 'failed', + subBuilder: SubagentFailed.create) + ..aOM(9, _omitFieldNames ? '' : 'cancelled', + subBuilder: SubagentCancelled.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentEvent clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentEvent copyWith(void Function(SubagentEvent) updates) => + super.copyWith((message) => updates(message as SubagentEvent)) + as SubagentEvent; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentEvent create() => SubagentEvent._(); + @$core.override + SubagentEvent createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentEvent getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentEvent? _defaultInstance; + + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + @$pb.TagNumber(8) + @$pb.TagNumber(9) + SubagentEvent_Event whichEvent() => + _SubagentEvent_EventByTag[$_whichOneof(0)]!; + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + @$pb.TagNumber(8) + @$pb.TagNumber(9) + void clearEvent() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + $core.String get subagentId => $_getSZ(0); + @$pb.TagNumber(1) + set subagentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSubagentId() => $_has(0); + @$pb.TagNumber(1) + void clearSubagentId() => $_clearField(1); + + @$pb.TagNumber(2) + $1.Timestamp get timestamp => $_getN(1); + @$pb.TagNumber(2) + set timestamp($1.Timestamp value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasTimestamp() => $_has(1); + @$pb.TagNumber(2) + void clearTimestamp() => $_clearField(2); + @$pb.TagNumber(2) + $1.Timestamp ensureTimestamp() => $_ensure(1); + + @$pb.TagNumber(3) + SubagentStarted get started => $_getN(2); + @$pb.TagNumber(3) + set started(SubagentStarted value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasStarted() => $_has(2); + @$pb.TagNumber(3) + void clearStarted() => $_clearField(3); + @$pb.TagNumber(3) + SubagentStarted ensureStarted() => $_ensure(2); + + @$pb.TagNumber(4) + SubagentOutput get output => $_getN(3); + @$pb.TagNumber(4) + set output(SubagentOutput value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasOutput() => $_has(3); + @$pb.TagNumber(4) + void clearOutput() => $_clearField(4); + @$pb.TagNumber(4) + SubagentOutput ensureOutput() => $_ensure(3); + + @$pb.TagNumber(5) + SubagentToolUse get toolUse => $_getN(4); + @$pb.TagNumber(5) + set toolUse(SubagentToolUse value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasToolUse() => $_has(4); + @$pb.TagNumber(5) + void clearToolUse() => $_clearField(5); + @$pb.TagNumber(5) + SubagentToolUse ensureToolUse() => $_ensure(4); + + @$pb.TagNumber(6) + SubagentPermissionRequest get permissionRequest => $_getN(5); + @$pb.TagNumber(6) + set permissionRequest(SubagentPermissionRequest value) => + $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasPermissionRequest() => $_has(5); + @$pb.TagNumber(6) + void clearPermissionRequest() => $_clearField(6); + @$pb.TagNumber(6) + SubagentPermissionRequest ensurePermissionRequest() => $_ensure(5); + + @$pb.TagNumber(7) + SubagentCompleted get completed => $_getN(6); + @$pb.TagNumber(7) + set completed(SubagentCompleted value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasCompleted() => $_has(6); + @$pb.TagNumber(7) + void clearCompleted() => $_clearField(7); + @$pb.TagNumber(7) + SubagentCompleted ensureCompleted() => $_ensure(6); + + @$pb.TagNumber(8) + SubagentFailed get failed => $_getN(7); + @$pb.TagNumber(8) + set failed(SubagentFailed value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasFailed() => $_has(7); + @$pb.TagNumber(8) + void clearFailed() => $_clearField(8); + @$pb.TagNumber(8) + SubagentFailed ensureFailed() => $_ensure(7); + + @$pb.TagNumber(9) + SubagentCancelled get cancelled => $_getN(8); + @$pb.TagNumber(9) + set cancelled(SubagentCancelled value) => $_setField(9, value); + @$pb.TagNumber(9) + $core.bool hasCancelled() => $_has(8); + @$pb.TagNumber(9) + void clearCancelled() => $_clearField(9); + @$pb.TagNumber(9) + SubagentCancelled ensureCancelled() => $_ensure(8); +} + +/// SubagentStarted indicates the subagent process has started. +class SubagentStarted extends $pb.GeneratedMessage { + factory SubagentStarted({ + $core.String? sessionId, + $core.String? model, + }) { + final result = create(); + if (sessionId != null) result.sessionId = sessionId; + if (model != null) result.model = model; + return result; + } + + SubagentStarted._(); + + factory SubagentStarted.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentStarted.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentStarted', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'sessionId') + ..aOS(2, _omitFieldNames ? '' : 'model') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentStarted clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentStarted copyWith(void Function(SubagentStarted) updates) => + super.copyWith((message) => updates(message as SubagentStarted)) + as SubagentStarted; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentStarted create() => SubagentStarted._(); + @$core.override + SubagentStarted createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentStarted getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentStarted? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get sessionId => $_getSZ(0); + @$pb.TagNumber(1) + set sessionId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSessionId() => $_has(0); + @$pb.TagNumber(1) + void clearSessionId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get model => $_getSZ(1); + @$pb.TagNumber(2) + set model($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasModel() => $_has(1); + @$pb.TagNumber(2) + void clearModel() => $_clearField(2); +} + +/// SubagentOutput streams incremental text output from the subagent. +class SubagentOutput extends $pb.GeneratedMessage { + factory SubagentOutput({ + $core.String? text, + $core.bool? isComplete, + }) { + final result = create(); + if (text != null) result.text = text; + if (isComplete != null) result.isComplete = isComplete; + return result; + } + + SubagentOutput._(); + + factory SubagentOutput.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentOutput.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentOutput', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'text') + ..aOB(2, _omitFieldNames ? '' : 'isComplete') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentOutput clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentOutput copyWith(void Function(SubagentOutput) updates) => + super.copyWith((message) => updates(message as SubagentOutput)) + as SubagentOutput; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentOutput create() => SubagentOutput._(); + @$core.override + SubagentOutput createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentOutput getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentOutput? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get text => $_getSZ(0); + @$pb.TagNumber(1) + set text($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasText() => $_has(0); + @$pb.TagNumber(1) + void clearText() => $_clearField(1); + + @$pb.TagNumber(2) + $core.bool get isComplete => $_getBF(1); + @$pb.TagNumber(2) + set isComplete($core.bool value) => $_setBool(1, value); + @$pb.TagNumber(2) + $core.bool hasIsComplete() => $_has(1); + @$pb.TagNumber(2) + void clearIsComplete() => $_clearField(2); +} + +/// SubagentToolUse indicates the subagent is invoking a tool. +class SubagentToolUse extends $pb.GeneratedMessage { + factory SubagentToolUse({ + $core.String? toolId, + $core.String? toolName, + $core.String? description, + }) { + final result = create(); + if (toolId != null) result.toolId = toolId; + if (toolName != null) result.toolName = toolName; + if (description != null) result.description = description; + return result; + } + + SubagentToolUse._(); + + factory SubagentToolUse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentToolUse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentToolUse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'toolId') + ..aOS(2, _omitFieldNames ? '' : 'toolName') + ..aOS(3, _omitFieldNames ? '' : 'description') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentToolUse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentToolUse copyWith(void Function(SubagentToolUse) updates) => + super.copyWith((message) => updates(message as SubagentToolUse)) + as SubagentToolUse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentToolUse create() => SubagentToolUse._(); + @$core.override + SubagentToolUse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentToolUse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentToolUse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get toolId => $_getSZ(0); + @$pb.TagNumber(1) + set toolId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasToolId() => $_has(0); + @$pb.TagNumber(1) + void clearToolId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get toolName => $_getSZ(1); + @$pb.TagNumber(2) + set toolName($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasToolName() => $_has(1); + @$pb.TagNumber(2) + void clearToolName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get description => $_getSZ(2); + @$pb.TagNumber(3) + set description($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasDescription() => $_has(2); + @$pb.TagNumber(3) + void clearDescription() => $_clearField(3); +} + +/// SubagentPermissionRequest forwards a permission request from the subagent. +class SubagentPermissionRequest extends $pb.GeneratedMessage { + factory SubagentPermissionRequest({ + $core.String? requestId, + $core.String? toolName, + $core.String? description, + }) { + final result = create(); + if (requestId != null) result.requestId = requestId; + if (toolName != null) result.toolName = toolName; + if (description != null) result.description = description; + return result; + } + + SubagentPermissionRequest._(); + + factory SubagentPermissionRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentPermissionRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentPermissionRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'requestId') + ..aOS(2, _omitFieldNames ? '' : 'toolName') + ..aOS(3, _omitFieldNames ? '' : 'description') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentPermissionRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentPermissionRequest copyWith( + void Function(SubagentPermissionRequest) updates) => + super.copyWith((message) => updates(message as SubagentPermissionRequest)) + as SubagentPermissionRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentPermissionRequest create() => SubagentPermissionRequest._(); + @$core.override + SubagentPermissionRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentPermissionRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentPermissionRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get requestId => $_getSZ(0); + @$pb.TagNumber(1) + set requestId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRequestId() => $_has(0); + @$pb.TagNumber(1) + void clearRequestId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get toolName => $_getSZ(1); + @$pb.TagNumber(2) + set toolName($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasToolName() => $_has(1); + @$pb.TagNumber(2) + void clearToolName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get description => $_getSZ(2); + @$pb.TagNumber(3) + set description($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasDescription() => $_has(2); + @$pb.TagNumber(3) + void clearDescription() => $_clearField(3); +} + +/// SubagentCompleted indicates the subagent finished successfully. +class SubagentCompleted extends $pb.GeneratedMessage { + factory SubagentCompleted({ + $core.int? exitCode, + $core.String? resultSummary, + }) { + final result = create(); + if (exitCode != null) result.exitCode = exitCode; + if (resultSummary != null) result.resultSummary = resultSummary; + return result; + } + + SubagentCompleted._(); + + factory SubagentCompleted.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentCompleted.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentCompleted', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'exitCode') + ..aOS(2, _omitFieldNames ? '' : 'resultSummary') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentCompleted clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentCompleted copyWith(void Function(SubagentCompleted) updates) => + super.copyWith((message) => updates(message as SubagentCompleted)) + as SubagentCompleted; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentCompleted create() => SubagentCompleted._(); + @$core.override + SubagentCompleted createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentCompleted getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentCompleted? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get exitCode => $_getIZ(0); + @$pb.TagNumber(1) + set exitCode($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasExitCode() => $_has(0); + @$pb.TagNumber(1) + void clearExitCode() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get resultSummary => $_getSZ(1); + @$pb.TagNumber(2) + set resultSummary($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasResultSummary() => $_has(1); + @$pb.TagNumber(2) + void clearResultSummary() => $_clearField(2); +} + +/// SubagentFailed indicates the subagent exited with an error. +class SubagentFailed extends $pb.GeneratedMessage { + factory SubagentFailed({ + $core.int? exitCode, + $core.String? errorMessage, + }) { + final result = create(); + if (exitCode != null) result.exitCode = exitCode; + if (errorMessage != null) result.errorMessage = errorMessage; + return result; + } + + SubagentFailed._(); + + factory SubagentFailed.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentFailed.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentFailed', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'exitCode') + ..aOS(2, _omitFieldNames ? '' : 'errorMessage') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentFailed clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentFailed copyWith(void Function(SubagentFailed) updates) => + super.copyWith((message) => updates(message as SubagentFailed)) + as SubagentFailed; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentFailed create() => SubagentFailed._(); + @$core.override + SubagentFailed createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentFailed getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentFailed? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get exitCode => $_getIZ(0); + @$pb.TagNumber(1) + set exitCode($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasExitCode() => $_has(0); + @$pb.TagNumber(1) + void clearExitCode() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get errorMessage => $_getSZ(1); + @$pb.TagNumber(2) + set errorMessage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasErrorMessage() => $_has(1); + @$pb.TagNumber(2) + void clearErrorMessage() => $_clearField(2); +} + +/// SubagentCancelled indicates the subagent was cancelled. +class SubagentCancelled extends $pb.GeneratedMessage { + factory SubagentCancelled({ + $core.String? reason, + }) { + final result = create(); + if (reason != null) result.reason = reason; + return result; + } + + SubagentCancelled._(); + + factory SubagentCancelled.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SubagentCancelled.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SubagentCancelled', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'reason') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentCancelled clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SubagentCancelled copyWith(void Function(SubagentCancelled) updates) => + super.copyWith((message) => updates(message as SubagentCancelled)) + as SubagentCancelled; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SubagentCancelled create() => SubagentCancelled._(); + @$core.override + SubagentCancelled createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SubagentCancelled getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SubagentCancelled? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get reason => $_getSZ(0); + @$pb.TagNumber(1) + set reason($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasReason() => $_has(0); + @$pb.TagNumber(1) + void clearReason() => $_clearField(1); +} + +/// RevokeAutoApproveRequest revokes auto-approve on a running subagent. +class RevokeAutoApproveRequest extends $pb.GeneratedMessage { + factory RevokeAutoApproveRequest({ + $core.String? subagentId, + $core.String? reason, + $core.bool? terminateIfPending, + }) { + final result = create(); + if (subagentId != null) result.subagentId = subagentId; + if (reason != null) result.reason = reason; + if (terminateIfPending != null) + result.terminateIfPending = terminateIfPending; + return result; + } + + RevokeAutoApproveRequest._(); + + factory RevokeAutoApproveRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RevokeAutoApproveRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RevokeAutoApproveRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'subagentId') + ..aOS(2, _omitFieldNames ? '' : 'reason') + ..aOB(3, _omitFieldNames ? '' : 'terminateIfPending') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RevokeAutoApproveRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RevokeAutoApproveRequest copyWith( + void Function(RevokeAutoApproveRequest) updates) => + super.copyWith((message) => updates(message as RevokeAutoApproveRequest)) + as RevokeAutoApproveRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RevokeAutoApproveRequest create() => RevokeAutoApproveRequest._(); + @$core.override + RevokeAutoApproveRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RevokeAutoApproveRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RevokeAutoApproveRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get subagentId => $_getSZ(0); + @$pb.TagNumber(1) + set subagentId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasSubagentId() => $_has(0); + @$pb.TagNumber(1) + void clearSubagentId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get reason => $_getSZ(1); + @$pb.TagNumber(2) + set reason($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasReason() => $_has(1); + @$pb.TagNumber(2) + void clearReason() => $_clearField(2); + + @$pb.TagNumber(3) + $core.bool get terminateIfPending => $_getBF(2); + @$pb.TagNumber(3) + set terminateIfPending($core.bool value) => $_setBool(2, value); + @$pb.TagNumber(3) + $core.bool hasTerminateIfPending() => $_has(2); + @$pb.TagNumber(3) + void clearTerminateIfPending() => $_clearField(3); +} + +/// RevokeAutoApproveResponse returns the result of revocation. +class RevokeAutoApproveResponse extends $pb.GeneratedMessage { + factory RevokeAutoApproveResponse({ + $core.bool? revoked, + $core.int? pendingToolCalls, + $core.String? subagentStatus, + }) { + final result = create(); + if (revoked != null) result.revoked = revoked; + if (pendingToolCalls != null) result.pendingToolCalls = pendingToolCalls; + if (subagentStatus != null) result.subagentStatus = subagentStatus; + return result; + } + + RevokeAutoApproveResponse._(); + + factory RevokeAutoApproveResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RevokeAutoApproveResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RevokeAutoApproveResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'revoked') + ..aI(2, _omitFieldNames ? '' : 'pendingToolCalls') + ..aOS(3, _omitFieldNames ? '' : 'subagentStatus') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RevokeAutoApproveResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RevokeAutoApproveResponse copyWith( + void Function(RevokeAutoApproveResponse) updates) => + super.copyWith((message) => updates(message as RevokeAutoApproveResponse)) + as RevokeAutoApproveResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RevokeAutoApproveResponse create() => RevokeAutoApproveResponse._(); + @$core.override + RevokeAutoApproveResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RevokeAutoApproveResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RevokeAutoApproveResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get revoked => $_getBF(0); + @$pb.TagNumber(1) + set revoked($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasRevoked() => $_has(0); + @$pb.TagNumber(1) + void clearRevoked() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get pendingToolCalls => $_getIZ(1); + @$pb.TagNumber(2) + set pendingToolCalls($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasPendingToolCalls() => $_has(1); + @$pb.TagNumber(2) + void clearPendingToolCalls() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get subagentStatus => $_getSZ(2); + @$pb.TagNumber(3) + set subagentStatus($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasSubagentStatus() => $_has(2); + @$pb.TagNumber(3) + void clearSubagentStatus() => $_clearField(3); +} + +/// CreateOrchestrationRequest creates a multi-step orchestration plan. +class CreateOrchestrationRequest extends $pb.GeneratedMessage { + factory CreateOrchestrationRequest({ + $core.String? parentSessionId, + $core.Iterable? steps, + OrchestrationStrategy? strategy, + }) { + final result = create(); + if (parentSessionId != null) result.parentSessionId = parentSessionId; + if (steps != null) result.steps.addAll(steps); + if (strategy != null) result.strategy = strategy; + return result; + } + + CreateOrchestrationRequest._(); + + factory CreateOrchestrationRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory CreateOrchestrationRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CreateOrchestrationRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'parentSessionId') + ..pPM(2, _omitFieldNames ? '' : 'steps', + subBuilder: OrchestrationStep.create) + ..aE(3, _omitFieldNames ? '' : 'strategy', + enumValues: OrchestrationStrategy.values) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateOrchestrationRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateOrchestrationRequest copyWith( + void Function(CreateOrchestrationRequest) updates) => + super.copyWith( + (message) => updates(message as CreateOrchestrationRequest)) + as CreateOrchestrationRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CreateOrchestrationRequest create() => CreateOrchestrationRequest._(); + @$core.override + CreateOrchestrationRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static CreateOrchestrationRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static CreateOrchestrationRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get parentSessionId => $_getSZ(0); + @$pb.TagNumber(1) + set parentSessionId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasParentSessionId() => $_has(0); + @$pb.TagNumber(1) + void clearParentSessionId() => $_clearField(1); + + @$pb.TagNumber(2) + $pb.PbList get steps => $_getList(1); + + @$pb.TagNumber(3) + OrchestrationStrategy get strategy => $_getN(2); + @$pb.TagNumber(3) + set strategy(OrchestrationStrategy value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasStrategy() => $_has(2); + @$pb.TagNumber(3) + void clearStrategy() => $_clearField(3); +} + +/// CreateOrchestrationResponse returns the orchestration ID. +class CreateOrchestrationResponse extends $pb.GeneratedMessage { + factory CreateOrchestrationResponse({ + $core.String? orchestrationId, + $core.int? totalSteps, + }) { + final result = create(); + if (orchestrationId != null) result.orchestrationId = orchestrationId; + if (totalSteps != null) result.totalSteps = totalSteps; + return result; + } + + CreateOrchestrationResponse._(); + + factory CreateOrchestrationResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory CreateOrchestrationResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CreateOrchestrationResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'orchestrationId') + ..aI(2, _omitFieldNames ? '' : 'totalSteps') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateOrchestrationResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateOrchestrationResponse copyWith( + void Function(CreateOrchestrationResponse) updates) => + super.copyWith( + (message) => updates(message as CreateOrchestrationResponse)) + as CreateOrchestrationResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CreateOrchestrationResponse create() => + CreateOrchestrationResponse._(); + @$core.override + CreateOrchestrationResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static CreateOrchestrationResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static CreateOrchestrationResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get orchestrationId => $_getSZ(0); + @$pb.TagNumber(1) + set orchestrationId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasOrchestrationId() => $_has(0); + @$pb.TagNumber(1) + void clearOrchestrationId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get totalSteps => $_getIZ(1); + @$pb.TagNumber(2) + set totalSteps($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasTotalSteps() => $_has(1); + @$pb.TagNumber(2) + void clearTotalSteps() => $_clearField(2); +} + +/// OrchestrationStep defines a single step in an orchestration plan. +class OrchestrationStep extends $pb.GeneratedMessage { + factory OrchestrationStep({ + $core.String? id, + $core.String? name, + $core.String? prompt, + $core.String? model, + $core.String? workingDirectory, + $core.Iterable<$core.String>? allowedTools, + $core.Iterable<$core.String>? dependsOn, + $core.int? maxTurns, + $core.bool? autoApprove, + }) { + final result = create(); + if (id != null) result.id = id; + if (name != null) result.name = name; + if (prompt != null) result.prompt = prompt; + if (model != null) result.model = model; + if (workingDirectory != null) result.workingDirectory = workingDirectory; + if (allowedTools != null) result.allowedTools.addAll(allowedTools); + if (dependsOn != null) result.dependsOn.addAll(dependsOn); + if (maxTurns != null) result.maxTurns = maxTurns; + if (autoApprove != null) result.autoApprove = autoApprove; + return result; + } + + OrchestrationStep._(); + + factory OrchestrationStep.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory OrchestrationStep.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'OrchestrationStep', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'name') + ..aOS(3, _omitFieldNames ? '' : 'prompt') + ..aOS(4, _omitFieldNames ? '' : 'model') + ..aOS(5, _omitFieldNames ? '' : 'workingDirectory') + ..pPS(6, _omitFieldNames ? '' : 'allowedTools') + ..pPS(7, _omitFieldNames ? '' : 'dependsOn') + ..aI(8, _omitFieldNames ? '' : 'maxTurns') + ..aOB(9, _omitFieldNames ? '' : 'autoApprove') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationStep clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationStep copyWith(void Function(OrchestrationStep) updates) => + super.copyWith((message) => updates(message as OrchestrationStep)) + as OrchestrationStep; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static OrchestrationStep create() => OrchestrationStep._(); + @$core.override + OrchestrationStep createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static OrchestrationStep getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static OrchestrationStep? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get name => $_getSZ(1); + @$pb.TagNumber(2) + set name($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasName() => $_has(1); + @$pb.TagNumber(2) + void clearName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get prompt => $_getSZ(2); + @$pb.TagNumber(3) + set prompt($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasPrompt() => $_has(2); + @$pb.TagNumber(3) + void clearPrompt() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get model => $_getSZ(3); + @$pb.TagNumber(4) + set model($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasModel() => $_has(3); + @$pb.TagNumber(4) + void clearModel() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get workingDirectory => $_getSZ(4); + @$pb.TagNumber(5) + set workingDirectory($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasWorkingDirectory() => $_has(4); + @$pb.TagNumber(5) + void clearWorkingDirectory() => $_clearField(5); + + @$pb.TagNumber(6) + $pb.PbList<$core.String> get allowedTools => $_getList(5); + + @$pb.TagNumber(7) + $pb.PbList<$core.String> get dependsOn => $_getList(6); + + @$pb.TagNumber(8) + $core.int get maxTurns => $_getIZ(7); + @$pb.TagNumber(8) + set maxTurns($core.int value) => $_setSignedInt32(7, value); + @$pb.TagNumber(8) + $core.bool hasMaxTurns() => $_has(7); + @$pb.TagNumber(8) + void clearMaxTurns() => $_clearField(8); + + @$pb.TagNumber(9) + $core.bool get autoApprove => $_getBF(8); + @$pb.TagNumber(9) + set autoApprove($core.bool value) => $_setBool(8, value); + @$pb.TagNumber(9) + $core.bool hasAutoApprove() => $_has(8); + @$pb.TagNumber(9) + void clearAutoApprove() => $_clearField(9); +} + +/// WatchOrchestrationRequest subscribes to orchestration progress events. +class WatchOrchestrationRequest extends $pb.GeneratedMessage { + factory WatchOrchestrationRequest({ + $core.String? orchestrationId, + }) { + final result = create(); + if (orchestrationId != null) result.orchestrationId = orchestrationId; + return result; + } + + WatchOrchestrationRequest._(); + + factory WatchOrchestrationRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory WatchOrchestrationRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'WatchOrchestrationRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'orchestrationId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchOrchestrationRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + WatchOrchestrationRequest copyWith( + void Function(WatchOrchestrationRequest) updates) => + super.copyWith((message) => updates(message as WatchOrchestrationRequest)) + as WatchOrchestrationRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static WatchOrchestrationRequest create() => WatchOrchestrationRequest._(); + @$core.override + WatchOrchestrationRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static WatchOrchestrationRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static WatchOrchestrationRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get orchestrationId => $_getSZ(0); + @$pb.TagNumber(1) + set orchestrationId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasOrchestrationId() => $_has(0); + @$pb.TagNumber(1) + void clearOrchestrationId() => $_clearField(1); +} + +enum OrchestrationEvent_Event { + stepStarted, + stepCompleted, + stepFailed, + completed, + failed, + notSet +} + +/// OrchestrationEvent wraps orchestration progress events. +class OrchestrationEvent extends $pb.GeneratedMessage { + factory OrchestrationEvent({ + $core.String? orchestrationId, + $1.Timestamp? timestamp, + StepStarted? stepStarted, + StepCompleted? stepCompleted, + StepFailed? stepFailed, + OrchestrationCompleted? completed, + OrchestrationFailed? failed, + }) { + final result = create(); + if (orchestrationId != null) result.orchestrationId = orchestrationId; + if (timestamp != null) result.timestamp = timestamp; + if (stepStarted != null) result.stepStarted = stepStarted; + if (stepCompleted != null) result.stepCompleted = stepCompleted; + if (stepFailed != null) result.stepFailed = stepFailed; + if (completed != null) result.completed = completed; + if (failed != null) result.failed = failed; + return result; + } + + OrchestrationEvent._(); + + factory OrchestrationEvent.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory OrchestrationEvent.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static const $core.Map<$core.int, OrchestrationEvent_Event> + _OrchestrationEvent_EventByTag = { + 3: OrchestrationEvent_Event.stepStarted, + 4: OrchestrationEvent_Event.stepCompleted, + 5: OrchestrationEvent_Event.stepFailed, + 6: OrchestrationEvent_Event.completed, + 7: OrchestrationEvent_Event.failed, + 0: OrchestrationEvent_Event.notSet + }; + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'OrchestrationEvent', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..oo(0, [3, 4, 5, 6, 7]) + ..aOS(1, _omitFieldNames ? '' : 'orchestrationId') + ..aOM<$1.Timestamp>(2, _omitFieldNames ? '' : 'timestamp', + subBuilder: $1.Timestamp.create) + ..aOM(3, _omitFieldNames ? '' : 'stepStarted', + subBuilder: StepStarted.create) + ..aOM(4, _omitFieldNames ? '' : 'stepCompleted', + subBuilder: StepCompleted.create) + ..aOM(5, _omitFieldNames ? '' : 'stepFailed', + subBuilder: StepFailed.create) + ..aOM(6, _omitFieldNames ? '' : 'completed', + subBuilder: OrchestrationCompleted.create) + ..aOM(7, _omitFieldNames ? '' : 'failed', + subBuilder: OrchestrationFailed.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationEvent clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationEvent copyWith(void Function(OrchestrationEvent) updates) => + super.copyWith((message) => updates(message as OrchestrationEvent)) + as OrchestrationEvent; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static OrchestrationEvent create() => OrchestrationEvent._(); + @$core.override + OrchestrationEvent createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static OrchestrationEvent getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static OrchestrationEvent? _defaultInstance; + + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + OrchestrationEvent_Event whichEvent() => + _OrchestrationEvent_EventByTag[$_whichOneof(0)]!; + @$pb.TagNumber(3) + @$pb.TagNumber(4) + @$pb.TagNumber(5) + @$pb.TagNumber(6) + @$pb.TagNumber(7) + void clearEvent() => $_clearField($_whichOneof(0)); + + @$pb.TagNumber(1) + $core.String get orchestrationId => $_getSZ(0); + @$pb.TagNumber(1) + set orchestrationId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasOrchestrationId() => $_has(0); + @$pb.TagNumber(1) + void clearOrchestrationId() => $_clearField(1); + + @$pb.TagNumber(2) + $1.Timestamp get timestamp => $_getN(1); + @$pb.TagNumber(2) + set timestamp($1.Timestamp value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasTimestamp() => $_has(1); + @$pb.TagNumber(2) + void clearTimestamp() => $_clearField(2); + @$pb.TagNumber(2) + $1.Timestamp ensureTimestamp() => $_ensure(1); + + @$pb.TagNumber(3) + StepStarted get stepStarted => $_getN(2); + @$pb.TagNumber(3) + set stepStarted(StepStarted value) => $_setField(3, value); + @$pb.TagNumber(3) + $core.bool hasStepStarted() => $_has(2); + @$pb.TagNumber(3) + void clearStepStarted() => $_clearField(3); + @$pb.TagNumber(3) + StepStarted ensureStepStarted() => $_ensure(2); + + @$pb.TagNumber(4) + StepCompleted get stepCompleted => $_getN(3); + @$pb.TagNumber(4) + set stepCompleted(StepCompleted value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasStepCompleted() => $_has(3); + @$pb.TagNumber(4) + void clearStepCompleted() => $_clearField(4); + @$pb.TagNumber(4) + StepCompleted ensureStepCompleted() => $_ensure(3); + + @$pb.TagNumber(5) + StepFailed get stepFailed => $_getN(4); + @$pb.TagNumber(5) + set stepFailed(StepFailed value) => $_setField(5, value); + @$pb.TagNumber(5) + $core.bool hasStepFailed() => $_has(4); + @$pb.TagNumber(5) + void clearStepFailed() => $_clearField(5); + @$pb.TagNumber(5) + StepFailed ensureStepFailed() => $_ensure(4); + + @$pb.TagNumber(6) + OrchestrationCompleted get completed => $_getN(5); + @$pb.TagNumber(6) + set completed(OrchestrationCompleted value) => $_setField(6, value); + @$pb.TagNumber(6) + $core.bool hasCompleted() => $_has(5); + @$pb.TagNumber(6) + void clearCompleted() => $_clearField(6); + @$pb.TagNumber(6) + OrchestrationCompleted ensureCompleted() => $_ensure(5); + + @$pb.TagNumber(7) + OrchestrationFailed get failed => $_getN(6); + @$pb.TagNumber(7) + set failed(OrchestrationFailed value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasFailed() => $_has(6); + @$pb.TagNumber(7) + void clearFailed() => $_clearField(7); + @$pb.TagNumber(7) + OrchestrationFailed ensureFailed() => $_ensure(6); +} + +/// StepStarted indicates an orchestration step has begun. +class StepStarted extends $pb.GeneratedMessage { + factory StepStarted({ + $core.String? stepId, + $core.String? subagentId, + $core.String? name, + }) { + final result = create(); + if (stepId != null) result.stepId = stepId; + if (subagentId != null) result.subagentId = subagentId; + if (name != null) result.name = name; + return result; + } + + StepStarted._(); + + factory StepStarted.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory StepStarted.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'StepStarted', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'stepId') + ..aOS(2, _omitFieldNames ? '' : 'subagentId') + ..aOS(3, _omitFieldNames ? '' : 'name') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepStarted clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepStarted copyWith(void Function(StepStarted) updates) => + super.copyWith((message) => updates(message as StepStarted)) + as StepStarted; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static StepStarted create() => StepStarted._(); + @$core.override + StepStarted createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static StepStarted getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static StepStarted? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get stepId => $_getSZ(0); + @$pb.TagNumber(1) + set stepId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasStepId() => $_has(0); + @$pb.TagNumber(1) + void clearStepId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get subagentId => $_getSZ(1); + @$pb.TagNumber(2) + set subagentId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasSubagentId() => $_has(1); + @$pb.TagNumber(2) + void clearSubagentId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get name => $_getSZ(2); + @$pb.TagNumber(3) + set name($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasName() => $_has(2); + @$pb.TagNumber(3) + void clearName() => $_clearField(3); +} + +/// StepCompleted indicates a step finished successfully. +class StepCompleted extends $pb.GeneratedMessage { + factory StepCompleted({ + $core.String? stepId, + $core.String? resultSummary, + $core.int? completedCount, + $core.int? totalCount, + }) { + final result = create(); + if (stepId != null) result.stepId = stepId; + if (resultSummary != null) result.resultSummary = resultSummary; + if (completedCount != null) result.completedCount = completedCount; + if (totalCount != null) result.totalCount = totalCount; + return result; + } + + StepCompleted._(); + + factory StepCompleted.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory StepCompleted.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'StepCompleted', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'stepId') + ..aOS(2, _omitFieldNames ? '' : 'resultSummary') + ..aI(3, _omitFieldNames ? '' : 'completedCount') + ..aI(4, _omitFieldNames ? '' : 'totalCount') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepCompleted clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepCompleted copyWith(void Function(StepCompleted) updates) => + super.copyWith((message) => updates(message as StepCompleted)) + as StepCompleted; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static StepCompleted create() => StepCompleted._(); + @$core.override + StepCompleted createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static StepCompleted getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static StepCompleted? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get stepId => $_getSZ(0); + @$pb.TagNumber(1) + set stepId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasStepId() => $_has(0); + @$pb.TagNumber(1) + void clearStepId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get resultSummary => $_getSZ(1); + @$pb.TagNumber(2) + set resultSummary($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasResultSummary() => $_has(1); + @$pb.TagNumber(2) + void clearResultSummary() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get completedCount => $_getIZ(2); + @$pb.TagNumber(3) + set completedCount($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasCompletedCount() => $_has(2); + @$pb.TagNumber(3) + void clearCompletedCount() => $_clearField(3); + + @$pb.TagNumber(4) + $core.int get totalCount => $_getIZ(3); + @$pb.TagNumber(4) + set totalCount($core.int value) => $_setSignedInt32(3, value); + @$pb.TagNumber(4) + $core.bool hasTotalCount() => $_has(3); + @$pb.TagNumber(4) + void clearTotalCount() => $_clearField(4); +} + +/// StepFailed indicates a step failed. +class StepFailed extends $pb.GeneratedMessage { + factory StepFailed({ + $core.String? stepId, + $core.String? errorMessage, + $core.Iterable<$core.String>? blockedSteps, + }) { + final result = create(); + if (stepId != null) result.stepId = stepId; + if (errorMessage != null) result.errorMessage = errorMessage; + if (blockedSteps != null) result.blockedSteps.addAll(blockedSteps); + return result; + } + + StepFailed._(); + + factory StepFailed.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory StepFailed.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'StepFailed', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'stepId') + ..aOS(2, _omitFieldNames ? '' : 'errorMessage') + ..pPS(3, _omitFieldNames ? '' : 'blockedSteps') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepFailed clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StepFailed copyWith(void Function(StepFailed) updates) => + super.copyWith((message) => updates(message as StepFailed)) as StepFailed; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static StepFailed create() => StepFailed._(); + @$core.override + StepFailed createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static StepFailed getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static StepFailed? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get stepId => $_getSZ(0); + @$pb.TagNumber(1) + set stepId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasStepId() => $_has(0); + @$pb.TagNumber(1) + void clearStepId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get errorMessage => $_getSZ(1); + @$pb.TagNumber(2) + set errorMessage($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasErrorMessage() => $_has(1); + @$pb.TagNumber(2) + void clearErrorMessage() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbList<$core.String> get blockedSteps => $_getList(2); +} + +/// OrchestrationCompleted indicates all steps finished. +class OrchestrationCompleted extends $pb.GeneratedMessage { + factory OrchestrationCompleted({ + $core.int? totalSteps, + $core.int? succeeded, + $core.int? failed, + }) { + final result = create(); + if (totalSteps != null) result.totalSteps = totalSteps; + if (succeeded != null) result.succeeded = succeeded; + if (failed != null) result.failed = failed; + return result; + } + + OrchestrationCompleted._(); + + factory OrchestrationCompleted.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory OrchestrationCompleted.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'OrchestrationCompleted', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'totalSteps') + ..aI(2, _omitFieldNames ? '' : 'succeeded') + ..aI(3, _omitFieldNames ? '' : 'failed') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationCompleted clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationCompleted copyWith( + void Function(OrchestrationCompleted) updates) => + super.copyWith((message) => updates(message as OrchestrationCompleted)) + as OrchestrationCompleted; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static OrchestrationCompleted create() => OrchestrationCompleted._(); + @$core.override + OrchestrationCompleted createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static OrchestrationCompleted getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static OrchestrationCompleted? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get totalSteps => $_getIZ(0); + @$pb.TagNumber(1) + set totalSteps($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasTotalSteps() => $_has(0); + @$pb.TagNumber(1) + void clearTotalSteps() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get succeeded => $_getIZ(1); + @$pb.TagNumber(2) + set succeeded($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasSucceeded() => $_has(1); + @$pb.TagNumber(2) + void clearSucceeded() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get failed => $_getIZ(2); + @$pb.TagNumber(3) + set failed($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasFailed() => $_has(2); + @$pb.TagNumber(3) + void clearFailed() => $_clearField(3); +} + +/// OrchestrationFailed indicates the orchestration could not complete. +class OrchestrationFailed extends $pb.GeneratedMessage { + factory OrchestrationFailed({ + $core.String? errorMessage, + $core.int? completedSteps, + $core.int? failedSteps, + }) { + final result = create(); + if (errorMessage != null) result.errorMessage = errorMessage; + if (completedSteps != null) result.completedSteps = completedSteps; + if (failedSteps != null) result.failedSteps = failedSteps; + return result; + } + + OrchestrationFailed._(); + + factory OrchestrationFailed.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory OrchestrationFailed.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'OrchestrationFailed', + package: const $pb.PackageName(_omitMessageNames ? '' : 'betcode.v1'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'errorMessage') + ..aI(2, _omitFieldNames ? '' : 'completedSteps') + ..aI(3, _omitFieldNames ? '' : 'failedSteps') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationFailed clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + OrchestrationFailed copyWith(void Function(OrchestrationFailed) updates) => + super.copyWith((message) => updates(message as OrchestrationFailed)) + as OrchestrationFailed; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static OrchestrationFailed create() => OrchestrationFailed._(); + @$core.override + OrchestrationFailed createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static OrchestrationFailed getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static OrchestrationFailed? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get errorMessage => $_getSZ(0); + @$pb.TagNumber(1) + set errorMessage($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasErrorMessage() => $_has(0); + @$pb.TagNumber(1) + void clearErrorMessage() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get completedSteps => $_getIZ(1); + @$pb.TagNumber(2) + set completedSteps($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasCompletedSteps() => $_has(1); + @$pb.TagNumber(2) + void clearCompletedSteps() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get failedSteps => $_getIZ(2); + @$pb.TagNumber(3) + set failedSteps($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasFailedSteps() => $_has(2); + @$pb.TagNumber(3) + void clearFailedSteps() => $_clearField(3); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/generated/betcode/v1/subagent.pbenum.dart b/lib/generated/betcode/v1/subagent.pbenum.dart new file mode 100644 index 0000000..a3c88bf --- /dev/null +++ b/lib/generated/betcode/v1/subagent.pbenum.dart @@ -0,0 +1,81 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/subagent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +/// SubagentStatus represents a subagent's lifecycle state. +class SubagentStatus extends $pb.ProtobufEnum { + static const SubagentStatus SUBAGENT_STATUS_UNSPECIFIED = + SubagentStatus._(0, _omitEnumNames ? '' : 'SUBAGENT_STATUS_UNSPECIFIED'); + static const SubagentStatus SUBAGENT_STATUS_PENDING = + SubagentStatus._(1, _omitEnumNames ? '' : 'SUBAGENT_STATUS_PENDING'); + static const SubagentStatus SUBAGENT_STATUS_RUNNING = + SubagentStatus._(2, _omitEnumNames ? '' : 'SUBAGENT_STATUS_RUNNING'); + static const SubagentStatus SUBAGENT_STATUS_COMPLETED = + SubagentStatus._(3, _omitEnumNames ? '' : 'SUBAGENT_STATUS_COMPLETED'); + static const SubagentStatus SUBAGENT_STATUS_FAILED = + SubagentStatus._(4, _omitEnumNames ? '' : 'SUBAGENT_STATUS_FAILED'); + static const SubagentStatus SUBAGENT_STATUS_CANCELLED = + SubagentStatus._(5, _omitEnumNames ? '' : 'SUBAGENT_STATUS_CANCELLED'); + + static const $core.List values = [ + SUBAGENT_STATUS_UNSPECIFIED, + SUBAGENT_STATUS_PENDING, + SUBAGENT_STATUS_RUNNING, + SUBAGENT_STATUS_COMPLETED, + SUBAGENT_STATUS_FAILED, + SUBAGENT_STATUS_CANCELLED, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 5); + static SubagentStatus? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const SubagentStatus._(super.value, super.name); +} + +/// OrchestrationStrategy defines how orchestration steps are executed. +class OrchestrationStrategy extends $pb.ProtobufEnum { + static const OrchestrationStrategy ORCHESTRATION_STRATEGY_UNSPECIFIED = + OrchestrationStrategy._( + 0, _omitEnumNames ? '' : 'ORCHESTRATION_STRATEGY_UNSPECIFIED'); + static const OrchestrationStrategy ORCHESTRATION_STRATEGY_PARALLEL = + OrchestrationStrategy._( + 1, _omitEnumNames ? '' : 'ORCHESTRATION_STRATEGY_PARALLEL'); + static const OrchestrationStrategy ORCHESTRATION_STRATEGY_SEQUENTIAL = + OrchestrationStrategy._( + 2, _omitEnumNames ? '' : 'ORCHESTRATION_STRATEGY_SEQUENTIAL'); + static const OrchestrationStrategy ORCHESTRATION_STRATEGY_DAG = + OrchestrationStrategy._( + 3, _omitEnumNames ? '' : 'ORCHESTRATION_STRATEGY_DAG'); + + static const $core.List values = + [ + ORCHESTRATION_STRATEGY_UNSPECIFIED, + ORCHESTRATION_STRATEGY_PARALLEL, + ORCHESTRATION_STRATEGY_SEQUENTIAL, + ORCHESTRATION_STRATEGY_DAG, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 3); + static OrchestrationStrategy? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const OrchestrationStrategy._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/generated/betcode/v1/subagent.pbgrpc.dart b/lib/generated/betcode/v1/subagent.pbgrpc.dart new file mode 100644 index 0000000..45c65f1 --- /dev/null +++ b/lib/generated/betcode/v1/subagent.pbgrpc.dart @@ -0,0 +1,298 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/subagent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'subagent.pb.dart' as $0; + +export 'subagent.pb.dart'; + +/// SubagentService provides daemon-orchestrated subagent management. +/// Unlike Level 1 (Claude-internal Task tool), Level 2 subagents are +/// independent Claude Code subprocesses managed by the daemon. +@$pb.GrpcServiceName('betcode.v1.SubagentService') +class SubagentServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + SubagentServiceClient(super.channel, {super.options, super.interceptors}); + + /// Spawn a new subagent subprocess under a parent session. + $grpc.ResponseFuture<$0.SpawnSubagentResponse> spawnSubagent( + $0.SpawnSubagentRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$spawnSubagent, request, options: options); + } + + /// Watch a subagent's event stream. + $grpc.ResponseStream<$0.SubagentEvent> watchSubagent( + $0.WatchSubagentRequest request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall( + _$watchSubagent, $async.Stream.fromIterable([request]), + options: options); + } + + /// Send input to a running subagent. + $grpc.ResponseFuture<$0.SendToSubagentResponse> sendToSubagent( + $0.SendToSubagentRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$sendToSubagent, request, options: options); + } + + /// Cancel a running subagent. + $grpc.ResponseFuture<$0.CancelSubagentResponse> cancelSubagent( + $0.CancelSubagentRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$cancelSubagent, request, options: options); + } + + /// List subagents for a parent session. + $grpc.ResponseFuture<$0.ListSubagentsResponse> listSubagents( + $0.ListSubagentsRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$listSubagents, request, options: options); + } + + /// Create a multi-step orchestration (parallel, sequential, or DAG). + $grpc.ResponseFuture<$0.CreateOrchestrationResponse> createOrchestration( + $0.CreateOrchestrationRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$createOrchestration, request, options: options); + } + + /// Watch orchestration progress events. + $grpc.ResponseStream<$0.OrchestrationEvent> watchOrchestration( + $0.WatchOrchestrationRequest request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall( + _$watchOrchestration, $async.Stream.fromIterable([request]), + options: options); + } + + /// Revoke auto-approve permissions on a running subagent. + $grpc.ResponseFuture<$0.RevokeAutoApproveResponse> revokeAutoApprove( + $0.RevokeAutoApproveRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$revokeAutoApprove, request, options: options); + } + + // method descriptors + + static final _$spawnSubagent = + $grpc.ClientMethod<$0.SpawnSubagentRequest, $0.SpawnSubagentResponse>( + '/betcode.v1.SubagentService/SpawnSubagent', + ($0.SpawnSubagentRequest value) => value.writeToBuffer(), + $0.SpawnSubagentResponse.fromBuffer); + static final _$watchSubagent = + $grpc.ClientMethod<$0.WatchSubagentRequest, $0.SubagentEvent>( + '/betcode.v1.SubagentService/WatchSubagent', + ($0.WatchSubagentRequest value) => value.writeToBuffer(), + $0.SubagentEvent.fromBuffer); + static final _$sendToSubagent = + $grpc.ClientMethod<$0.SendToSubagentRequest, $0.SendToSubagentResponse>( + '/betcode.v1.SubagentService/SendToSubagent', + ($0.SendToSubagentRequest value) => value.writeToBuffer(), + $0.SendToSubagentResponse.fromBuffer); + static final _$cancelSubagent = + $grpc.ClientMethod<$0.CancelSubagentRequest, $0.CancelSubagentResponse>( + '/betcode.v1.SubagentService/CancelSubagent', + ($0.CancelSubagentRequest value) => value.writeToBuffer(), + $0.CancelSubagentResponse.fromBuffer); + static final _$listSubagents = + $grpc.ClientMethod<$0.ListSubagentsRequest, $0.ListSubagentsResponse>( + '/betcode.v1.SubagentService/ListSubagents', + ($0.ListSubagentsRequest value) => value.writeToBuffer(), + $0.ListSubagentsResponse.fromBuffer); + static final _$createOrchestration = $grpc.ClientMethod< + $0.CreateOrchestrationRequest, $0.CreateOrchestrationResponse>( + '/betcode.v1.SubagentService/CreateOrchestration', + ($0.CreateOrchestrationRequest value) => value.writeToBuffer(), + $0.CreateOrchestrationResponse.fromBuffer); + static final _$watchOrchestration = + $grpc.ClientMethod<$0.WatchOrchestrationRequest, $0.OrchestrationEvent>( + '/betcode.v1.SubagentService/WatchOrchestration', + ($0.WatchOrchestrationRequest value) => value.writeToBuffer(), + $0.OrchestrationEvent.fromBuffer); + static final _$revokeAutoApprove = $grpc.ClientMethod< + $0.RevokeAutoApproveRequest, $0.RevokeAutoApproveResponse>( + '/betcode.v1.SubagentService/RevokeAutoApprove', + ($0.RevokeAutoApproveRequest value) => value.writeToBuffer(), + $0.RevokeAutoApproveResponse.fromBuffer); +} + +@$pb.GrpcServiceName('betcode.v1.SubagentService') +abstract class SubagentServiceBase extends $grpc.Service { + $core.String get $name => 'betcode.v1.SubagentService'; + + SubagentServiceBase() { + $addMethod( + $grpc.ServiceMethod<$0.SpawnSubagentRequest, $0.SpawnSubagentResponse>( + 'SpawnSubagent', + spawnSubagent_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.SpawnSubagentRequest.fromBuffer(value), + ($0.SpawnSubagentResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.WatchSubagentRequest, $0.SubagentEvent>( + 'WatchSubagent', + watchSubagent_Pre, + false, + true, + ($core.List<$core.int> value) => + $0.WatchSubagentRequest.fromBuffer(value), + ($0.SubagentEvent value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.SendToSubagentRequest, + $0.SendToSubagentResponse>( + 'SendToSubagent', + sendToSubagent_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.SendToSubagentRequest.fromBuffer(value), + ($0.SendToSubagentResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.CancelSubagentRequest, + $0.CancelSubagentResponse>( + 'CancelSubagent', + cancelSubagent_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.CancelSubagentRequest.fromBuffer(value), + ($0.CancelSubagentResponse value) => value.writeToBuffer())); + $addMethod( + $grpc.ServiceMethod<$0.ListSubagentsRequest, $0.ListSubagentsResponse>( + 'ListSubagents', + listSubagents_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.ListSubagentsRequest.fromBuffer(value), + ($0.ListSubagentsResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.CreateOrchestrationRequest, + $0.CreateOrchestrationResponse>( + 'CreateOrchestration', + createOrchestration_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.CreateOrchestrationRequest.fromBuffer(value), + ($0.CreateOrchestrationResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.WatchOrchestrationRequest, + $0.OrchestrationEvent>( + 'WatchOrchestration', + watchOrchestration_Pre, + false, + true, + ($core.List<$core.int> value) => + $0.WatchOrchestrationRequest.fromBuffer(value), + ($0.OrchestrationEvent value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.RevokeAutoApproveRequest, + $0.RevokeAutoApproveResponse>( + 'RevokeAutoApprove', + revokeAutoApprove_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.RevokeAutoApproveRequest.fromBuffer(value), + ($0.RevokeAutoApproveResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.SpawnSubagentResponse> spawnSubagent_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.SpawnSubagentRequest> $request) async { + return spawnSubagent($call, await $request); + } + + $async.Future<$0.SpawnSubagentResponse> spawnSubagent( + $grpc.ServiceCall call, $0.SpawnSubagentRequest request); + + $async.Stream<$0.SubagentEvent> watchSubagent_Pre($grpc.ServiceCall $call, + $async.Future<$0.WatchSubagentRequest> $request) async* { + yield* watchSubagent($call, await $request); + } + + $async.Stream<$0.SubagentEvent> watchSubagent( + $grpc.ServiceCall call, $0.WatchSubagentRequest request); + + $async.Future<$0.SendToSubagentResponse> sendToSubagent_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.SendToSubagentRequest> $request) async { + return sendToSubagent($call, await $request); + } + + $async.Future<$0.SendToSubagentResponse> sendToSubagent( + $grpc.ServiceCall call, $0.SendToSubagentRequest request); + + $async.Future<$0.CancelSubagentResponse> cancelSubagent_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.CancelSubagentRequest> $request) async { + return cancelSubagent($call, await $request); + } + + $async.Future<$0.CancelSubagentResponse> cancelSubagent( + $grpc.ServiceCall call, $0.CancelSubagentRequest request); + + $async.Future<$0.ListSubagentsResponse> listSubagents_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.ListSubagentsRequest> $request) async { + return listSubagents($call, await $request); + } + + $async.Future<$0.ListSubagentsResponse> listSubagents( + $grpc.ServiceCall call, $0.ListSubagentsRequest request); + + $async.Future<$0.CreateOrchestrationResponse> createOrchestration_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.CreateOrchestrationRequest> $request) async { + return createOrchestration($call, await $request); + } + + $async.Future<$0.CreateOrchestrationResponse> createOrchestration( + $grpc.ServiceCall call, $0.CreateOrchestrationRequest request); + + $async.Stream<$0.OrchestrationEvent> watchOrchestration_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.WatchOrchestrationRequest> $request) async* { + yield* watchOrchestration($call, await $request); + } + + $async.Stream<$0.OrchestrationEvent> watchOrchestration( + $grpc.ServiceCall call, $0.WatchOrchestrationRequest request); + + $async.Future<$0.RevokeAutoApproveResponse> revokeAutoApprove_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.RevokeAutoApproveRequest> $request) async { + return revokeAutoApprove($call, await $request); + } + + $async.Future<$0.RevokeAutoApproveResponse> revokeAutoApprove( + $grpc.ServiceCall call, $0.RevokeAutoApproveRequest request); +} diff --git a/lib/generated/betcode/v1/subagent.pbjson.dart b/lib/generated/betcode/v1/subagent.pbjson.dart new file mode 100644 index 0000000..d80554e --- /dev/null +++ b/lib/generated/betcode/v1/subagent.pbjson.dart @@ -0,0 +1,785 @@ +// This is a generated file - do not edit. +// +// Generated from betcode/v1/subagent.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use subagentStatusDescriptor instead') +const SubagentStatus$json = { + '1': 'SubagentStatus', + '2': [ + {'1': 'SUBAGENT_STATUS_UNSPECIFIED', '2': 0}, + {'1': 'SUBAGENT_STATUS_PENDING', '2': 1}, + {'1': 'SUBAGENT_STATUS_RUNNING', '2': 2}, + {'1': 'SUBAGENT_STATUS_COMPLETED', '2': 3}, + {'1': 'SUBAGENT_STATUS_FAILED', '2': 4}, + {'1': 'SUBAGENT_STATUS_CANCELLED', '2': 5}, + ], +}; + +/// Descriptor for `SubagentStatus`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List subagentStatusDescriptor = $convert.base64Decode( + 'Cg5TdWJhZ2VudFN0YXR1cxIfChtTVUJBR0VOVF9TVEFUVVNfVU5TUEVDSUZJRUQQABIbChdTVU' + 'JBR0VOVF9TVEFUVVNfUEVORElORxABEhsKF1NVQkFHRU5UX1NUQVRVU19SVU5OSU5HEAISHQoZ' + 'U1VCQUdFTlRfU1RBVFVTX0NPTVBMRVRFRBADEhoKFlNVQkFHRU5UX1NUQVRVU19GQUlMRUQQBB' + 'IdChlTVUJBR0VOVF9TVEFUVVNfQ0FOQ0VMTEVEEAU='); + +@$core.Deprecated('Use orchestrationStrategyDescriptor instead') +const OrchestrationStrategy$json = { + '1': 'OrchestrationStrategy', + '2': [ + {'1': 'ORCHESTRATION_STRATEGY_UNSPECIFIED', '2': 0}, + {'1': 'ORCHESTRATION_STRATEGY_PARALLEL', '2': 1}, + {'1': 'ORCHESTRATION_STRATEGY_SEQUENTIAL', '2': 2}, + {'1': 'ORCHESTRATION_STRATEGY_DAG', '2': 3}, + ], +}; + +/// Descriptor for `OrchestrationStrategy`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List orchestrationStrategyDescriptor = $convert.base64Decode( + 'ChVPcmNoZXN0cmF0aW9uU3RyYXRlZ3kSJgoiT1JDSEVTVFJBVElPTl9TVFJBVEVHWV9VTlNQRU' + 'NJRklFRBAAEiMKH09SQ0hFU1RSQVRJT05fU1RSQVRFR1lfUEFSQUxMRUwQARIlCiFPUkNIRVNU' + 'UkFUSU9OX1NUUkFURUdZX1NFUVVFTlRJQUwQAhIeChpPUkNIRVNUUkFUSU9OX1NUUkFURUdZX0' + 'RBRxAD'); + +@$core.Deprecated('Use spawnSubagentRequestDescriptor instead') +const SpawnSubagentRequest$json = { + '1': 'SpawnSubagentRequest', + '2': [ + {'1': 'parent_session_id', '3': 1, '4': 1, '5': 9, '10': 'parentSessionId'}, + {'1': 'prompt', '3': 2, '4': 1, '5': 9, '10': 'prompt'}, + {'1': 'model', '3': 3, '4': 1, '5': 9, '10': 'model'}, + { + '1': 'working_directory', + '3': 4, + '4': 1, + '5': 9, + '10': 'workingDirectory' + }, + {'1': 'allowed_tools', '3': 5, '4': 3, '5': 9, '10': 'allowedTools'}, + {'1': 'max_turns', '3': 6, '4': 1, '5': 5, '10': 'maxTurns'}, + { + '1': 'env', + '3': 7, + '4': 3, + '5': 11, + '6': '.betcode.v1.SpawnSubagentRequest.EnvEntry', + '10': 'env' + }, + {'1': 'name', '3': 8, '4': 1, '5': 9, '10': 'name'}, + {'1': 'auto_approve', '3': 9, '4': 1, '5': 8, '10': 'autoApprove'}, + ], + '3': [SpawnSubagentRequest_EnvEntry$json], +}; + +@$core.Deprecated('Use spawnSubagentRequestDescriptor instead') +const SpawnSubagentRequest_EnvEntry$json = { + '1': 'EnvEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + ], + '7': {'7': true}, +}; + +/// Descriptor for `SpawnSubagentRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List spawnSubagentRequestDescriptor = $convert.base64Decode( + 'ChRTcGF3blN1YmFnZW50UmVxdWVzdBIqChFwYXJlbnRfc2Vzc2lvbl9pZBgBIAEoCVIPcGFyZW' + '50U2Vzc2lvbklkEhYKBnByb21wdBgCIAEoCVIGcHJvbXB0EhQKBW1vZGVsGAMgASgJUgVtb2Rl' + 'bBIrChF3b3JraW5nX2RpcmVjdG9yeRgEIAEoCVIQd29ya2luZ0RpcmVjdG9yeRIjCg1hbGxvd2' + 'VkX3Rvb2xzGAUgAygJUgxhbGxvd2VkVG9vbHMSGwoJbWF4X3R1cm5zGAYgASgFUghtYXhUdXJu' + 'cxI7CgNlbnYYByADKAsyKS5iZXRjb2RlLnYxLlNwYXduU3ViYWdlbnRSZXF1ZXN0LkVudkVudH' + 'J5UgNlbnYSEgoEbmFtZRgIIAEoCVIEbmFtZRIhCgxhdXRvX2FwcHJvdmUYCSABKAhSC2F1dG9B' + 'cHByb3ZlGjYKCEVudkVudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhQKBXZhbHVlGAIgASgJUgV2YW' + 'x1ZToCOAE='); + +@$core.Deprecated('Use spawnSubagentResponseDescriptor instead') +const SpawnSubagentResponse$json = { + '1': 'SpawnSubagentResponse', + '2': [ + {'1': 'subagent_id', '3': 1, '4': 1, '5': 9, '10': 'subagentId'}, + {'1': 'session_id', '3': 2, '4': 1, '5': 9, '10': 'sessionId'}, + ], +}; + +/// Descriptor for `SpawnSubagentResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List spawnSubagentResponseDescriptor = $convert.base64Decode( + 'ChVTcGF3blN1YmFnZW50UmVzcG9uc2USHwoLc3ViYWdlbnRfaWQYASABKAlSCnN1YmFnZW50SW' + 'QSHQoKc2Vzc2lvbl9pZBgCIAEoCVIJc2Vzc2lvbklk'); + +@$core.Deprecated('Use watchSubagentRequestDescriptor instead') +const WatchSubagentRequest$json = { + '1': 'WatchSubagentRequest', + '2': [ + {'1': 'subagent_id', '3': 1, '4': 1, '5': 9, '10': 'subagentId'}, + {'1': 'from_sequence', '3': 2, '4': 1, '5': 4, '10': 'fromSequence'}, + ], +}; + +/// Descriptor for `WatchSubagentRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List watchSubagentRequestDescriptor = $convert.base64Decode( + 'ChRXYXRjaFN1YmFnZW50UmVxdWVzdBIfCgtzdWJhZ2VudF9pZBgBIAEoCVIKc3ViYWdlbnRJZB' + 'IjCg1mcm9tX3NlcXVlbmNlGAIgASgEUgxmcm9tU2VxdWVuY2U='); + +@$core.Deprecated('Use sendToSubagentRequestDescriptor instead') +const SendToSubagentRequest$json = { + '1': 'SendToSubagentRequest', + '2': [ + {'1': 'subagent_id', '3': 1, '4': 1, '5': 9, '10': 'subagentId'}, + {'1': 'content', '3': 2, '4': 1, '5': 9, '10': 'content'}, + ], +}; + +/// Descriptor for `SendToSubagentRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sendToSubagentRequestDescriptor = $convert.base64Decode( + 'ChVTZW5kVG9TdWJhZ2VudFJlcXVlc3QSHwoLc3ViYWdlbnRfaWQYASABKAlSCnN1YmFnZW50SW' + 'QSGAoHY29udGVudBgCIAEoCVIHY29udGVudA=='); + +@$core.Deprecated('Use sendToSubagentResponseDescriptor instead') +const SendToSubagentResponse$json = { + '1': 'SendToSubagentResponse', + '2': [ + {'1': 'delivered', '3': 1, '4': 1, '5': 8, '10': 'delivered'}, + ], +}; + +/// Descriptor for `SendToSubagentResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sendToSubagentResponseDescriptor = + $convert.base64Decode( + 'ChZTZW5kVG9TdWJhZ2VudFJlc3BvbnNlEhwKCWRlbGl2ZXJlZBgBIAEoCFIJZGVsaXZlcmVk'); + +@$core.Deprecated('Use cancelSubagentRequestDescriptor instead') +const CancelSubagentRequest$json = { + '1': 'CancelSubagentRequest', + '2': [ + {'1': 'subagent_id', '3': 1, '4': 1, '5': 9, '10': 'subagentId'}, + {'1': 'reason', '3': 2, '4': 1, '5': 9, '10': 'reason'}, + {'1': 'force', '3': 3, '4': 1, '5': 8, '10': 'force'}, + {'1': 'cleanup_worktree', '3': 4, '4': 1, '5': 8, '10': 'cleanupWorktree'}, + ], +}; + +/// Descriptor for `CancelSubagentRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List cancelSubagentRequestDescriptor = $convert.base64Decode( + 'ChVDYW5jZWxTdWJhZ2VudFJlcXVlc3QSHwoLc3ViYWdlbnRfaWQYASABKAlSCnN1YmFnZW50SW' + 'QSFgoGcmVhc29uGAIgASgJUgZyZWFzb24SFAoFZm9yY2UYAyABKAhSBWZvcmNlEikKEGNsZWFu' + 'dXBfd29ya3RyZWUYBCABKAhSD2NsZWFudXBXb3JrdHJlZQ=='); + +@$core.Deprecated('Use cancelSubagentResponseDescriptor instead') +const CancelSubagentResponse$json = { + '1': 'CancelSubagentResponse', + '2': [ + {'1': 'cancelled', '3': 1, '4': 1, '5': 8, '10': 'cancelled'}, + {'1': 'final_status', '3': 2, '4': 1, '5': 9, '10': 'finalStatus'}, + { + '1': 'tool_calls_executed', + '3': 3, + '4': 1, + '5': 5, + '10': 'toolCallsExecuted' + }, + { + '1': 'tool_calls_auto_approved', + '3': 4, + '4': 1, + '5': 5, + '10': 'toolCallsAutoApproved' + }, + ], +}; + +/// Descriptor for `CancelSubagentResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List cancelSubagentResponseDescriptor = $convert.base64Decode( + 'ChZDYW5jZWxTdWJhZ2VudFJlc3BvbnNlEhwKCWNhbmNlbGxlZBgBIAEoCFIJY2FuY2VsbGVkEi' + 'EKDGZpbmFsX3N0YXR1cxgCIAEoCVILZmluYWxTdGF0dXMSLgoTdG9vbF9jYWxsc19leGVjdXRl' + 'ZBgDIAEoBVIRdG9vbENhbGxzRXhlY3V0ZWQSNwoYdG9vbF9jYWxsc19hdXRvX2FwcHJvdmVkGA' + 'QgASgFUhV0b29sQ2FsbHNBdXRvQXBwcm92ZWQ='); + +@$core.Deprecated('Use listSubagentsRequestDescriptor instead') +const ListSubagentsRequest$json = { + '1': 'ListSubagentsRequest', + '2': [ + {'1': 'parent_session_id', '3': 1, '4': 1, '5': 9, '10': 'parentSessionId'}, + {'1': 'status_filter', '3': 2, '4': 1, '5': 9, '10': 'statusFilter'}, + ], +}; + +/// Descriptor for `ListSubagentsRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List listSubagentsRequestDescriptor = $convert.base64Decode( + 'ChRMaXN0U3ViYWdlbnRzUmVxdWVzdBIqChFwYXJlbnRfc2Vzc2lvbl9pZBgBIAEoCVIPcGFyZW' + '50U2Vzc2lvbklkEiMKDXN0YXR1c19maWx0ZXIYAiABKAlSDHN0YXR1c0ZpbHRlcg=='); + +@$core.Deprecated('Use listSubagentsResponseDescriptor instead') +const ListSubagentsResponse$json = { + '1': 'ListSubagentsResponse', + '2': [ + { + '1': 'subagents', + '3': 1, + '4': 3, + '5': 11, + '6': '.betcode.v1.SubagentInfo', + '10': 'subagents' + }, + ], +}; + +/// Descriptor for `ListSubagentsResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List listSubagentsResponseDescriptor = $convert.base64Decode( + 'ChVMaXN0U3ViYWdlbnRzUmVzcG9uc2USNgoJc3ViYWdlbnRzGAEgAygLMhguYmV0Y29kZS52MS' + '5TdWJhZ2VudEluZm9SCXN1YmFnZW50cw=='); + +@$core.Deprecated('Use subagentInfoDescriptor instead') +const SubagentInfo$json = { + '1': 'SubagentInfo', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + {'1': 'parent_session_id', '3': 2, '4': 1, '5': 9, '10': 'parentSessionId'}, + {'1': 'session_id', '3': 3, '4': 1, '5': 9, '10': 'sessionId'}, + {'1': 'name', '3': 4, '4': 1, '5': 9, '10': 'name'}, + {'1': 'prompt', '3': 5, '4': 1, '5': 9, '10': 'prompt'}, + {'1': 'model', '3': 6, '4': 1, '5': 9, '10': 'model'}, + { + '1': 'working_directory', + '3': 7, + '4': 1, + '5': 9, + '10': 'workingDirectory' + }, + { + '1': 'status', + '3': 8, + '4': 1, + '5': 14, + '6': '.betcode.v1.SubagentStatus', + '10': 'status' + }, + {'1': 'auto_approve', '3': 9, '4': 1, '5': 8, '10': 'autoApprove'}, + {'1': 'max_turns', '3': 10, '4': 1, '5': 5, '10': 'maxTurns'}, + {'1': 'allowed_tools', '3': 11, '4': 3, '5': 9, '10': 'allowedTools'}, + {'1': 'result_summary', '3': 12, '4': 1, '5': 9, '10': 'resultSummary'}, + { + '1': 'created_at', + '3': 13, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'createdAt' + }, + { + '1': 'completed_at', + '3': 14, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'completedAt' + }, + ], +}; + +/// Descriptor for `SubagentInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentInfoDescriptor = $convert.base64Decode( + 'CgxTdWJhZ2VudEluZm8SDgoCaWQYASABKAlSAmlkEioKEXBhcmVudF9zZXNzaW9uX2lkGAIgAS' + 'gJUg9wYXJlbnRTZXNzaW9uSWQSHQoKc2Vzc2lvbl9pZBgDIAEoCVIJc2Vzc2lvbklkEhIKBG5h' + 'bWUYBCABKAlSBG5hbWUSFgoGcHJvbXB0GAUgASgJUgZwcm9tcHQSFAoFbW9kZWwYBiABKAlSBW' + '1vZGVsEisKEXdvcmtpbmdfZGlyZWN0b3J5GAcgASgJUhB3b3JraW5nRGlyZWN0b3J5EjIKBnN0' + 'YXR1cxgIIAEoDjIaLmJldGNvZGUudjEuU3ViYWdlbnRTdGF0dXNSBnN0YXR1cxIhCgxhdXRvX2' + 'FwcHJvdmUYCSABKAhSC2F1dG9BcHByb3ZlEhsKCW1heF90dXJucxgKIAEoBVIIbWF4VHVybnMS' + 'IwoNYWxsb3dlZF90b29scxgLIAMoCVIMYWxsb3dlZFRvb2xzEiUKDnJlc3VsdF9zdW1tYXJ5GA' + 'wgASgJUg1yZXN1bHRTdW1tYXJ5EjkKCmNyZWF0ZWRfYXQYDSABKAsyGi5nb29nbGUucHJvdG9i' + 'dWYuVGltZXN0YW1wUgljcmVhdGVkQXQSPQoMY29tcGxldGVkX2F0GA4gASgLMhouZ29vZ2xlLn' + 'Byb3RvYnVmLlRpbWVzdGFtcFILY29tcGxldGVkQXQ='); + +@$core.Deprecated('Use subagentEventDescriptor instead') +const SubagentEvent$json = { + '1': 'SubagentEvent', + '2': [ + {'1': 'subagent_id', '3': 1, '4': 1, '5': 9, '10': 'subagentId'}, + { + '1': 'timestamp', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'timestamp' + }, + { + '1': 'started', + '3': 3, + '4': 1, + '5': 11, + '6': '.betcode.v1.SubagentStarted', + '9': 0, + '10': 'started' + }, + { + '1': 'output', + '3': 4, + '4': 1, + '5': 11, + '6': '.betcode.v1.SubagentOutput', + '9': 0, + '10': 'output' + }, + { + '1': 'tool_use', + '3': 5, + '4': 1, + '5': 11, + '6': '.betcode.v1.SubagentToolUse', + '9': 0, + '10': 'toolUse' + }, + { + '1': 'permission_request', + '3': 6, + '4': 1, + '5': 11, + '6': '.betcode.v1.SubagentPermissionRequest', + '9': 0, + '10': 'permissionRequest' + }, + { + '1': 'completed', + '3': 7, + '4': 1, + '5': 11, + '6': '.betcode.v1.SubagentCompleted', + '9': 0, + '10': 'completed' + }, + { + '1': 'failed', + '3': 8, + '4': 1, + '5': 11, + '6': '.betcode.v1.SubagentFailed', + '9': 0, + '10': 'failed' + }, + { + '1': 'cancelled', + '3': 9, + '4': 1, + '5': 11, + '6': '.betcode.v1.SubagentCancelled', + '9': 0, + '10': 'cancelled' + }, + ], + '8': [ + {'1': 'event'}, + ], +}; + +/// Descriptor for `SubagentEvent`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentEventDescriptor = $convert.base64Decode( + 'Cg1TdWJhZ2VudEV2ZW50Eh8KC3N1YmFnZW50X2lkGAEgASgJUgpzdWJhZ2VudElkEjgKCXRpbW' + 'VzdGFtcBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBSCXRpbWVzdGFtcBI3Cgdz' + 'dGFydGVkGAMgASgLMhsuYmV0Y29kZS52MS5TdWJhZ2VudFN0YXJ0ZWRIAFIHc3RhcnRlZBI0Cg' + 'ZvdXRwdXQYBCABKAsyGi5iZXRjb2RlLnYxLlN1YmFnZW50T3V0cHV0SABSBm91dHB1dBI4Cgh0' + 'b29sX3VzZRgFIAEoCzIbLmJldGNvZGUudjEuU3ViYWdlbnRUb29sVXNlSABSB3Rvb2xVc2USVg' + 'oScGVybWlzc2lvbl9yZXF1ZXN0GAYgASgLMiUuYmV0Y29kZS52MS5TdWJhZ2VudFBlcm1pc3Np' + 'b25SZXF1ZXN0SABSEXBlcm1pc3Npb25SZXF1ZXN0Ej0KCWNvbXBsZXRlZBgHIAEoCzIdLmJldG' + 'NvZGUudjEuU3ViYWdlbnRDb21wbGV0ZWRIAFIJY29tcGxldGVkEjQKBmZhaWxlZBgIIAEoCzIa' + 'LmJldGNvZGUudjEuU3ViYWdlbnRGYWlsZWRIAFIGZmFpbGVkEj0KCWNhbmNlbGxlZBgJIAEoCz' + 'IdLmJldGNvZGUudjEuU3ViYWdlbnRDYW5jZWxsZWRIAFIJY2FuY2VsbGVkQgcKBWV2ZW50'); + +@$core.Deprecated('Use subagentStartedDescriptor instead') +const SubagentStarted$json = { + '1': 'SubagentStarted', + '2': [ + {'1': 'session_id', '3': 1, '4': 1, '5': 9, '10': 'sessionId'}, + {'1': 'model', '3': 2, '4': 1, '5': 9, '10': 'model'}, + ], +}; + +/// Descriptor for `SubagentStarted`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentStartedDescriptor = $convert.base64Decode( + 'Cg9TdWJhZ2VudFN0YXJ0ZWQSHQoKc2Vzc2lvbl9pZBgBIAEoCVIJc2Vzc2lvbklkEhQKBW1vZG' + 'VsGAIgASgJUgVtb2RlbA=='); + +@$core.Deprecated('Use subagentOutputDescriptor instead') +const SubagentOutput$json = { + '1': 'SubagentOutput', + '2': [ + {'1': 'text', '3': 1, '4': 1, '5': 9, '10': 'text'}, + {'1': 'is_complete', '3': 2, '4': 1, '5': 8, '10': 'isComplete'}, + ], +}; + +/// Descriptor for `SubagentOutput`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentOutputDescriptor = $convert.base64Decode( + 'Cg5TdWJhZ2VudE91dHB1dBISCgR0ZXh0GAEgASgJUgR0ZXh0Eh8KC2lzX2NvbXBsZXRlGAIgAS' + 'gIUgppc0NvbXBsZXRl'); + +@$core.Deprecated('Use subagentToolUseDescriptor instead') +const SubagentToolUse$json = { + '1': 'SubagentToolUse', + '2': [ + {'1': 'tool_id', '3': 1, '4': 1, '5': 9, '10': 'toolId'}, + {'1': 'tool_name', '3': 2, '4': 1, '5': 9, '10': 'toolName'}, + {'1': 'description', '3': 3, '4': 1, '5': 9, '10': 'description'}, + ], +}; + +/// Descriptor for `SubagentToolUse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentToolUseDescriptor = $convert.base64Decode( + 'Cg9TdWJhZ2VudFRvb2xVc2USFwoHdG9vbF9pZBgBIAEoCVIGdG9vbElkEhsKCXRvb2xfbmFtZR' + 'gCIAEoCVIIdG9vbE5hbWUSIAoLZGVzY3JpcHRpb24YAyABKAlSC2Rlc2NyaXB0aW9u'); + +@$core.Deprecated('Use subagentPermissionRequestDescriptor instead') +const SubagentPermissionRequest$json = { + '1': 'SubagentPermissionRequest', + '2': [ + {'1': 'request_id', '3': 1, '4': 1, '5': 9, '10': 'requestId'}, + {'1': 'tool_name', '3': 2, '4': 1, '5': 9, '10': 'toolName'}, + {'1': 'description', '3': 3, '4': 1, '5': 9, '10': 'description'}, + ], +}; + +/// Descriptor for `SubagentPermissionRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentPermissionRequestDescriptor = $convert.base64Decode( + 'ChlTdWJhZ2VudFBlcm1pc3Npb25SZXF1ZXN0Eh0KCnJlcXVlc3RfaWQYASABKAlSCXJlcXVlc3' + 'RJZBIbCgl0b29sX25hbWUYAiABKAlSCHRvb2xOYW1lEiAKC2Rlc2NyaXB0aW9uGAMgASgJUgtk' + 'ZXNjcmlwdGlvbg=='); + +@$core.Deprecated('Use subagentCompletedDescriptor instead') +const SubagentCompleted$json = { + '1': 'SubagentCompleted', + '2': [ + {'1': 'exit_code', '3': 1, '4': 1, '5': 5, '10': 'exitCode'}, + {'1': 'result_summary', '3': 2, '4': 1, '5': 9, '10': 'resultSummary'}, + ], +}; + +/// Descriptor for `SubagentCompleted`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentCompletedDescriptor = $convert.base64Decode( + 'ChFTdWJhZ2VudENvbXBsZXRlZBIbCglleGl0X2NvZGUYASABKAVSCGV4aXRDb2RlEiUKDnJlc3' + 'VsdF9zdW1tYXJ5GAIgASgJUg1yZXN1bHRTdW1tYXJ5'); + +@$core.Deprecated('Use subagentFailedDescriptor instead') +const SubagentFailed$json = { + '1': 'SubagentFailed', + '2': [ + {'1': 'exit_code', '3': 1, '4': 1, '5': 5, '10': 'exitCode'}, + {'1': 'error_message', '3': 2, '4': 1, '5': 9, '10': 'errorMessage'}, + ], +}; + +/// Descriptor for `SubagentFailed`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentFailedDescriptor = $convert.base64Decode( + 'Cg5TdWJhZ2VudEZhaWxlZBIbCglleGl0X2NvZGUYASABKAVSCGV4aXRDb2RlEiMKDWVycm9yX2' + '1lc3NhZ2UYAiABKAlSDGVycm9yTWVzc2FnZQ=='); + +@$core.Deprecated('Use subagentCancelledDescriptor instead') +const SubagentCancelled$json = { + '1': 'SubagentCancelled', + '2': [ + {'1': 'reason', '3': 1, '4': 1, '5': 9, '10': 'reason'}, + ], +}; + +/// Descriptor for `SubagentCancelled`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List subagentCancelledDescriptor = $convert.base64Decode( + 'ChFTdWJhZ2VudENhbmNlbGxlZBIWCgZyZWFzb24YASABKAlSBnJlYXNvbg=='); + +@$core.Deprecated('Use revokeAutoApproveRequestDescriptor instead') +const RevokeAutoApproveRequest$json = { + '1': 'RevokeAutoApproveRequest', + '2': [ + {'1': 'subagent_id', '3': 1, '4': 1, '5': 9, '10': 'subagentId'}, + {'1': 'reason', '3': 2, '4': 1, '5': 9, '10': 'reason'}, + { + '1': 'terminate_if_pending', + '3': 3, + '4': 1, + '5': 8, + '10': 'terminateIfPending' + }, + ], +}; + +/// Descriptor for `RevokeAutoApproveRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List revokeAutoApproveRequestDescriptor = $convert.base64Decode( + 'ChhSZXZva2VBdXRvQXBwcm92ZVJlcXVlc3QSHwoLc3ViYWdlbnRfaWQYASABKAlSCnN1YmFnZW' + '50SWQSFgoGcmVhc29uGAIgASgJUgZyZWFzb24SMAoUdGVybWluYXRlX2lmX3BlbmRpbmcYAyAB' + 'KAhSEnRlcm1pbmF0ZUlmUGVuZGluZw=='); + +@$core.Deprecated('Use revokeAutoApproveResponseDescriptor instead') +const RevokeAutoApproveResponse$json = { + '1': 'RevokeAutoApproveResponse', + '2': [ + {'1': 'revoked', '3': 1, '4': 1, '5': 8, '10': 'revoked'}, + { + '1': 'pending_tool_calls', + '3': 2, + '4': 1, + '5': 5, + '10': 'pendingToolCalls' + }, + {'1': 'subagent_status', '3': 3, '4': 1, '5': 9, '10': 'subagentStatus'}, + ], +}; + +/// Descriptor for `RevokeAutoApproveResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List revokeAutoApproveResponseDescriptor = $convert.base64Decode( + 'ChlSZXZva2VBdXRvQXBwcm92ZVJlc3BvbnNlEhgKB3Jldm9rZWQYASABKAhSB3Jldm9rZWQSLA' + 'oScGVuZGluZ190b29sX2NhbGxzGAIgASgFUhBwZW5kaW5nVG9vbENhbGxzEicKD3N1YmFnZW50' + 'X3N0YXR1cxgDIAEoCVIOc3ViYWdlbnRTdGF0dXM='); + +@$core.Deprecated('Use createOrchestrationRequestDescriptor instead') +const CreateOrchestrationRequest$json = { + '1': 'CreateOrchestrationRequest', + '2': [ + {'1': 'parent_session_id', '3': 1, '4': 1, '5': 9, '10': 'parentSessionId'}, + { + '1': 'steps', + '3': 2, + '4': 3, + '5': 11, + '6': '.betcode.v1.OrchestrationStep', + '10': 'steps' + }, + { + '1': 'strategy', + '3': 3, + '4': 1, + '5': 14, + '6': '.betcode.v1.OrchestrationStrategy', + '10': 'strategy' + }, + ], +}; + +/// Descriptor for `CreateOrchestrationRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List createOrchestrationRequestDescriptor = $convert.base64Decode( + 'ChpDcmVhdGVPcmNoZXN0cmF0aW9uUmVxdWVzdBIqChFwYXJlbnRfc2Vzc2lvbl9pZBgBIAEoCV' + 'IPcGFyZW50U2Vzc2lvbklkEjMKBXN0ZXBzGAIgAygLMh0uYmV0Y29kZS52MS5PcmNoZXN0cmF0' + 'aW9uU3RlcFIFc3RlcHMSPQoIc3RyYXRlZ3kYAyABKA4yIS5iZXRjb2RlLnYxLk9yY2hlc3RyYX' + 'Rpb25TdHJhdGVneVIIc3RyYXRlZ3k='); + +@$core.Deprecated('Use createOrchestrationResponseDescriptor instead') +const CreateOrchestrationResponse$json = { + '1': 'CreateOrchestrationResponse', + '2': [ + {'1': 'orchestration_id', '3': 1, '4': 1, '5': 9, '10': 'orchestrationId'}, + {'1': 'total_steps', '3': 2, '4': 1, '5': 5, '10': 'totalSteps'}, + ], +}; + +/// Descriptor for `CreateOrchestrationResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List createOrchestrationResponseDescriptor = + $convert.base64Decode( + 'ChtDcmVhdGVPcmNoZXN0cmF0aW9uUmVzcG9uc2USKQoQb3JjaGVzdHJhdGlvbl9pZBgBIAEoCV' + 'IPb3JjaGVzdHJhdGlvbklkEh8KC3RvdGFsX3N0ZXBzGAIgASgFUgp0b3RhbFN0ZXBz'); + +@$core.Deprecated('Use orchestrationStepDescriptor instead') +const OrchestrationStep$json = { + '1': 'OrchestrationStep', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, + {'1': 'prompt', '3': 3, '4': 1, '5': 9, '10': 'prompt'}, + {'1': 'model', '3': 4, '4': 1, '5': 9, '10': 'model'}, + { + '1': 'working_directory', + '3': 5, + '4': 1, + '5': 9, + '10': 'workingDirectory' + }, + {'1': 'allowed_tools', '3': 6, '4': 3, '5': 9, '10': 'allowedTools'}, + {'1': 'depends_on', '3': 7, '4': 3, '5': 9, '10': 'dependsOn'}, + {'1': 'max_turns', '3': 8, '4': 1, '5': 5, '10': 'maxTurns'}, + {'1': 'auto_approve', '3': 9, '4': 1, '5': 8, '10': 'autoApprove'}, + ], +}; + +/// Descriptor for `OrchestrationStep`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List orchestrationStepDescriptor = $convert.base64Decode( + 'ChFPcmNoZXN0cmF0aW9uU3RlcBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZR' + 'IWCgZwcm9tcHQYAyABKAlSBnByb21wdBIUCgVtb2RlbBgEIAEoCVIFbW9kZWwSKwoRd29ya2lu' + 'Z19kaXJlY3RvcnkYBSABKAlSEHdvcmtpbmdEaXJlY3RvcnkSIwoNYWxsb3dlZF90b29scxgGIA' + 'MoCVIMYWxsb3dlZFRvb2xzEh0KCmRlcGVuZHNfb24YByADKAlSCWRlcGVuZHNPbhIbCgltYXhf' + 'dHVybnMYCCABKAVSCG1heFR1cm5zEiEKDGF1dG9fYXBwcm92ZRgJIAEoCFILYXV0b0FwcHJvdm' + 'U='); + +@$core.Deprecated('Use watchOrchestrationRequestDescriptor instead') +const WatchOrchestrationRequest$json = { + '1': 'WatchOrchestrationRequest', + '2': [ + {'1': 'orchestration_id', '3': 1, '4': 1, '5': 9, '10': 'orchestrationId'}, + ], +}; + +/// Descriptor for `WatchOrchestrationRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List watchOrchestrationRequestDescriptor = + $convert.base64Decode( + 'ChlXYXRjaE9yY2hlc3RyYXRpb25SZXF1ZXN0EikKEG9yY2hlc3RyYXRpb25faWQYASABKAlSD2' + '9yY2hlc3RyYXRpb25JZA=='); + +@$core.Deprecated('Use orchestrationEventDescriptor instead') +const OrchestrationEvent$json = { + '1': 'OrchestrationEvent', + '2': [ + {'1': 'orchestration_id', '3': 1, '4': 1, '5': 9, '10': 'orchestrationId'}, + { + '1': 'timestamp', + '3': 2, + '4': 1, + '5': 11, + '6': '.google.protobuf.Timestamp', + '10': 'timestamp' + }, + { + '1': 'step_started', + '3': 3, + '4': 1, + '5': 11, + '6': '.betcode.v1.StepStarted', + '9': 0, + '10': 'stepStarted' + }, + { + '1': 'step_completed', + '3': 4, + '4': 1, + '5': 11, + '6': '.betcode.v1.StepCompleted', + '9': 0, + '10': 'stepCompleted' + }, + { + '1': 'step_failed', + '3': 5, + '4': 1, + '5': 11, + '6': '.betcode.v1.StepFailed', + '9': 0, + '10': 'stepFailed' + }, + { + '1': 'completed', + '3': 6, + '4': 1, + '5': 11, + '6': '.betcode.v1.OrchestrationCompleted', + '9': 0, + '10': 'completed' + }, + { + '1': 'failed', + '3': 7, + '4': 1, + '5': 11, + '6': '.betcode.v1.OrchestrationFailed', + '9': 0, + '10': 'failed' + }, + ], + '8': [ + {'1': 'event'}, + ], +}; + +/// Descriptor for `OrchestrationEvent`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List orchestrationEventDescriptor = $convert.base64Decode( + 'ChJPcmNoZXN0cmF0aW9uRXZlbnQSKQoQb3JjaGVzdHJhdGlvbl9pZBgBIAEoCVIPb3JjaGVzdH' + 'JhdGlvbklkEjgKCXRpbWVzdGFtcBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBS' + 'CXRpbWVzdGFtcBI8CgxzdGVwX3N0YXJ0ZWQYAyABKAsyFy5iZXRjb2RlLnYxLlN0ZXBTdGFydG' + 'VkSABSC3N0ZXBTdGFydGVkEkIKDnN0ZXBfY29tcGxldGVkGAQgASgLMhkuYmV0Y29kZS52MS5T' + 'dGVwQ29tcGxldGVkSABSDXN0ZXBDb21wbGV0ZWQSOQoLc3RlcF9mYWlsZWQYBSABKAsyFi5iZX' + 'Rjb2RlLnYxLlN0ZXBGYWlsZWRIAFIKc3RlcEZhaWxlZBJCCgljb21wbGV0ZWQYBiABKAsyIi5i' + 'ZXRjb2RlLnYxLk9yY2hlc3RyYXRpb25Db21wbGV0ZWRIAFIJY29tcGxldGVkEjkKBmZhaWxlZB' + 'gHIAEoCzIfLmJldGNvZGUudjEuT3JjaGVzdHJhdGlvbkZhaWxlZEgAUgZmYWlsZWRCBwoFZXZl' + 'bnQ='); + +@$core.Deprecated('Use stepStartedDescriptor instead') +const StepStarted$json = { + '1': 'StepStarted', + '2': [ + {'1': 'step_id', '3': 1, '4': 1, '5': 9, '10': 'stepId'}, + {'1': 'subagent_id', '3': 2, '4': 1, '5': 9, '10': 'subagentId'}, + {'1': 'name', '3': 3, '4': 1, '5': 9, '10': 'name'}, + ], +}; + +/// Descriptor for `StepStarted`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List stepStartedDescriptor = $convert.base64Decode( + 'CgtTdGVwU3RhcnRlZBIXCgdzdGVwX2lkGAEgASgJUgZzdGVwSWQSHwoLc3ViYWdlbnRfaWQYAi' + 'ABKAlSCnN1YmFnZW50SWQSEgoEbmFtZRgDIAEoCVIEbmFtZQ=='); + +@$core.Deprecated('Use stepCompletedDescriptor instead') +const StepCompleted$json = { + '1': 'StepCompleted', + '2': [ + {'1': 'step_id', '3': 1, '4': 1, '5': 9, '10': 'stepId'}, + {'1': 'result_summary', '3': 2, '4': 1, '5': 9, '10': 'resultSummary'}, + {'1': 'completed_count', '3': 3, '4': 1, '5': 5, '10': 'completedCount'}, + {'1': 'total_count', '3': 4, '4': 1, '5': 5, '10': 'totalCount'}, + ], +}; + +/// Descriptor for `StepCompleted`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List stepCompletedDescriptor = $convert.base64Decode( + 'Cg1TdGVwQ29tcGxldGVkEhcKB3N0ZXBfaWQYASABKAlSBnN0ZXBJZBIlCg5yZXN1bHRfc3VtbW' + 'FyeRgCIAEoCVINcmVzdWx0U3VtbWFyeRInCg9jb21wbGV0ZWRfY291bnQYAyABKAVSDmNvbXBs' + 'ZXRlZENvdW50Eh8KC3RvdGFsX2NvdW50GAQgASgFUgp0b3RhbENvdW50'); + +@$core.Deprecated('Use stepFailedDescriptor instead') +const StepFailed$json = { + '1': 'StepFailed', + '2': [ + {'1': 'step_id', '3': 1, '4': 1, '5': 9, '10': 'stepId'}, + {'1': 'error_message', '3': 2, '4': 1, '5': 9, '10': 'errorMessage'}, + {'1': 'blocked_steps', '3': 3, '4': 3, '5': 9, '10': 'blockedSteps'}, + ], +}; + +/// Descriptor for `StepFailed`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List stepFailedDescriptor = $convert.base64Decode( + 'CgpTdGVwRmFpbGVkEhcKB3N0ZXBfaWQYASABKAlSBnN0ZXBJZBIjCg1lcnJvcl9tZXNzYWdlGA' + 'IgASgJUgxlcnJvck1lc3NhZ2USIwoNYmxvY2tlZF9zdGVwcxgDIAMoCVIMYmxvY2tlZFN0ZXBz'); + +@$core.Deprecated('Use orchestrationCompletedDescriptor instead') +const OrchestrationCompleted$json = { + '1': 'OrchestrationCompleted', + '2': [ + {'1': 'total_steps', '3': 1, '4': 1, '5': 5, '10': 'totalSteps'}, + {'1': 'succeeded', '3': 2, '4': 1, '5': 5, '10': 'succeeded'}, + {'1': 'failed', '3': 3, '4': 1, '5': 5, '10': 'failed'}, + ], +}; + +/// Descriptor for `OrchestrationCompleted`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List orchestrationCompletedDescriptor = $convert.base64Decode( + 'ChZPcmNoZXN0cmF0aW9uQ29tcGxldGVkEh8KC3RvdGFsX3N0ZXBzGAEgASgFUgp0b3RhbFN0ZX' + 'BzEhwKCXN1Y2NlZWRlZBgCIAEoBVIJc3VjY2VlZGVkEhYKBmZhaWxlZBgDIAEoBVIGZmFpbGVk'); + +@$core.Deprecated('Use orchestrationFailedDescriptor instead') +const OrchestrationFailed$json = { + '1': 'OrchestrationFailed', + '2': [ + {'1': 'error_message', '3': 1, '4': 1, '5': 9, '10': 'errorMessage'}, + {'1': 'completed_steps', '3': 2, '4': 1, '5': 5, '10': 'completedSteps'}, + {'1': 'failed_steps', '3': 3, '4': 1, '5': 5, '10': 'failedSteps'}, + ], +}; + +/// Descriptor for `OrchestrationFailed`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List orchestrationFailedDescriptor = $convert.base64Decode( + 'ChNPcmNoZXN0cmF0aW9uRmFpbGVkEiMKDWVycm9yX21lc3NhZ2UYASABKAlSDGVycm9yTWVzc2' + 'FnZRInCg9jb21wbGV0ZWRfc3RlcHMYAiABKAVSDmNvbXBsZXRlZFN0ZXBzEiEKDGZhaWxlZF9z' + 'dGVwcxgDIAEoBVILZmFpbGVkU3RlcHM='); diff --git a/lib/main.dart b/lib/main.dart index b293db1..411746e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,10 @@ +import 'dart:async'; + import 'package:betcode_app/app.dart'; import 'package:betcode_app/core/auth/auth.dart'; import 'package:betcode_app/core/grpc/grpc_providers.dart'; import 'package:betcode_app/features/machines/notifiers/machines_providers.dart'; +import 'package:betcode_app/generated/betcode/v1/machine.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -28,8 +31,12 @@ Future main() async { // Load machines and auto-select the sole machine before the app renders, // so gRPC calls that require the x-machine-id header have it available. + // + // NOTE: container.read(provider.future) can hang with Riverpod 3 on a bare + // ProviderContainer (before runApp). Use container.listen to reliably await + // the async build result. try { - await container.read(machinesProvider.future); + await _awaitProvider>(container, machinesProvider); debugPrint('[main] Machines loaded'); } on Exception catch (e) { debugPrint('[main] Machines pre-load failed: $e'); @@ -40,3 +47,35 @@ Future main() async { UncontrolledProviderScope(container: container, child: const BetCodeApp()), ); } + +/// Awaits an [AsyncNotifierProvider]'s build by listening for state +/// transitions. +/// +/// Returns the data value when the provider transitions to [AsyncData], or +/// throws the error if it transitions to [AsyncError]. +Future _awaitProvider( + ProviderContainer container, + AsyncNotifierProvider provider, +) { + final completer = Completer(); + late final ProviderSubscription> sub; + sub = container.listen>( + provider, + (_, next) { + if (completer.isCompleted) return; + next.when( + data: (value) { + completer.complete(value); + sub.close(); + }, + error: (e, st) { + completer.completeError(e, st); + sub.close(); + }, + loading: () {}, + ); + }, + fireImmediately: true, + ); + return completer.future; +} diff --git a/lib/shared/widgets/connectivity_banner.dart b/lib/shared/widgets/connectivity_banner.dart new file mode 100644 index 0000000..0fc8ed5 --- /dev/null +++ b/lib/shared/widgets/connectivity_banner.dart @@ -0,0 +1,70 @@ +import 'package:betcode_app/core/grpc/connection_state.dart'; +import 'package:betcode_app/core/grpc/grpc_providers.dart'; +import 'package:betcode_app/core/sync/connectivity.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +/// A persistent banner shown at the top of the app when there are +/// connectivity issues. +/// +/// Watches [networkStatusProvider] and [connectionStatusProvider] to +/// determine what to display. Hidden (zero height) when everything is fine. +class ConnectivityBanner extends ConsumerWidget { + /// Creates a [ConnectivityBanner]. + const ConnectivityBanner({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final networkAsync = ref.watch(networkStatusProvider); + final connectionAsync = ref.watch(connectionStatusProvider); + + final isOffline = networkAsync.value == NetworkStatus.offline; + final connectionStatus = connectionAsync.value; + final isRelayDown = + !isOffline && + (connectionStatus == GrpcConnectionStatus.disconnected || + connectionStatus == GrpcConnectionStatus.reconnecting); + + final String? message; + final Color? backgroundColor; + final IconData? icon; + + if (isOffline) { + message = 'No internet connection'; + backgroundColor = Theme.of(context).colorScheme.error; + icon = Icons.wifi_off; + } else if (isRelayDown) { + message = connectionStatus == GrpcConnectionStatus.reconnecting + ? 'Relay unreachable \u2014 reconnecting...' + : 'Relay unreachable'; + backgroundColor = Theme.of(context).colorScheme.tertiary; + icon = Icons.cloud_off; + } else { + message = null; + backgroundColor = null; + icon = null; + } + + return AnimatedSize( + duration: const Duration(milliseconds: 200), + child: message != null + ? MaterialBanner( + content: Row( + children: [ + Icon(icon, color: Colors.white, size: 18), + const SizedBox(width: 8), + Expanded( + child: Text( + message, + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + backgroundColor: backgroundColor, + actions: const [SizedBox.shrink()], + ) + : const SizedBox.shrink(), + ); + } +} diff --git a/lib/shared/widgets/error_display.dart b/lib/shared/widgets/error_display.dart index 56371b6..2ca76e4 100644 --- a/lib/shared/widgets/error_display.dart +++ b/lib/shared/widgets/error_display.dart @@ -1,3 +1,4 @@ +import 'package:betcode_app/core/grpc/app_exceptions.dart'; import 'package:flutter/material.dart'; /// A widget that displays an error state with an icon, message, and an @@ -39,7 +40,9 @@ class ErrorDisplay extends StatelessWidget { Icon(Icons.error_outline, size: 48, color: theme.colorScheme.error), const SizedBox(height: 16), Text( - error.toString(), + error is AppException + ? (error as AppException).message + : error.toString(), textAlign: TextAlign.center, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.error, diff --git a/lib/shared/widgets/widgets.dart b/lib/shared/widgets/widgets.dart index 4a950de..a51bb1a 100644 --- a/lib/shared/widgets/widgets.dart +++ b/lib/shared/widgets/widgets.dart @@ -1,6 +1,7 @@ export 'async_list_scaffold.dart'; export 'confirm_dialog.dart'; export 'connection_indicator.dart'; +export 'connectivity_banner.dart'; export 'dialog_actions.dart'; export 'empty_state.dart'; export 'error_display.dart'; diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig index f022c34..df4c964 100644 --- a/macos/Flutter/Flutter-Debug.xcconfig +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig index f022c34..e79501e 100644 --- a/macos/Flutter/Flutter-Release.xcconfig +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -1 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 8ac2845..0000000 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Generated file. Do not edit. -// - -import FlutterMacOS -import Foundation - -import connectivity_plus -import flutter_secure_storage_darwin -import sqlite3_flutter_libs - -func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) - FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) - Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin")) -} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/proto b/proto index bba1736..0f96f86 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit bba17362027338edc582ae201e44f78d979ff550 +Subproject commit 0f96f865d26c97c2101f04287aa587989c765a99 diff --git a/test/core/grpc/app_exceptions_test.dart b/test/core/grpc/app_exceptions_test.dart new file mode 100644 index 0000000..2f5ceb7 --- /dev/null +++ b/test/core/grpc/app_exceptions_test.dart @@ -0,0 +1,84 @@ +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('AppException', () { + test('NetworkError carries message and cause', () { + final cause = Exception('socket closed'); + const message = 'connection lost'; + final error = NetworkError(message: message, cause: cause); + + expect(error.message, message); + expect(error.cause, cause); + expect(error.toString(), message); + }); + + test('NetworkError works without cause', () { + const error = NetworkError(message: 'timeout'); + + expect(error.message, 'timeout'); + expect(error.cause, isNull); + }); + + test('SessionNotFoundError includes sessionId', () { + const error = SessionNotFoundError( + message: 'not found', + sessionId: 'sess-42', + ); + + expect(error.sessionId, 'sess-42'); + expect(error.message, 'not found'); + expect(error.cause, isNull); + }); + + test('SessionNotFoundError works without sessionId', () { + const error = SessionNotFoundError(message: 'not found'); + + expect(error.sessionId, isNull); + expect(error.message, 'not found'); + }); + + test('all subtypes are AppException', () { + const exceptions = [ + NetworkError(message: 'a'), + RelayUnavailableError(message: 'b'), + SessionNotFoundError(message: 'c', sessionId: 's'), + SessionInvalidError(message: 'd'), + AuthExpiredError(message: 'e'), + PermissionDeniedError(message: 'f'), + ServerError(message: 'g'), + RateLimitError(message: 'h'), + ]; + + for (final e in exceptions) { + expect(e, isA()); + expect(e, isA()); + } + }); + + test('sealed switch is exhaustive', () { + const AppException error = NetworkError(message: 'test'); + + // If a subtype is added to the sealed class without updating this + // switch, the analyzer will report a missing-case warning, proving + // exhaustiveness. + final label = switch (error) { + NetworkError() => 'network', + RelayUnavailableError() => 'relay', + SessionNotFoundError() => 'session_not_found', + SessionInvalidError() => 'session_invalid', + AuthExpiredError() => 'auth', + PermissionDeniedError() => 'permission', + ServerError() => 'server', + RateLimitError() => 'rate_limit', + }; + + expect(label, 'network'); + }); + + test('toString returns message', () { + const error = ServerError(message: 'internal error'); + expect('$error', 'internal error'); + }); + }); +} diff --git a/test/core/grpc/error_mapping_interceptor_test.dart b/test/core/grpc/error_mapping_interceptor_test.dart new file mode 100644 index 0000000..5b66a52 --- /dev/null +++ b/test/core/grpc/error_mapping_interceptor_test.dart @@ -0,0 +1,158 @@ +import 'dart:async'; + +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:betcode_app/core/grpc/interceptors.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; + +import '../../helpers/fake_response_future.dart'; +import '../interceptor_test_helpers.dart'; + +void main() { + group('ErrorMappingInterceptor', () { + late ErrorMappingInterceptor interceptor; + + setUp(() { + interceptor = ErrorMappingInterceptor(); + }); + + // ---- Unary tests ---- + + test('passes through successful unary responses unchanged', () async { + final response = interceptor.interceptUnary( + testMethod('/test/Ok'), + 'request', + CallOptions(), + (method, request, options) => FakeResponseFuture.value('success'), + ); + expect(await response, 'success'); + }); + + test('maps GrpcError to AppException on unary failure', () async { + final response = interceptor.interceptUnary( + testMethod('/betcode.v1.AgentService/DeleteSession'), + 'request', + CallOptions(), + (method, request, options) => + FakeResponseFuture.error(const GrpcError.notFound()), + ); + await expectLater( + response, + throwsA(isA()), + ); + }); + + test('maps UNAVAILABLE to NetworkError on unary', () async { + final response = interceptor.interceptUnary( + testMethod('/test/Rpc'), + 'request', + CallOptions(), + (method, request, options) => + FakeResponseFuture.error(const GrpcError.unavailable()), + ); + await expectLater( + response, + throwsA(isA()), + ); + }); + + test('non-GrpcError passes through unmapped', () async { + final response = interceptor.interceptUnary( + testMethod('/test/Rpc'), + 'request', + CallOptions(), + (method, request, options) => + FakeResponseFuture.error(Exception('not grpc')), + ); + await expectLater( + response, + throwsA( + isA().having( + (e) => e.toString(), + 'toString', + contains('not grpc'), + ), + ), + ); + }); + + test('maps UNAUTHENTICATED to AuthExpiredError on unary', () async { + final response = interceptor.interceptUnary( + testMethod('/test/Rpc'), + 'request', + CallOptions(), + (method, request, options) => + FakeResponseFuture.error(const GrpcError.unauthenticated()), + ); + await expectLater( + response, + throwsA(isA()), + ); + }); + + // ---- Streaming tests ---- + + test('streaming: maps GrpcError to AppException', () async { + final response = interceptor.interceptStreaming( + testMethod('/betcode.v1.AgentService/Converse'), + const Stream.empty(), + CallOptions(), + (method, requests, options) => FakeInterceptorResponseStream( + Stream.error(const GrpcError.notFound()), + ), + ); + + final completer = Completer(); + response.listen( + (_) {}, + onError: (Object error) { + if (!completer.isCompleted) completer.complete(error); + }, + ); + final error = await completer.future; + expect(error, isA()); + }); + + test('streaming: non-GrpcError passes through unmapped', () async { + final response = interceptor.interceptStreaming( + testMethod('/test/Rpc'), + const Stream.empty(), + CallOptions(), + (method, requests, options) => FakeInterceptorResponseStream( + Stream.error(Exception('not grpc')), + ), + ); + + final completer = Completer(); + response.listen( + (_) {}, + onError: (Object error) { + if (!completer.isCompleted) completer.complete(error); + }, + ); + final error = await completer.future; + expect(error, isA()); + expect(error.toString(), contains('not grpc')); + }); + + test('streaming: successful values pass through unchanged', () async { + final response = interceptor.interceptStreaming( + testMethod('/test/Rpc'), + const Stream.empty(), + CallOptions(), + (method, requests, options) => FakeInterceptorResponseStream( + Stream.fromIterable(['a', 'b', 'c']), + ), + ); + + final values = []; + final completer = Completer(); + response.listen( + values.add, + onDone: completer.complete, + ); + await completer.future; + expect(values, ['a', 'b', 'c']); + }); + }); +} diff --git a/test/core/grpc/error_mapping_test.dart b/test/core/grpc/error_mapping_test.dart new file mode 100644 index 0000000..4988cd7 --- /dev/null +++ b/test/core/grpc/error_mapping_test.dart @@ -0,0 +1,143 @@ +import 'package:betcode_app/core/grpc/app_exceptions.dart'; +import 'package:betcode_app/core/grpc/error_mapping.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; + +void main() { + group('mapGrpcError', () { + test('UNAVAILABLE with handshake error -> RelayUnavailableError', () { + const grpcError = GrpcError.custom( + StatusCode.unavailable, + 'Error connecting: HandshakeException: WRONG_VERSION_NUMBER', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + expect(result.cause, grpcError); + }); + + test('UNAVAILABLE with channel shutting down -> NetworkError', () { + const grpcError = GrpcError.custom( + StatusCode.unavailable, + 'Channel shutting down.', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + }); + + test('UNAVAILABLE with TLS keyword -> RelayUnavailableError', () { + const grpcError = GrpcError.custom( + StatusCode.unavailable, + 'TLS handshake failed', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + }); + + test('UNAVAILABLE with CERTIFICATE keyword -> RelayUnavailableError', () { + const grpcError = GrpcError.custom( + StatusCode.unavailable, + 'CERTIFICATE_VERIFY_FAILED', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + }); + + test('UNAVAILABLE generic -> NetworkError', () { + const grpcError = GrpcError.custom( + StatusCode.unavailable, + 'Connection timed out', + ); + final result = mapGrpcError(grpcError); + expect(result, isA()); + }); + + test('NOT_FOUND on session RPC -> SessionNotFoundError', () { + const grpcError = GrpcError.notFound('session not found'); + final result = mapGrpcError( + grpcError, + method: '/betcode.v1.AgentService/DeleteSession', + ); + expect(result, isA()); + }); + + test('NOT_FOUND on Converse RPC -> SessionNotFoundError', () { + const grpcError = GrpcError.notFound('session not found'); + final result = mapGrpcError( + grpcError, + method: '/betcode.v1.AgentService/Converse', + ); + expect(result, isA()); + }); + + test('NOT_FOUND on non-session RPC -> ServerError', () { + const grpcError = GrpcError.notFound('machine not found'); + final result = mapGrpcError( + grpcError, + method: '/betcode.v1.MachineService/ListMachines', + ); + expect(result, isA()); + }); + + test('NOT_FOUND with no method -> ServerError', () { + const grpcError = GrpcError.notFound('not found'); + final result = mapGrpcError(grpcError); + expect(result, isA()); + }); + + test('UNAUTHENTICATED -> AuthExpiredError', () { + final result = mapGrpcError(const GrpcError.unauthenticated()); + expect(result, isA()); + }); + + test('PERMISSION_DENIED -> PermissionDeniedError', () { + final result = mapGrpcError( + const GrpcError.custom(StatusCode.permissionDenied, 'not owner'), + ); + expect(result, isA()); + }); + + test('RESOURCE_EXHAUSTED -> RateLimitError', () { + final result = mapGrpcError(const GrpcError.resourceExhausted()); + expect(result, isA()); + }); + + test('INVALID_ARGUMENT -> SessionInvalidError', () { + final result = mapGrpcError( + const GrpcError.custom(StatusCode.invalidArgument, 'name too long'), + ); + expect(result, isA()); + expect(result.message, 'name too long'); + }); + + test('FAILED_PRECONDITION -> SessionInvalidError', () { + final result = mapGrpcError( + const GrpcError.custom(StatusCode.failedPrecondition, 'session locked'), + ); + expect(result, isA()); + expect(result.message, 'session locked'); + }); + + test('INTERNAL -> ServerError', () { + final result = mapGrpcError(const GrpcError.internal()); + expect(result, isA()); + }); + + test('DEADLINE_EXCEEDED -> NetworkError', () { + final result = mapGrpcError(const GrpcError.deadlineExceeded()); + expect(result, isA()); + }); + + test('UNKNOWN -> ServerError', () { + final result = mapGrpcError( + const GrpcError.custom(StatusCode.unknown, 'something broke'), + ); + expect(result, isA()); + }); + + test('preserves original GrpcError as cause', () { + const grpcError = GrpcError.internal('db crashed'); + final result = mapGrpcError(grpcError); + expect(result.cause, grpcError); + }); + }); +} diff --git a/test/core/grpc/grpc_providers_test.dart b/test/core/grpc/grpc_providers_test.dart index 75f0df6..15a18d3 100644 --- a/test/core/grpc/grpc_providers_test.dart +++ b/test/core/grpc/grpc_providers_test.dart @@ -86,9 +86,33 @@ void main() { ); }); - test('has exactly four interceptors', () { + test('interceptor chain contains ErrorMappingInterceptor', () { final manager = container.read(grpcClientManagerProvider); - expect(manager.interceptors, hasLength(4)); + final interceptors = manager.interceptors; + + expect( + interceptors.whereType(), + hasLength(1), + reason: 'ErrorMappingInterceptor should be present in the chain', + ); + }); + + test('ErrorMappingInterceptor is last in the chain', () { + final manager = container.read(grpcClientManagerProvider); + final interceptors = manager.interceptors; + + expect( + interceptors.last, + isA(), + reason: + 'ErrorMappingInterceptor must be last so it wraps errors from ' + 'all preceding interceptors', + ); + }); + + test('has exactly five interceptors', () { + final manager = container.read(grpcClientManagerProvider); + expect(manager.interceptors, hasLength(5)); }); test('has a health check function configured', () { diff --git a/test/core/grpc/interceptors_test.dart b/test/core/grpc/interceptors_test.dart index af6540e..d84d70c 100644 --- a/test/core/grpc/interceptors_test.dart +++ b/test/core/grpc/interceptors_test.dart @@ -247,7 +247,7 @@ void main() { expect(captured, same(opts)); }); - test('returns invoker response stream', () { + test('returns invoker response stream wrapped for logging', () async { final interceptor = LoggingInterceptor(); final expected = FakeInterceptorResponseStream( Stream.fromIterable(['d']), @@ -258,7 +258,9 @@ void main() { CallOptions(), (m, r, o) => expected, ); - expect(result, same(expected)); + expect(result, isA>()); + expect(result, isNot(same(expected))); + expect(await result.toList(), ['d']); }); }); diff --git a/test/core/grpc/lifecycle_bridge_test.dart b/test/core/grpc/lifecycle_bridge_test.dart index 0b62b11..76cda74 100644 --- a/test/core/grpc/lifecycle_bridge_test.dart +++ b/test/core/grpc/lifecycle_bridge_test.dart @@ -3,6 +3,7 @@ import 'package:betcode_app/core/grpc/connection_state.dart'; import 'package:betcode_app/core/grpc/lifecycle_bridge.dart'; import 'package:fake_async/fake_async.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:grpc/grpc.dart'; void main() { late GrpcClientManager manager; @@ -184,5 +185,100 @@ void main() { bridge.onResumed(); expect(manager.isPaused, isFalse); }); + + test( + 'short background with failing health check triggers reconnect', + () async { + // Create a manager whose health check always fails, simulating a + // zombie channel (TCP died while the phone was asleep). + final failingManager = GrpcClientManager( + healthCheckFn: (_) async { + throw Exception('connection dead'); + }, + ); + final failingBridge = GrpcLifecycleBridge(failingManager); + addTearDown(() async { + failingBridge.dispose(); + await failingManager.dispose(); + }); + + await failingManager.connect('localhost', 50051); + expect(failingManager.status, GrpcConnectionStatus.connected); + + fakeAsync((async) { + // Short background (< 5 minutes) + failingBridge.onPaused(); + async.elapse(const Duration(minutes: 1)); + failingBridge.onResumed(); + // Flush the health check + reconnect futures + async.flushMicrotasks(); + + // Manager should have attempted to reconnect (status is connecting + // or connected depending on whether connect() to localhost succeeded) + expect( + failingManager.status, + anyOf( + GrpcConnectionStatus.connecting, + GrpcConnectionStatus.connected, + ), + ); + }); + }, + ); + + test( + 'short background with UNIMPLEMENTED health check does not reconnect', + () async { + // Simulate a relay that returns UNIMPLEMENTED for the Health service. + // The healthCheckFn (as configured in grpc_providers) catches + // UNIMPLEMENTED and returns normally — connection is alive. + final unimplManager = GrpcClientManager( + healthCheckFn: (_) async { + try { + throw const GrpcError.unimplemented(); + } on GrpcError catch (e) { + if (e.code == StatusCode.unimplemented) return; + rethrow; + } + }, + ); + final unimplBridge = GrpcLifecycleBridge(unimplManager); + addTearDown(() async { + unimplBridge.dispose(); + await unimplManager.dispose(); + }); + + await unimplManager.connect('localhost', 50051); + expect(unimplManager.status, GrpcConnectionStatus.connected); + + fakeAsync((async) { + unimplBridge.onPaused(); + async.elapse(const Duration(minutes: 1)); + unimplBridge.onResumed(); + async.flushMicrotasks(); + + // Channel should still be present — UNIMPLEMENTED means alive + expect(unimplManager.channelOrNull, isNotNull); + expect(unimplManager.status, GrpcConnectionStatus.connected); + }); + }, + ); + + test('short background with no health check skips verification', () async { + // Default manager has no health check + await manager.connect('localhost', 50051); + expect(manager.hasHealthCheck, isFalse); + + fakeAsync((async) { + bridge.onPaused(); + async.elapse(const Duration(minutes: 1)); + bridge.onResumed(); + async.flushMicrotasks(); + + // Channel should still be the same (no reconnect triggered) + expect(manager.channelOrNull, isNotNull); + expect(manager.status, GrpcConnectionStatus.connected); + }); + }); }); } diff --git a/test/features/commands/notifiers/commands_notifier_test.dart b/test/features/commands/notifiers/commands_notifier_test.dart index 8be5df3..023b6a4 100644 --- a/test/features/commands/notifiers/commands_notifier_test.dart +++ b/test/features/commands/notifiers/commands_notifier_test.dart @@ -176,9 +176,10 @@ void main() { ); final captured = - verify(() => mockClient.getCommandRegistry(captureAny())) - .captured - .single as GetCommandRegistryRequest; + verify( + () => mockClient.getCommandRegistry(captureAny()), + ).captured.single + as GetCommandRegistryRequest; expect(captured.sessionId, 'sess-123'); }); @@ -190,9 +191,10 @@ void main() { await container.read(commandsProvider(null).future); final captured = - verify(() => mockClient.getCommandRegistry(captureAny())) - .captured - .single as GetCommandRegistryRequest; + verify( + () => mockClient.getCommandRegistry(captureAny()), + ).captured.single + as GetCommandRegistryRequest; expect(captured.sessionId, ''); }); }); @@ -248,12 +250,10 @@ void main() { ), ]; when(() => mockClient.listAgents(any())).thenAnswer( - (_) => - FakeResponseFuture.value(ListAgentsResponse(agents: agents)), + (_) => FakeResponseFuture.value(ListAgentsResponse(agents: agents)), ); - final result = - await notifier.listAgents(query: 'test', maxResults: 10); + final result = await notifier.listAgents(query: 'test', maxResults: 10); expect(result, hasLength(2)); expect(result[0].name, 'agent-1'); @@ -272,9 +272,8 @@ void main() { await notifier.listAgents(query: 'claude', maxResults: 5); final captured = - verify(() => mockClient.listAgents(captureAny())) - .captured - .single as ListAgentsRequest; + verify(() => mockClient.listAgents(captureAny())).captured.single + as ListAgentsRequest; expect(captured.query, 'claude'); expect(captured.maxResults, 5); @@ -329,12 +328,10 @@ void main() { ), ]; when(() => mockClient.listPath(any())).thenAnswer( - (_) => - FakeResponseFuture.value(ListPathResponse(entries: entries)), + (_) => FakeResponseFuture.value(ListPathResponse(entries: entries)), ); - final result = - await notifier.listPath(query: '/home', maxResults: 10); + final result = await notifier.listPath(query: '/home', maxResults: 10); expect(result, hasLength(2)); expect(result[0].path, '/home/user/project'); @@ -355,9 +352,8 @@ void main() { await notifier.listPath(query: '/tmp', maxResults: 15); final captured = - verify(() => mockClient.listPath(captureAny())) - .captured - .single as ListPathRequest; + verify(() => mockClient.listPath(captureAny())).captured.single + as ListPathRequest; expect(captured.query, '/tmp'); expect(captured.maxResults, 15); diff --git a/test/features/conversation/notifiers/conversation_notifier_test.dart b/test/features/conversation/notifiers/conversation_notifier_test.dart index b3f9b8b..753ea62 100644 --- a/test/features/conversation/notifiers/conversation_notifier_test.dart +++ b/test/features/conversation/notifiers/conversation_notifier_test.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:betcode_app/core/grpc/app_exceptions.dart'; import 'package:betcode_app/core/grpc/service_providers.dart'; import 'package:betcode_app/core/lifecycle/app_lifecycle_notifier.dart'; import 'package:betcode_app/features/conversation/models/conversation_state.dart'; @@ -138,7 +139,10 @@ void main() { final s = fc.read(conversationProvider(null)).value; expect(s, isA()); - expect((s! as ConversationError).message, contains('UNAVAILABLE')); + expect( + (s! as ConversationError).message, + contains('Failed to start conversation'), + ); }); }); @@ -263,6 +267,41 @@ void main() { }); }); + group('history load', () { + test( + 'AppException during history load sets errorMessage, not fatal', + () async { + // When the ErrorMappingInterceptor maps a GrpcError to an AppException + // during history load, the conversation should stay active with a soft + // error message rather than transitioning to ConversationError. + const sid = 'sess-history'; + final historyController = StreamController(); + when(() => mockClient.resumeSession(any())).thenAnswer((_) { + historyController.addError( + const NetworkError(message: 'Connection lost. Retrying...'), + ); + return FakeResponseStream(historyController); + }); + + final n = notifier(sid); + await n.startConversation(workingDirectory: '/tmp'); + await Future.delayed(Duration.zero); + + // startConversation sets state to ConversationActive before calling + // _loadHistory. The history load should catch the AppException + // gracefully and set errorMessage without killing the conversation. + final s = stateVal(sid); + expect(s, isA()); + expect( + (s! as ConversationActive).errorMessage, + "Couldn't load message history.", + ); + + await historyController.close(); + }, + ); + }); + group('stream error handling', () { test( 'transient stream error triggers reconnection on active session', @@ -287,12 +326,19 @@ void main() { final n = notifier(); await goActive(n); - eventController.addError(const GrpcError.unauthenticated('expired')); + eventController.addError( + const AuthExpiredError( + message: 'Your session has expired. Please log in again.', + ), + ); await Future.delayed(Duration.zero); final s = stateVal(); expect(s, isA()); - expect((s! as ConversationError).message, contains('expired')); + expect( + (s! as ConversationError).message, + 'Your session has expired. Please log in again.', + ); }); test('stream done triggers reconnection on active session', () async { @@ -430,7 +476,8 @@ void main() { ({ int Function() callCount, StreamController Function() latestController, - }) setupReconnectMock({ + }) + setupReconnectMock({ void Function(StreamController)? onReconnect, bool immediateError = false, GrpcError? throwOnConverse, @@ -440,8 +487,7 @@ void main() { var isFirstCall = true; when(() => mockClient.converse(any())).thenAnswer((inv) { - final reqStream = - inv.positionalArguments[0] as Stream; + final reqStream = inv.positionalArguments[0] as Stream; if (isFirstCall) { // First call is the initial startConversation. @@ -544,9 +590,15 @@ void main() { final mock = setupReconnectMock(); startActive(async); - injectError(async, const GrpcError.unauthenticated('expired')); - async.elapse(const Duration(seconds: 60)); + eventController.addError( + const AuthExpiredError( + message: 'Your session has expired. Please log in again.', + ), + ); + async + ..flushMicrotasks() + ..elapse(const Duration(seconds: 60)); expect(mock.callCount(), 0); diff --git a/test/features/conversation/widgets/command_palette_test.dart b/test/features/conversation/widgets/command_palette_test.dart index 4460ef2..d1a0856 100644 --- a/test/features/conversation/widgets/command_palette_test.dart +++ b/test/features/conversation/widgets/command_palette_test.dart @@ -203,8 +203,9 @@ void main() { expect(find.text('/exit'), findsOneWidget); }); - testWidgets('daemon command overrides local command with same name', - (t) async { + testWidgets('daemon command overrides local command with same name', ( + t, + ) async { final daemonCommands = AsyncData([ CommandEntry( name: 'exit', diff --git a/test/features/conversation/widgets/input_bar_test.dart b/test/features/conversation/widgets/input_bar_test.dart index 610f73f..d35a798 100644 --- a/test/features/conversation/widgets/input_bar_test.dart +++ b/test/features/conversation/widgets/input_bar_test.dart @@ -15,9 +15,9 @@ import 'package:flutter_test/flutter_test.dart'; /// A notifier that returns canned async value without gRPC calls. class _FakeCommandsNotifier extends CommandsNotifier { + // Never completes — keeps the provider in loading state. @override - Future> build() => - Completer>().future; // never completes → empty + Future> build() => Completer>().future; } Widget _app(Widget child) => ProviderScope( diff --git a/test/features/sessions/notifiers/sessions_notifier_test.dart b/test/features/sessions/notifiers/sessions_notifier_test.dart index 98c6228..85b6386 100644 --- a/test/features/sessions/notifiers/sessions_notifier_test.dart +++ b/test/features/sessions/notifiers/sessions_notifier_test.dart @@ -48,6 +48,7 @@ void main() { registerFallbackValue(ListSessionsRequest()); registerFallbackValue(RenameSessionRequest()); registerFallbackValue(CompactSessionRequest()); + registerFallbackValue(DeleteSessionRequest()); registerFallbackValue((Batch _) async {}); }); @@ -318,4 +319,46 @@ void main() { verify(() => mockClient.listSessions(any())).called(2); }); }); + + group('SessionsNotifier - deleteSession', () { + test('calls deleteSession RPC with correct session ID', () async { + // Build initial state + when(() => mockClient.listSessions(any())).thenAnswer( + (_) => FakeResponseFuture.value( + ListSessionsResponse(sessions: [makeSession('s-1')]), + ), + ); + await container.read(sessionsProvider.future); + + when( + () => mockClient.deleteSession(any()), + ).thenAnswer((_) => FakeResponseFuture.value(DeleteSessionResponse())); + + final notifier = container.read(sessionsProvider.notifier); + await notifier.deleteSession('s-1'); + + final captured = + verify(() => mockClient.deleteSession(captureAny())).captured.single + as DeleteSessionRequest; + expect(captured.sessionId, 's-1'); + }); + + test('refreshes sessions after deletion', () async { + await initNotifier( + container: container, + provider: sessionsProvider, + stubEmpty: stubSessionsEmpty, + ); + + when( + () => mockClient.deleteSession(any()), + ).thenAnswer((_) => FakeResponseFuture.value(DeleteSessionResponse())); + + final notifier = container.read(sessionsProvider.notifier); + await notifier.deleteSession('s-1'); + + // listSessions is called once for build, once for refresh after delete + verify(() => mockClient.listSessions(any())).called(2); + }); + }); } diff --git a/test/shared/widgets/connectivity_banner_test.dart b/test/shared/widgets/connectivity_banner_test.dart new file mode 100644 index 0000000..7a250cf --- /dev/null +++ b/test/shared/widgets/connectivity_banner_test.dart @@ -0,0 +1,78 @@ +import 'package:betcode_app/core/grpc/connection_state.dart'; +import 'package:betcode_app/core/grpc/grpc_providers.dart'; +import 'package:betcode_app/core/sync/connectivity.dart'; +import 'package:betcode_app/shared/widgets/connectivity_banner.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ConnectivityBanner', () { + Widget buildApp({ + AsyncValue connectionStatus = const AsyncData( + GrpcConnectionStatus.connected, + ), + AsyncValue networkStatus = const AsyncData( + NetworkStatus.online, + ), + }) { + return ProviderScope( + overrides: [ + connectionStatusProvider.overrideWith( + (_) => connectionStatus.when( + data: Stream.value, + loading: () => const Stream.empty(), + error: Stream.error, + ), + ), + networkStatusProvider.overrideWith( + (_) => networkStatus.when( + data: Stream.value, + loading: () => const Stream.empty(), + error: Stream.error, + ), + ), + ], + child: const MaterialApp( + home: Scaffold( + body: Column( + children: [ + ConnectivityBanner(), + Expanded(child: Placeholder()), + ], + ), + ), + ), + ); + } + + testWidgets('hidden when online and connected', (tester) async { + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + expect(find.text('No internet connection'), findsNothing); + expect(find.text('Relay unreachable'), findsNothing); + }); + + testWidgets('shows offline banner when network is offline', (tester) async { + await tester.pumpWidget( + buildApp( + networkStatus: const AsyncData(NetworkStatus.offline), + ), + ); + await tester.pumpAndSettle(); + expect(find.textContaining('No internet'), findsOneWidget); + }); + + testWidgets('shows relay banner when disconnected but online', ( + tester, + ) async { + await tester.pumpWidget( + buildApp( + connectionStatus: const AsyncData(GrpcConnectionStatus.reconnecting), + ), + ); + await tester.pumpAndSettle(); + expect(find.textContaining('reconnecting'), findsOneWidget); + }); + }); +}