From 60e621a5978a4c7f2e4ff6d21a19db5a3f399227 Mon Sep 17 00:00:00 2001 From: winetree94 Date: Mon, 3 Aug 2026 08:48:42 +0900 Subject: [PATCH 1/2] Unify daemon workspaces and settings --- README.md | 23 +- .../integration_test/debug_e2e_test.dart | 111 +- .../remote_bootstrap_smoke_test.dart | 43 +- apps/coder_app/lib/main_desktop.dart | 6 +- apps/coder_app/lib/main_mobile.dart | 6 +- apps/coder_app/lib/src/app.dart | 1988 +++--- apps/coder_app/lib/src/app.g.dart | 154 +- apps/coder_app/lib/src/app_services.dart | 101 + apps/coder_app/lib/src/app_settings_page.dart | 425 ++ apps/coder_app/lib/src/app_storage.dart | 266 + apps/coder_app/lib/src/bootstrap.dart | 55 - apps/coder_app/lib/src/controller.dart | 552 +- apps/coder_app/lib/src/controller.g.dart | 313 +- apps/coder_app/lib/src/desktop_bootstrap.dart | 168 +- apps/coder_app/lib/src/host_models.dart | 333 + apps/coder_app/lib/src/host_ports.dart | 165 + apps/coder_app/lib/src/host_registry.dart | 630 ++ apps/coder_app/lib/src/remote_bootstrap.dart | 76 +- apps/coder_app/lib/src/settings_page.dart | 75 +- .../Flutter/GeneratedPluginRegistrant.swift | 2 + apps/coder_app/pubspec.yaml | 2 + apps/coder_app/test/app_bootstrap_test.dart | 89 - apps/coder_app/test/app_flow_test.dart | 578 +- apps/coder_app/test/app_storage_test.dart | 180 + apps/coder_app/test/bootstrap_ports_test.dart | 158 - apps/coder_app/test/controller_test.dart | 341 +- apps/coder_app/test/entrypoint_test.dart | 8 +- .../test/golden/core_golden_test.dart | 114 +- .../golden/goldens/linux/daemon_hosts.png | Bin 0 -> 51684 bytes .../goldens/linux/provider_settings.png | Bin 93005 -> 96567 bytes apps/coder_app/test/host_registry_test.dart | 728 +++ .../test/host_settings_flow_test.dart | 311 + .../test/platform_services_test.dart | 198 + apps/coder_app/test/settings_flow_test.dart | 63 +- .../test/support/fake_coder_api.dart | 197 +- docs/remote-daemon.md | 87 + docs/testing.md | 5 + packages/coder_client/lib/src/api.dart | 43 +- packages/coder_client/lib/src/client.dart | 115 +- packages/coder_client/lib/src/endpoint.dart | 46 +- packages/coder_client/test/client_test.dart | 132 +- packages/coder_client/test/endpoint_test.dart | 14 +- packages/coder_daemon/bin/coder_daemon.dart | 8 +- packages/coder_daemon/lib/coder_daemon.dart | 2 + .../coder_daemon/lib/src/agent_service.dart | 12 +- .../coder_daemon/lib/src/application.dart | 47 +- packages/coder_daemon/lib/src/config.dart | 9 + .../lib/src/credential_store.dart | 44 +- packages/coder_daemon/lib/src/daos.dart | 123 +- packages/coder_daemon/lib/src/daos.g.dart | 30 + packages/coder_daemon/lib/src/database.dart | 47 +- packages/coder_daemon/lib/src/database.g.dart | 1503 ++++- packages/coder_daemon/lib/src/embedded.dart | 23 +- .../coder_daemon/lib/src/git_workspace.dart | 158 + packages/coder_daemon/lib/src/ports.dart | 189 + .../coder_daemon/lib/src/repositories.dart | 39 +- packages/coder_daemon/lib/src/server.dart | 118 +- .../lib/src/workspace_service.dart | 280 + .../test/credential_store_test.dart | 21 +- .../test/daemon_integration_test.dart | 139 +- .../test/provider_service_test.dart | 12 +- packages/coder_daemon/test/recovery_test.dart | 14 +- packages/coder_daemon/test/schema_test.dart | 21 +- .../test/workspace_path_gateway_test.dart | 51 + .../test/workspace_service_test.dart | 574 ++ packages/coder_protocol/lib/src/models.dart | 118 +- .../lib/src/models.freezed.dart | 1525 ++++- packages/coder_protocol/lib/src/models.g.dart | 109 +- packages/coder_protocol/lib/src/protocol.dart | 27 +- .../coder_protocol/lib/src/rpc_models.dart | 190 +- .../lib/src/rpc_models.freezed.dart | 5541 +++++++++++++---- .../coder_protocol/lib/src/rpc_models.g.dart | 173 +- .../coder_protocol/test/protocol_test.dart | 195 +- pubspec.lock | 56 + pubspec.yaml | 2 +- 75 files changed, 16437 insertions(+), 3864 deletions(-) create mode 100644 apps/coder_app/lib/src/app_services.dart create mode 100644 apps/coder_app/lib/src/app_settings_page.dart create mode 100644 apps/coder_app/lib/src/app_storage.dart delete mode 100644 apps/coder_app/lib/src/bootstrap.dart create mode 100644 apps/coder_app/lib/src/host_models.dart create mode 100644 apps/coder_app/lib/src/host_ports.dart create mode 100644 apps/coder_app/lib/src/host_registry.dart delete mode 100644 apps/coder_app/test/app_bootstrap_test.dart create mode 100644 apps/coder_app/test/app_storage_test.dart delete mode 100644 apps/coder_app/test/bootstrap_ports_test.dart create mode 100644 apps/coder_app/test/golden/goldens/linux/daemon_hosts.png create mode 100644 apps/coder_app/test/host_registry_test.dart create mode 100644 apps/coder_app/test/host_settings_flow_test.dart create mode 100644 apps/coder_app/test/platform_services_test.dart create mode 100644 docs/remote-daemon.md create mode 100644 packages/coder_daemon/lib/src/git_workspace.dart create mode 100644 packages/coder_daemon/lib/src/workspace_service.dart create mode 100644 packages/coder_daemon/test/workspace_path_gateway_test.dart create mode 100644 packages/coder_daemon/test/workspace_service_test.dart diff --git a/README.md b/README.md index 7ec49e4..9b9966e 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,15 @@ directory and listener with `TINYRACK_CODER_HOME` and `TINYRACK_CODER_LISTEN`. Without an override, Linux uses the XDG config/state directories, macOS uses Application Support, and Windows uses AppData. -Use the gear button in the connected app to configure multiple OpenAI or -OpenAI-compatible endpoints. Each provider can use Responses or Chat -Completions, discover model IDs through `/models`, and retain manually entered -models and capability overrides. Explicit model diagnostics make a small -streaming tool-call request and may incur provider charges. +The app shell does not require a daemon connection. Desktop enables its +app-owned embedded daemon by default, while mobile remains remote-only. Global +Settings can disable the embedded daemon and save any number of independent +`ws://` or `wss://` remote daemon profiles. Offline profiles and the last +selected host remain navigable. + +Provider setup belongs to a connected daemon. Embedded clients carry a separate +local-admin token and can mutate provider credentials; ordinary remote clients +carry only the bearer token and see provider settings read-only. Desktop and mobile use separate targets so the mobile bootstrap never starts a daemon. Run these commands from `apps/coder_app`: @@ -42,8 +46,7 @@ flutter run -d linux -t lib/main_desktop.dart flutter run -t lib/main_mobile.dart ``` -LAN mode is intentionally plain `ws://` for trusted local networks only. Bind a -standalone daemon to `0.0.0.0:7337` explicitly and provide a 256-bit token via -`TINYRACK_CODER_TOKEN`. Do not expose this listener to the public internet. -Remote clients can inspect the provider catalog but provider and credential -mutations are restricted to loopback connections. +The daemon intentionally does not implement TLS or certificate bypasses. Keep it +bound to loopback and terminate TLS in a reverse proxy for remote access. See +[`docs/remote-daemon.md`](docs/remote-daemon.md) for Caddy/Nginx WebSocket, +authentication-header, and development-data reset examples. diff --git a/apps/coder_app/integration_test/debug_e2e_test.dart b/apps/coder_app/integration_test/debug_e2e_test.dart index 1b32c58..ac40250 100644 --- a/apps/coder_app/integration_test/debug_e2e_test.dart +++ b/apps/coder_app/integration_test/debug_e2e_test.dart @@ -2,7 +2,9 @@ import 'dart:io'; import 'package:coder_agent/coder_agent.dart'; import 'package:coder_app/src/app.dart'; -import 'package:coder_app/src/desktop_bootstrap.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; import 'package:coder_client/coder_client.dart'; import 'package:coder_daemon/coder_daemon.dart'; import 'package:flutter/material.dart'; @@ -14,13 +16,19 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets( - 'embedded daemon streams, approves a patch, and restores timeline', + 'app switches hosts, streams, approves a patch, and restores timeline', (tester) async { FlutterSecureStorage.setMockInitialValues({}); final home = await Directory.systemTemp.createTemp('coder-e2e-home-'); final workspace = await Directory.systemTemp.createTemp( 'coder-e2e-workspace-', ); + final remoteHome = await Directory.systemTemp.createTemp( + 'coder-e2e-remote-home-', + ); + final remoteWorkspace = await Directory.systemTemp.createTemp( + 'coder-e2e-remote-workspace-', + ); final handle = await EmbeddedDaemonHandle.start( DaemonConfig( homeDirectory: home.path, @@ -30,47 +38,108 @@ void main() { ), provider: _PatchProvider(), ); + final remoteHandle = await DaemonApplication.start( + DaemonConfig( + homeDirectory: remoteHome.path, + port: 0, + bearerToken: 'remote-token-0123456789abcdef0123456789', + useEnvironmentCredentials: false, + ), + provider: _PatchProvider(), + ); addTearDown(() async { await handle.stop(); + await remoteHandle.stop(); if (home.existsSync()) home.deleteSync(recursive: true); + if (remoteHome.existsSync()) remoteHome.deleteSync(recursive: true); if (workspace.existsSync()) workspace.deleteSync(recursive: true); + if (remoteWorkspace.existsSync()) { + remoteWorkspace.deleteSync(recursive: true); + } }); final endpoint = HostEndpoint( websocketUri: handle.boundEndpoint, - token: handle.bearerToken, ); final setupClient = await CoderClient.connect( endpoint: endpoint, + credentials: DaemonCredentials( + bearerToken: handle.bearerToken, + adminToken: handle.adminToken, + ), clientId: 'e2e-setup', clientKind: 'integration-test', ); await setupClient.registerWorkspace( - id: 'workspace-e2e', + workspaceId: 'workspace-e2e', + checkoutId: 'checkout-e2e', rootPath: workspace.path, name: 'E2E Workspace', ); await setupClient.close(); + final remoteSetupClient = await CoderClient.connect( + endpoint: HostEndpoint(websocketUri: remoteHandle.boundEndpoint), + credentials: const DaemonCredentials( + bearerToken: 'remote-token-0123456789abcdef0123456789', + ), + clientId: 'remote-e2e-setup', + clientKind: 'integration-test', + ); + await remoteSetupClient.registerWorkspace( + workspaceId: 'remote-workspace-e2e', + checkoutId: 'remote-checkout-e2e', + rootPath: remoteWorkspace.path, + name: 'Remote Workspace', + ); + await remoteSetupClient.close(); + + final now = DateTime.utc(2026, 8, 3); + final appStore = MemoryAppStore( + profiles: [ + RemoteDaemonProfile( + id: 'remote', + label: 'Remote daemon', + websocketUri: remoteHandle.boundEndpoint, + autoConnect: true, + createdAt: now, + updatedAt: now, + ), + ], + tokens: const { + 'remote': 'remote-token-0123456789abcdef0123456789', + }, + ); await tester.pumpWidget( CoderApp( - bootstrap: DesktopBootstrap( - launcher: _ExistingLauncher(handle), + services: AppServices( + settings: appStore, + profiles: appStore, + credentials: appStore, + clients: const WebSocketHostClientFactory(), + clientKind: 'desktop-integration-test', + embeddedLauncher: _ExistingLauncher(handle), ), ), ); + await _pumpUntil(tester, find.text('내장 daemon')); + await _pumpUntil(tester, find.text('Remote daemon')); await _pumpUntil(tester, find.text('E2E Workspace')); - await tester.tap(find.text('E2E Workspace')); - await _pumpUntil(tester, find.text('새 agent를 만들어 시작하세요.')); - await tester.tap(find.byTooltip('Agent 생성')); + await _pumpUntil(tester, find.text('Remote Workspace')); + await tester.tap(find.text('E2E Workspace').last); await tester.pumpAndSettle(); - await tester.tap(find.text('생성')); await _pumpUntil( tester, - find.text('요청을 입력해 coding agent를 시작하세요.'), + find.text(workspace.path), ); + await tester.tap(find.text('E2E Workspace').last); + await _pumpUntil(tester, find.text('새 session 시작')); + await tester.tap(find.text('새 session 시작')); + await tester.pumpAndSettle(); + await tester.tap(find.text('생성')); + await _pumpUntil(tester, find.text('코딩 요청을 입력하세요.')); await tester.enterText(find.byType(TextField).last, 'Create result.txt'); - await tester.tap(find.byIcon(Icons.arrow_upward)); + await tester.testTextInput.receiveAction(TextInputAction.done); await _pumpUntil(tester, find.text('승인 필요 · apply_patch')); await tester.tap(find.text('승인')); await _pumpUntil( @@ -84,10 +153,14 @@ void main() { final reconnected = await CoderClient.connect( endpoint: endpoint, + credentials: DaemonCredentials( + bearerToken: handle.bearerToken, + adminToken: handle.adminToken, + ), clientId: 'e2e-reconnect', clientKind: 'integration-test', ); - final agents = await reconnected.listAgents(workspaceId: 'workspace-e2e'); + final agents = await reconnected.listAgents(worktreeId: 'checkout-e2e'); expect(agents, hasLength(1)); final timeline = await reconnected.subscribeTimeline(agents.single.id); expect(timeline.map((event) => event.type), contains('turn.completed')); @@ -129,10 +202,18 @@ final class _ExistingSession implements EmbeddedDaemonSession { final EmbeddedDaemonHandle handle; @override - String get bearerToken => handle.bearerToken; + DaemonCredentials get credentials => DaemonCredentials( + bearerToken: handle.bearerToken, + adminToken: handle.adminToken, + ); + + @override + HostEndpoint get endpoint => HostEndpoint( + websocketUri: handle.boundEndpoint, + ); @override - Uri get boundEndpoint => handle.boundEndpoint; + String get serverId => handle.serverId; @override Future stop() => handle.stop(); diff --git a/apps/coder_app/integration_test/remote_bootstrap_smoke_test.dart b/apps/coder_app/integration_test/remote_bootstrap_smoke_test.dart index e2678d0..1abb48f 100644 --- a/apps/coder_app/integration_test/remote_bootstrap_smoke_test.dart +++ b/apps/coder_app/integration_test/remote_bootstrap_smoke_test.dart @@ -1,6 +1,8 @@ import 'package:coder_app/src/app.dart'; -import 'package:coder_app/src/remote_bootstrap.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_client/coder_client.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -8,11 +10,40 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('mobile bootstrap remains remote-only', (tester) async { - FlutterSecureStorage.setMockInitialValues({}); - await tester.pumpWidget(CoderApp(bootstrap: RemoteBootstrap())); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + ); + await tester.pumpWidget( + CoderApp( + services: AppServices( + settings: store, + profiles: store, + credentials: store, + clients: const _UnusedClients(), + clientKind: 'mobile-integration-test', + ), + ), + ); await tester.pumpAndSettle(); - expect(find.text('모바일은 원격 daemon에만 연결합니다.'), findsOneWidget); - expect(find.text('Daemon WebSocket 주소'), findsOneWidget); + expect(find.text('설정된 daemon이 없습니다.'), findsOneWidget); + await tester.tap(find.byTooltip('설정')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Daemon')); + await tester.pumpAndSettle(); + expect(find.text('내장 daemon'), findsNothing); + expect(find.text('원격 daemon 추가'), findsOneWidget); }); } + +final class _UnusedClients implements HostClientFactory { + const _UnusedClients(); + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) => throw StateError('No host should connect in a remote-only smoke test.'); +} diff --git a/apps/coder_app/lib/main_desktop.dart b/apps/coder_app/lib/main_desktop.dart index 2079788..31d0e6a 100644 --- a/apps/coder_app/lib/main_desktop.dart +++ b/apps/coder_app/lib/main_desktop.dart @@ -1,12 +1,12 @@ import 'package:coder_app/src/app.dart'; -import 'package:coder_app/src/bootstrap.dart'; +import 'package:coder_app/src/app_services.dart'; import 'package:coder_app/src/desktop_bootstrap.dart'; import 'package:flutter/material.dart'; /// Starts the desktop widget tree with an injectable bootstrap. -Future runDesktopApp({AppBootstrap? bootstrap}) async { +Future runDesktopApp({AppServices? services}) async { WidgetsFlutterBinding.ensureInitialized(); - runApp(CoderApp(bootstrap: bootstrap ?? DesktopBootstrap())); + runApp(CoderApp(services: services ?? await createDesktopServices())); } /// Starts the production desktop application. diff --git a/apps/coder_app/lib/main_mobile.dart b/apps/coder_app/lib/main_mobile.dart index ae323cf..e616189 100644 --- a/apps/coder_app/lib/main_mobile.dart +++ b/apps/coder_app/lib/main_mobile.dart @@ -1,12 +1,12 @@ import 'package:coder_app/src/app.dart'; -import 'package:coder_app/src/bootstrap.dart'; +import 'package:coder_app/src/app_services.dart'; import 'package:coder_app/src/remote_bootstrap.dart'; import 'package:flutter/material.dart'; /// Starts the mobile widget tree with an injectable remote-only bootstrap. -Future runMobileApp({AppBootstrap? bootstrap}) async { +Future runMobileApp({AppServices? services}) async { WidgetsFlutterBinding.ensureInitialized(); - runApp(CoderApp(bootstrap: bootstrap ?? RemoteBootstrap())); + runApp(CoderApp(services: services ?? await createRemoteServices())); } /// Starts the production mobile application. diff --git a/apps/coder_app/lib/src/app.dart b/apps/coder_app/lib/src/app.dart index 1e6e89a..b0cf6bd 100644 --- a/apps/coder_app/lib/src/app.dart +++ b/apps/coder_app/lib/src/app.dart @@ -1,9 +1,13 @@ +import 'dart:async'; import 'dart:convert'; -import 'package:coder_app/src/bootstrap.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/app_settings_page.dart'; import 'package:coder_app/src/controller.dart'; import 'package:coder_app/src/external_url_opener.dart'; +import 'package:coder_app/src/host_models.dart'; import 'package:coder_app/src/settings_page.dart'; +import 'package:coder_client/coder_client.dart'; import 'package:coder_protocol/coder_protocol.dart'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; @@ -13,19 +17,19 @@ import 'package:go_router/go_router.dart'; part 'app.g.dart'; -/// CoderApp defines a public contract. +/// Tinyrack Coder application composition. class CoderApp extends StatelessWidget { - /// Creates a [CoderApp]. + /// Creates the application. CoderApp({ - required this.bootstrap, + required this.services, this.externalUrlOpener = const PlatformExternalUrlOpener(), super.key, }); - /// The bootstrap public API member. - final AppBootstrap bootstrap; + /// Platform services used by feature controllers. + final AppServices services; - /// Platform adapter used to open interactive authorization pages. + /// Opens interactive provider authorization pages. final ExternalUrlOpener externalUrlOpener; late final GoRouter _router = GoRouter(routes: $appRoutes); @@ -33,674 +37,849 @@ class CoderApp extends StatelessWidget { @override Widget build(BuildContext context) => ProviderScope( overrides: [ - bootstrapProvider.overrideWithValue(bootstrap), + appServicesProvider.overrideWithValue(services), externalUrlOpenerProvider.overrideWithValue(externalUrlOpener), ], child: MaterialApp.router( title: 'Tinyrack Coder', debugShowCheckedModeBanner: false, - theme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xff625bff), - ), - useMaterial3: true, - cardTheme: const CardThemeData(margin: EdgeInsets.zero), - ), - darkTheme: ThemeData( - colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xff948dff), - brightness: Brightness.dark, - ), - useMaterial3: true, - cardTheme: const CardThemeData(margin: EdgeInsets.zero), - ), + theme: _theme(Brightness.light), + darkTheme: _theme(Brightness.dark), routerConfig: _router, ), ); } -@TypedGoRoute(path: '/') -/// HostRoute defines a public contract. -class HostRoute extends GoRouteData with $HostRoute { - /// Creates a [HostRoute]. - const HostRoute(); +ThemeData _theme(Brightness brightness) => ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: brightness == Brightness.light + ? const Color(0xff625bff) + : const Color(0xff948dff), + brightness: brightness, + ), + useMaterial3: true, + cardTheme: const CardThemeData(margin: EdgeInsets.zero), +); + +@TypedGoRoute(path: '/') +/// Unified workspace home shown before daemon connections complete. +class WorkspaceHomeRoute extends GoRouteData with $WorkspaceHomeRoute { + /// Creates the workspace home route. + const WorkspaceHomeRoute(); @override - Widget build(BuildContext context, GoRouterState state) => const HostPage(); + Widget build(BuildContext context, GoRouterState state) => + const WorkspacePage(); } -@TypedGoRoute(path: '/hosts/:hostId') -/// DashboardRoute defines a public contract. -class DashboardRoute extends GoRouteData with $DashboardRoute { - /// Creates a [DashboardRoute]. - const DashboardRoute({required this.hostId}); +@TypedGoRoute( + path: '/workspaces/:hostId/:workspaceId/:worktreeId', +) +/// Opens a checkout and its session tabs. +class WorktreeRoute extends GoRouteData with $WorktreeRoute { + /// Creates a checkout route. + const WorktreeRoute({ + required this.hostId, + required this.workspaceId, + required this.worktreeId, + }); - /// The hostId public API member. + /// App-local daemon ID. final String hostId; + /// Daemon-local repository ID. + final String workspaceId; + + /// Daemon-local checkout ID. + final String worktreeId; + @override - Widget build(BuildContext context, GoRouterState state) => - DashboardPage(hostId: hostId); + Widget build(BuildContext context, GoRouterState state) => WorkspacePage( + selection: WorkspaceSelection( + hostId: hostId, + workspaceId: workspaceId, + worktreeId: worktreeId, + ), + ); } -@TypedGoRoute(path: '/hosts/:hostId/settings') -/// SettingsRoute defines a public contract. -class SettingsRoute extends GoRouteData with $SettingsRoute { - /// Creates a [SettingsRoute]. - const SettingsRoute({required this.hostId}); +@TypedGoRoute( + path: '/workspaces/:hostId/:workspaceId/:worktreeId/sessions/:agentId', +) +/// Opens one AI session in the checkout tab strip. +class SessionRoute extends GoRouteData with $SessionRoute { + /// Creates a session route. + const SessionRoute({ + required this.hostId, + required this.workspaceId, + required this.worktreeId, + required this.agentId, + }); - /// The hostId public API member. + /// App-local daemon ID. final String hostId; + /// Daemon-local repository ID. + final String workspaceId; + + /// Daemon-local checkout ID. + final String worktreeId; + + /// Daemon-local AI session ID. + final String agentId; + @override - Widget build(BuildContext context, GoRouterState state) => - SettingsPage(hostId: hostId); + Widget build(BuildContext context, GoRouterState state) => WorkspacePage( + selection: WorkspaceSelection( + hostId: hostId, + workspaceId: workspaceId, + worktreeId: worktreeId, + ), + requestedAgentId: agentId, + ); } -@TypedGoRoute( - path: '/hosts/:hostId/workspaces/:workspaceId', -) -/// WorkspaceRoute defines a public contract. -class WorkspaceRoute extends GoRouteData with $WorkspaceRoute { - /// Creates a [WorkspaceRoute]. - const WorkspaceRoute({required this.hostId, required this.workspaceId}); +@TypedGoRoute(path: '/settings/providers') +/// Unified settings route with Provider selected. +class ProviderSettingsRoute extends GoRouteData with $ProviderSettingsRoute { + /// Creates the provider settings route. + const ProviderSettingsRoute({this.hostId}); - /// The hostId public API member. - final String hostId; + /// Preferred daemon in the provider selector. + final String? hostId; - /// The workspaceId public API member. - final String workspaceId; + @override + Widget build(BuildContext context, GoRouterState state) => + UnifiedSettingsPage(category: SettingsCategory.provider, hostId: hostId); +} + +@TypedGoRoute(path: '/settings/daemons') +/// Unified settings route with Daemon selected. +class DaemonSettingsRoute extends GoRouteData with $DaemonSettingsRoute { + /// Creates daemon settings route. + const DaemonSettingsRoute(); @override Widget build(BuildContext context, GoRouterState state) => - DashboardPage(hostId: hostId, workspaceId: workspaceId); + const UnifiedSettingsPage(category: SettingsCategory.daemon); } -@TypedGoRoute( - path: '/hosts/:hostId/workspaces/:workspaceId/agents/:agentId', -) -/// AgentRoute defines a public contract. -class AgentRoute extends GoRouteData with $AgentRoute { - /// Creates a [AgentRoute]. - const AgentRoute({ - required this.hostId, - required this.workspaceId, - required this.agentId, - }); +@TypedGoRoute(path: '/settings/daemons/new') +/// Adds a remote daemon profile. +class NewHostRoute extends GoRouteData with $NewHostRoute { + /// Creates the route. + const NewHostRoute(); - /// The hostId public API member. - final String hostId; + @override + Widget build(BuildContext context, GoRouterState state) => + const RemoteHostEditPage(); +} - /// The workspaceId public API member. - final String workspaceId; +@TypedGoRoute(path: '/settings/daemons/:hostId') +/// Edits a remote daemon profile. +class EditHostRoute extends GoRouteData with $EditHostRoute { + /// Creates the route. + const EditHostRoute({required this.hostId}); - /// The agentId public API member. - final String agentId; + /// App-local daemon profile ID. + final String hostId; @override Widget build(BuildContext context, GoRouterState state) => - DashboardPage(hostId: hostId, workspaceId: workspaceId, agentId: agentId); + RemoteHostEditPage(hostId: hostId); } -/// HostPage defines a public contract. -class HostPage extends ConsumerStatefulWidget { - /// Creates a [HostPage]. - const HostPage({super.key}); +/// Top-level settings categories. +enum SettingsCategory { + /// API provider connections owned by one daemon. + provider, + + /// Embedded and remote daemon connections. + daemon, +} + +/// Shared two-pane settings shell. +class UnifiedSettingsPage extends ConsumerStatefulWidget { + /// Creates a unified settings page. + const UnifiedSettingsPage({ + required this.category, + this.hostId, + super.key, + }); + + /// Selected settings category. + final SettingsCategory category; + + /// Preferred provider daemon. + final String? hostId; @override - ConsumerState createState() => _HostPageState(); + ConsumerState createState() => + _UnifiedSettingsPageState(); } -class _HostPageState extends ConsumerState { - final _address = TextEditingController(text: 'ws://127.0.0.1:7337/ws'); - final _token = TextEditingController(); +class _UnifiedSettingsPageState extends ConsumerState { + String? _hostId; @override - void initState() { - super.initState(); + Widget build(BuildContext context) { + final registry = ref.watch(hostRegistryControllerProvider).asData?.value; + final online = + registry?.runtimes.values + .where((item) => item.connected) + .toList(growable: false) ?? + const []; + _hostId ??= online.any((item) => item.id == widget.hostId) + ? widget.hostId + : online.firstOrNull?.id; + final detail = widget.category == SettingsCategory.daemon + ? const AppSettingsPage(embedded: true) + : _ProviderSettingsDetail( + hosts: online, + hostId: _hostId, + onChanged: (value) => setState(() => _hostId = value), + ); + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => const WorkspaceHomeRoute().go(context), + icon: const Icon(Icons.arrow_back), + ), + title: const Text('설정'), + ), + body: LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth < 760) return detail; + return Row( + children: [ + SizedBox( + width: 230, + child: _SettingsSidebar(selected: widget.category), + ), + const VerticalDivider(width: 1), + Expanded(child: detail), + ], + ); + }, + ), + ); } +} + +class _SettingsSidebar extends StatelessWidget { + const _SettingsSidebar({required this.selected}); + + final SettingsCategory selected; @override - void dispose() { - _address.dispose(); - _token.dispose(); - super.dispose(); - } + Widget build(BuildContext context) => ListView( + padding: const EdgeInsets.all(12), + children: [ + ListTile( + selected: selected == SettingsCategory.provider, + leading: const Icon(Icons.hub_outlined), + title: const Text('Provider'), + onTap: () => const ProviderSettingsRoute().go(context), + ), + ListTile( + selected: selected == SettingsCategory.daemon, + leading: const Icon(Icons.dns_outlined), + title: const Text('Daemon'), + onTap: () => const DaemonSettingsRoute().go(context), + ), + ], + ); +} + +class _ProviderSettingsDetail extends StatelessWidget { + const _ProviderSettingsDetail({ + required this.hosts, + required this.hostId, + required this.onChanged, + }); + + final List hosts; + final String? hostId; + final ValueChanged onChanged; @override Widget build(BuildContext context) { - ref.listen(connectionControllerProvider, (previous, next) { - final current = next.asData?.value; - final wasConnected = previous?.asData?.value?.connected == true; - if (current?.connected == true && !wasConnected) { - DashboardRoute(hostId: current!.serverInfo.serverId).go(context); - } - }); - final state = ref.watch(connectionControllerProvider); - final connection = state.asData?.value; - final connecting = state.isLoading || connection?.connecting == true; - return Scaffold( - body: Center( - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 460), - child: Padding( - padding: const EdgeInsets.all(32), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Icon( - Icons.auto_awesome, - size: 48, - color: Theme.of(context).colorScheme.primary, - ), - const SizedBox(height: 20), - Text( - 'Tinyrack Coder', - style: Theme.of(context).textTheme.headlineMedium, - ), - const SizedBox(height: 8), - Text( - ref.read(bootstrapProvider).canRegisterLocalWorkspace - ? '로컬 daemon을 시작하거나 원격 host에 연결합니다.' - : '모바일은 원격 daemon에만 연결합니다.', - ), - const SizedBox(height: 28), - TextField( - controller: _address, - decoration: const InputDecoration( - labelText: 'Daemon WebSocket 주소', - ), - ), - const SizedBox(height: 12), - TextField( - controller: _token, - obscureText: true, - decoration: const InputDecoration(labelText: 'Bearer token'), - ), - if (state.hasError) ...[ - const SizedBox(height: 12), - Text( - '${state.error}', - style: TextStyle( - color: Theme.of(context).colorScheme.error, - ), + if (hosts.isEmpty || hostId == null) { + return const Center(child: Text('온라인 daemon 연결이 필요합니다.')); + } + return Column( + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), + child: DropdownButtonFormField( + initialValue: hostId, + decoration: const InputDecoration(labelText: 'Daemon'), + items: hosts + .map( + (host) => DropdownMenuItem( + value: host.id, + child: Text(host.label), ), - ], - const SizedBox(height: 20), - FilledButton.icon( - onPressed: connecting - ? null - : () => ref - .read(connectionControllerProvider.notifier) - .connect(_address.text, _token.text), - icon: connecting - ? const SizedBox.square( - dimension: 18, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Icon(Icons.lan), - label: Text(connecting ? '연결 중…' : '연결'), - ), - ], - ), + ) + .toList(growable: false), + onChanged: onChanged, ), ), - ), + Expanded(child: SettingsPage(hostId: hostId!, embedded: true)), + ], ); } } -/// DashboardPage defines a public contract. -class DashboardPage extends ConsumerWidget { - /// Creates a [DashboardPage]. - const DashboardPage({ - required this.hostId, - this.workspaceId, - this.agentId, +/// Unified host/repository/worktree tree and session-tab workspace. +class WorkspacePage extends ConsumerStatefulWidget { + /// Creates a workspace page. + const WorkspacePage({ + this.selection, + this.requestedAgentId, super.key, }); - /// The hostId public API member. - final String hostId; + /// Selected checkout, if any. + final WorkspaceSelection? selection; - /// The workspaceId public API member. - final String? workspaceId; + /// Session requested by the route. + final String? requestedAgentId; - /// The agentId public API member. - final String? agentId; + @override + ConsumerState createState() => _WorkspacePageState(); +} + +class _WorkspacePageState extends ConsumerState { + bool _restoreScheduled = false; @override - Widget build(BuildContext context, WidgetRef ref) { - final connection = ref.watch(connectionControllerProvider).asData?.value; - if (connection?.connected != true) { - return Scaffold( - body: Center( - child: FilledButton( - onPressed: () => const HostRoute().go(context), - child: const Text('Host 연결로 돌아가기'), - ), - ), - ); - } + Widget build(BuildContext context) { + final registry = ref.watch(hostRegistryControllerProvider); + final catalog = ref.watch(workspaceCatalogControllerProvider); + _restoreSelection(registry.asData?.value, catalog.asData?.value); return Scaffold( appBar: AppBar( - title: const Text('Tinyrack Coder'), + title: const Text('Workspaces'), actions: [ + IconButton( + tooltip: '폴더 추가', + onPressed: + registry.asData?.value.runtimes.values.any( + (item) => item.connected, + ) == + true + ? () => _addFolder(registry.requireValue) + : null, + icon: const Icon(Icons.create_new_folder_outlined), + ), IconButton( tooltip: '설정', - onPressed: () => SettingsRoute(hostId: hostId).go(context), + onPressed: () { + final hostId = widget.selection?.hostId; + if (hostId == null) { + const DaemonSettingsRoute().go(context); + } else { + ProviderSettingsRoute(hostId: hostId).go(context); + } + }, icon: const Icon(Icons.settings_outlined), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Center(child: Text(connection?.label ?? 'connected')), - ), ], ), body: LayoutBuilder( builder: (context, constraints) { - if (constraints.maxWidth >= 1000) { - return Row( - children: [ - SizedBox( - width: 270, - child: _WorkspacePane( - hostId: hostId, - selectedWorkspaceId: workspaceId, - ), - ), - const VerticalDivider(width: 1), - SizedBox( - width: 280, - child: _AgentPane( - hostId: hostId, - workspaceId: workspaceId, - selectedAgentId: agentId, - ), - ), - const VerticalDivider(width: 1), - Expanded( - child: _ConversationPane( - workspaceId: workspaceId, - agentId: agentId, - ), - ), - ], - ); - } - if (agentId != null) { - return _ConversationPane( - workspaceId: workspaceId, - agentId: agentId, - ); - } - if (workspaceId != null) { - return _AgentPane( - hostId: hostId, - workspaceId: workspaceId, - selectedAgentId: agentId, - ); + final tree = _WorkspaceTree( + registry: registry.asData?.value, + catalog: catalog, + selected: widget.selection, + onAddFolder: registry.asData == null + ? null + : () => _addFolder(registry.requireValue), + ); + if (constraints.maxWidth < 760) { + return widget.selection == null + ? tree + : _SessionArea( + selection: widget.selection!, + requestedAgentId: widget.requestedAgentId, + showBack: true, + ); } - return _WorkspacePane( - hostId: hostId, - selectedWorkspaceId: workspaceId, + return Row( + children: [ + SizedBox(width: 320, child: tree), + const VerticalDivider(width: 1), + Expanded( + child: widget.selection == null + ? const _EmptyWorkspaceDetail() + : _SessionArea( + selection: widget.selection!, + requestedAgentId: widget.requestedAgentId, + ), + ), + ], ); }, ), ); } + + void _restoreSelection( + HostRegistryState? registry, + UnifiedWorkspaceCatalogState? catalog, + ) { + if (_restoreScheduled || widget.selection != null) return; + final saved = registry?.settings.lastWorktree; + if (saved == null || catalog == null) return; + final exists = + catalog.catalogs[saved.hostId]?.worktrees.any( + (item) => + item.id == saved.worktreeId && + item.workspaceId == saved.workspaceId, + ) ?? + false; + if (!exists) return; + _restoreScheduled = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) _goWorktree(context, saved); + }); + } + + Future _addFolder(HostRegistryState registry) async { + final online = registry.runtimes.values + .where((item) => item.connected) + .toList(growable: false); + final hostId = await showDialog( + context: context, + builder: (context) => SimpleDialog( + title: const Text('폴더를 추가할 daemon'), + children: [ + for (final host in online) + SimpleDialogOption( + onPressed: () => Navigator.pop(context, host.id), + child: ListTile( + leading: Icon( + host.kind == HostKind.embedded + ? Icons.computer_outlined + : Icons.cloud_outlined, + ), + title: Text(host.label), + ), + ), + ], + ), + ); + if (hostId == null || !mounted) return; + final host = registry.runtimes[hostId]!; + final path = + host.kind == HostKind.embedded && + ref.read(appServicesProvider).supportsEmbeddedDaemon + ? await getDirectoryPath(confirmButtonText: 'Workspace 선택') + : await showDialog( + context: context, + builder: (context) => _RemoteDirectoryDialog(api: host.api!), + ); + if (path == null || !mounted) return; + final result = await ref + .read(workspaceCatalogControllerProvider.notifier) + .register(hostId, path); + if (!mounted || result.worktrees.isEmpty) return; + _goWorktree( + context, + WorkspaceSelection( + hostId: hostId, + workspaceId: result.workspace.id, + worktreeId: result.worktrees.first.id, + ), + ); + } } -class _WorkspacePane extends ConsumerWidget { - const _WorkspacePane({ - required this.hostId, - required this.selectedWorkspaceId, +class _WorkspaceTree extends StatelessWidget { + const _WorkspaceTree({ + required this.registry, + required this.catalog, + required this.selected, + required this.onAddFolder, }); - final String hostId; - final String? selectedWorkspaceId; + final HostRegistryState? registry; + final AsyncValue catalog; + final WorkspaceSelection? selected; + final VoidCallback? onAddFolder; @override - Widget build(BuildContext context, WidgetRef ref) { - final state = ref.watch(workspacesControllerProvider); - final controller = ref.read(workspacesControllerProvider.notifier); - final canRegister = ref - .read(connectionControllerProvider.notifier) - .canRegisterLocalWorkspace; + Widget build(BuildContext context) { + final runtimes = + registry?.runtimes.values.toList(growable: false) ?? + const []; + final catalogs = + catalog.asData?.value.catalogs ?? const {}; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ListTile( - title: const Text('Workspaces'), - trailing: canRegister - ? IconButton( - tooltip: '로컬 workspace 등록', - onPressed: () async { - final path = await getDirectoryPath( - confirmButtonText: 'Workspace 선택', - ); - if (path == null || !context.mounted) return; - final workspace = await controller.register(path); - if (context.mounted) { - WorkspaceRoute( - hostId: hostId, - workspaceId: workspace.id, - ).go(context); - } + title: const Text('Repositories'), + trailing: IconButton( + tooltip: '폴더 추가', + onPressed: onAddFolder, + icon: const Icon(Icons.add), + ), + ), + const Divider(height: 1), + Expanded( + child: runtimes.isEmpty + ? _NoDaemonState( + onSettings: () { + const DaemonSettingsRoute().go(context); }, - icon: const Icon(Icons.create_new_folder_outlined), ) - : null, + : ListView( + children: [ + for (final host in runtimes) + _HostTreeNode( + host: host, + catalog: catalogs[host.id], + selected: selected, + ), + ], + ), ), - Expanded( - child: state.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stackTrace) => Center(child: Text('$error')), - data: (workspaces) => workspaces.isEmpty - ? const Center(child: Text('등록된 workspace가 없습니다.')) - : ListView.builder( - itemCount: workspaces.length, - itemBuilder: (context, index) { - final item = workspaces[index]; - return ListTile( - selected: item.id == selectedWorkspaceId, - leading: const Icon(Icons.folder_outlined), - title: Text(item.name), - subtitle: Text( - item.rootPath, - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - onTap: () => WorkspaceRoute( - hostId: hostId, - workspaceId: item.id, - ).go(context), - ); - }, - ), - ), + ], + ); + } +} + +class _HostTreeNode extends StatelessWidget { + const _HostTreeNode({ + required this.host, + required this.catalog, + required this.selected, + }); + + final HostRuntimeSnapshot host; + final WorkspaceCatalogDto? catalog; + final WorkspaceSelection? selected; + + @override + Widget build(BuildContext context) { + final workspaces = catalog?.workspaces ?? const []; + if (!host.connected) { + return ListTile( + leading: const Icon(Icons.cloud_off_outlined), + title: Text(host.label), + subtitle: Text( + '${_hostStatusLabel(host.status)}' + '${host.error == null ? '' : ' · ${host.error}'}', ), + ); + } + return ExpansionTile( + initiallyExpanded: true, + leading: const Icon(Icons.dns_outlined), + title: Text(host.label), + subtitle: Text(_hostStatusLabel(host.status)), + children: [ + for (final workspace in workspaces) + _RepositoryTreeNode( + hostId: host.id, + workspace: workspace, + worktrees: catalog!.worktrees + .where((item) => item.workspaceId == workspace.id) + .toList(growable: false), + selected: selected, + ), ], ); } } -class _AgentPane extends ConsumerWidget { - const _AgentPane({ +class _RepositoryTreeNode extends ConsumerWidget { + const _RepositoryTreeNode({ required this.hostId, - required this.workspaceId, - required this.selectedAgentId, + required this.workspace, + required this.worktrees, + required this.selected, }); final String hostId; - final String? workspaceId; - final String? selectedAgentId; + final WorkspaceDto workspace; + final List worktrees; + final WorkspaceSelection? selected; @override - Widget build(BuildContext context, WidgetRef ref) { - final state = ref.watch(agentsControllerProvider(workspaceId)); - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + Widget build(BuildContext context, WidgetRef ref) => Padding( + padding: const EdgeInsets.only(left: 16), + child: ExpansionTile( + initiallyExpanded: selected?.workspaceId == workspace.id, + leading: Icon( + workspace.kind == WorkspaceKind.git + ? Icons.account_tree_outlined + : Icons.folder_outlined, + ), + title: Row( + children: [ + Expanded(child: Text(workspace.name)), + if (workspace.kind == WorkspaceKind.git) + IconButton( + tooltip: '새 worktree', + visualDensity: VisualDensity.compact, + onPressed: () => _createWorktree(context, ref), + icon: const Icon(Icons.add, size: 18), + ), + ], + ), + subtitle: Text( + workspace.rootPath, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), children: [ - ListTile( - leading: MediaQuery.sizeOf(context).width < 1000 - ? IconButton( - onPressed: () => context.pop(), - icon: const Icon(Icons.arrow_back), - ) - : null, - title: const Text('Agents'), - trailing: workspaceId == null - ? null - : IconButton( - tooltip: 'Agent 생성', - onPressed: () => _createAgent(context, ref, workspaceId!), - icon: const Icon(Icons.add), - ), - ), - Expanded( - child: workspaceId == null - ? const Center(child: Text('Workspace를 선택하세요.')) - : state.when( - loading: () => - const Center(child: CircularProgressIndicator()), - error: (error, stackTrace) => Center(child: Text('$error')), - data: (agents) => agents.isEmpty - ? const Center(child: Text('새 agent를 만들어 시작하세요.')) - : ListView.builder( - itemCount: agents.length, - itemBuilder: (context, index) { - final agent = agents[index]; - return ListTile( - selected: agent.id == selectedAgentId, - leading: Icon(_agentIcon(agent.status)), - title: Text(agent.title), - subtitle: Text( - '${agent.model} · ${agent.status.name}', - ), - onTap: () => AgentRoute( - hostId: hostId, - workspaceId: workspaceId!, - agentId: agent.id, - ).go(context), - ); - }, - ), + for (final worktree in worktrees) + ListTile( + contentPadding: const EdgeInsets.only(left: 48, right: 12), + selected: + selected?.hostId == hostId && + selected?.worktreeId == worktree.id, + leading: Icon( + worktree.kind == WorktreeKind.checkout + ? Icons.home_work_outlined + : Icons.call_split_outlined, + size: 20, + ), + title: Text(worktree.branch ?? worktree.name), + subtitle: Text( + worktree.path, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + trailing: PopupMenuButton( + tooltip: 'Worktree 메뉴', + onSelected: (action) { + if (action == 'archive') { + unawaited(_archiveWorktree(context, ref, worktree)); + } + }, + itemBuilder: (context) => const >[ + PopupMenuItem( + value: 'archive', + child: Text('Archive'), ), - ), + ], + ), + onTap: () => _goWorktree( + context, + WorkspaceSelection( + hostId: hostId, + workspaceId: workspace.id, + worktreeId: worktree.id, + ), + ), + ), ], + ), + ); + + Future _createWorktree(BuildContext context, WidgetRef ref) async { + final api = await _api(ref); + final branches = await api.listGitBranches(workspace.id); + if (!context.mounted) return; + final draft = await showDialog<_WorktreeDraft>( + context: context, + builder: (context) => _CreateWorktreeDialog(branches: branches), + ); + if (draft == null || !context.mounted) return; + final worktree = await api.createWorktree( + id: ref.read(appIdGeneratorProvider).generate(), + workspaceId: workspace.id, + mode: draft.mode, + branchName: draft.branchName, + baseBranch: draft.baseBranch, + ); + await ref + .read(workspaceCatalogControllerProvider.notifier) + .refreshHost(hostId); + if (!context.mounted) return; + _goWorktree( + context, + WorkspaceSelection( + hostId: hostId, + workspaceId: workspace.id, + worktreeId: worktree.id, + ), ); } - IconData _agentIcon(AgentStatus status) => switch (status) { - AgentStatus.running => Icons.sync, - AgentStatus.waitingForApproval => Icons.approval_outlined, - AgentStatus.failed => Icons.error_outline, - _ => Icons.smart_toy_outlined, - }; - - Future _createAgent( + Future _archiveWorktree( BuildContext context, WidgetRef ref, - String workspaceId, + WorktreeDto worktree, ) async { - final controller = ref.read(agentsControllerProvider(workspaceId).notifier); - final providerController = ref.read( - providerSettingsControllerProvider.notifier, - ); - final providerState = await ref.read( - providerSettingsControllerProvider.future, - ); - final connections = providerState?.connections - .where( - (connection) => - connection.status == ProviderConnectionStatus.connected || - connection.status == ProviderConnectionStatus.degraded, - ) - .toList(growable: false); - if (connections == null || connections.isEmpty) return; - var connectionId = connections - .where((connection) => connection.isDefault) - .firstOrNull - ?.id; - connectionId ??= connections.first.id; - await providerController.loadModels(connectionId); + final api = await _api(ref); + final preview = await api.previewWorktreeArchive(worktree.id); if (!context.mounted) return; - final availableModels = - ref - .read(providerSettingsControllerProvider) - .asData - ?.value - ?.models[connectionId] ?? - const []; - final draft = await showDialog<_AgentDraft>( + if (preview.runningSessionCount > 0) { + await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Archive할 수 없습니다'), + content: Text( + '실행 중인 session ${preview.runningSessionCount}개를 먼저 ' + '중지하세요.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('확인'), + ), + ], + ), + ); + return; + } + final risky = preview.dirty || preview.unpushedCommitCount > 0; + final dirtyWarning = preview.dirty ? '커밋하지 않은 변경이 있습니다.\n' : ''; + final unpushedWarning = preview.unpushedCommitCount > 0 + ? '${preview.unpushedCommitCount}개의 push하지 않은 commit이 있습니다.\n' + : ''; + final removalWarning = preview.removesDirectory + ? 'Coder가 만든 checkout 디렉터리가 제거됩니다.' + : '등록만 숨기고 디스크의 checkout은 유지합니다.'; + final confirmed = await showDialog( context: context, - builder: (context) => _AgentDraftDialog( - connections: connections, - connectionId: connectionId!, - models: availableModels, - loadModels: (value) async { - await providerController.loadModels(value); - return ref - .read(providerSettingsControllerProvider) - .asData - ?.value - ?.models[value] ?? - const []; - }, + builder: (context) => AlertDialog( + title: Text('${worktree.name}을 Archive할까요?'), + content: Text('$dirtyWarning$unpushedWarning$removalWarning'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('취소'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: Text(risky ? '위험을 확인하고 Archive' : 'Archive'), + ), + ], ), ); - if (draft == null || !context.mounted) return; - final agent = await controller.create( - title: draft.title, - providerConnectionId: draft.providerConnectionId, - model: draft.model, - reasoningEffort: draft.reasoningEffort, - permissionMode: draft.permissionMode, - ); - if (context.mounted) { - AgentRoute( - hostId: hostId, - workspaceId: workspaceId, - agentId: agent.id, - ).go(context); + if (confirmed != true) return; + await api.archiveWorktree(worktree.id, force: risky); + await ref + .read(workspaceCatalogControllerProvider.notifier) + .refreshHost(hostId); + if (context.mounted && selected?.worktreeId == worktree.id) { + const WorkspaceHomeRoute().go(context); } } -} -typedef _ModelLoader = - Future> Function( - String connectionId, - ); + Future _api(WidgetRef ref) async { + final runtime = (await ref.read( + hostRegistryControllerProvider.future, + )).runtimes[hostId]; + return runtime?.api ?? + (throw StateError('Online daemon connection required.')); + } +} -final class _AgentDraft { - const _AgentDraft({ - required this.title, - required this.providerConnectionId, - required this.model, - required this.reasoningEffort, - required this.permissionMode, +final class _WorktreeDraft { + const _WorktreeDraft({ + required this.mode, + required this.branchName, + this.baseBranch, }); - final String title; - final String providerConnectionId; - final String model; - final String reasoningEffort; - final PermissionMode permissionMode; + final WorktreeCreateMode mode; + final String branchName; + final String? baseBranch; } -class _AgentDraftDialog extends StatefulWidget { - const _AgentDraftDialog({ - required this.connections, - required this.connectionId, - required this.models, - required this.loadModels, - }); +class _CreateWorktreeDialog extends StatefulWidget { + const _CreateWorktreeDialog({required this.branches}); - final List connections; - final String connectionId; - final List models; - final _ModelLoader loadModels; + final List branches; @override - State<_AgentDraftDialog> createState() => _AgentDraftDialogState(); + State<_CreateWorktreeDialog> createState() => _CreateWorktreeDialogState(); } -class _AgentDraftDialogState extends State<_AgentDraftDialog> { - late final TextEditingController _title; - late final TextEditingController _model; - late String _connectionId; - late List _models; - PermissionMode _permission = PermissionMode.ask; - var _reasoningEffort = 'medium'; +class _CreateWorktreeDialogState extends State<_CreateWorktreeDialog> { + final _branch = TextEditingController(); + WorktreeCreateMode _mode = WorktreeCreateMode.newBranch; + String? _baseBranch; @override void initState() { super.initState(); - _connectionId = widget.connectionId; - _models = widget.models; - _title = TextEditingController(text: 'Coding session'); - _model = TextEditingController( - text: - widget.connections - .where((item) => item.id == _connectionId) - .firstOrNull - ?.defaultModelId ?? - '', - ); + _baseBranch = widget.branches + .where((item) => item.current) + .firstOrNull + ?.name; } @override void dispose() { - _title.dispose(); - _model.dispose(); + _branch.dispose(); super.dispose(); } @override Widget build(BuildContext context) => AlertDialog( - title: const Text('새 agent'), + title: const Text('새 worktree'), content: Column( mainAxisSize: MainAxisSize.min, children: [ - TextField( - controller: _title, - decoration: const InputDecoration(labelText: '이름'), - ), - const SizedBox(height: 12), - DropdownButtonFormField( - initialValue: _connectionId, - decoration: const InputDecoration(labelText: 'API provider'), - items: widget.connections - .map( - (value) => DropdownMenuItem( - value: value.id, - child: Text(value.displayName), - ), - ) - .toList(), - onChanged: _selectProvider, - ), - const SizedBox(height: 12), - DropdownMenu( - controller: _model, - enableFilter: true, - expandedInsets: EdgeInsets.zero, - label: const Text('Model ID'), - dropdownMenuEntries: _models - .map( - (item) => DropdownMenuEntry(value: item.id, label: item.label), - ) - .toList(), - ), - const SizedBox(height: 12), - DropdownButtonFormField( - initialValue: _reasoningEffort, - decoration: const InputDecoration(labelText: 'Reasoning effort'), - items: const ['none', 'low', 'medium', 'high', 'xhigh'] - .map( - (value) => DropdownMenuItem(value: value, child: Text(value)), - ) - .toList(), - onChanged: (value) => - setState(() => _reasoningEffort = value ?? _reasoningEffort), - ), - const SizedBox(height: 12), - DropdownButtonFormField( - initialValue: _permission, - decoration: const InputDecoration(labelText: 'Permission mode'), - items: PermissionMode.values - .map( - (value) => DropdownMenuItem( - value: value, - child: Text(value.name), - ), - ) - .toList(), - onChanged: (value) => - setState(() => _permission = value ?? PermissionMode.ask), + SegmentedButton( + segments: const >[ + ButtonSegment( + value: WorktreeCreateMode.newBranch, + label: Text('새 branch'), + ), + ButtonSegment( + value: WorktreeCreateMode.existingBranch, + label: Text('기존 branch'), + ), + ], + selected: {_mode}, + onSelectionChanged: (value) => setState(() { + _mode = value.single; + _branch.clear(); + }), ), + const SizedBox(height: 16), + if (_mode == WorktreeCreateMode.newBranch) ...[ + TextField( + controller: _branch, + decoration: const InputDecoration(labelText: '새 branch 이름'), + ), + const SizedBox(height: 12), + DropdownButtonFormField( + initialValue: _baseBranch, + decoration: const InputDecoration(labelText: 'Base branch'), + items: widget.branches + .map( + (branch) => DropdownMenuItem( + value: branch.name, + child: Text(branch.name), + ), + ) + .toList(growable: false), + onChanged: (value) => _baseBranch = value, + ), + ] else + DropdownButtonFormField( + decoration: const InputDecoration(labelText: 'Local branch'), + items: widget.branches + .where((branch) => !branch.checkedOut) + .map( + (branch) => DropdownMenuItem( + value: branch.name, + child: Text(branch.name), + ), + ) + .toList(growable: false), + onChanged: (value) => _branch.text = value ?? '', + ), ], ), actions: [ @@ -708,46 +887,398 @@ class _AgentDraftDialogState extends State<_AgentDraftDialog> { onPressed: () => Navigator.pop(context), child: const Text('취소'), ), - FilledButton(onPressed: _submit, child: const Text('생성')), + FilledButton( + onPressed: () { + final branch = _branch.text.trim(); + if (branch.isEmpty) return; + Navigator.pop( + context, + _WorktreeDraft( + mode: _mode, + branchName: branch, + baseBranch: _mode == WorktreeCreateMode.newBranch + ? _baseBranch + : null, + ), + ); + }, + child: const Text('생성'), + ), ], ); +} - Future _selectProvider(String? value) async { - if (value == null) return; - final models = await widget.loadModels(value); - if (!mounted) return; - final provider = widget.connections - .where((item) => item.id == value) - .firstOrNull; - setState(() { - _connectionId = value; - _models = models; - _model.text = provider?.defaultModelId ?? ''; - }); +class _NoDaemonState extends StatelessWidget { + const _NoDaemonState({required this.onSettings}); + + final VoidCallback onSettings; + + @override + Widget build(BuildContext context) => Center( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text('설정된 daemon이 없습니다.'), + const SizedBox(height: 12), + OutlinedButton( + onPressed: onSettings, + child: const Text('Daemon 설정'), + ), + ], + ), + ), + ); +} + +class _EmptyWorkspaceDetail extends StatelessWidget { + const _EmptyWorkspaceDetail(); + + @override + Widget build(BuildContext context) => const Center( + child: Text('왼쪽 트리에서 checkout 또는 worktree를 선택하세요.'), + ); +} + +class _RemoteDirectoryDialog extends StatefulWidget { + const _RemoteDirectoryDialog({required this.api}); + + final CoderApi api; + + @override + State<_RemoteDirectoryDialog> createState() => _RemoteDirectoryDialogState(); +} + +class _RemoteDirectoryDialogState extends State<_RemoteDirectoryDialog> { + final _path = TextEditingController(); + List _suggestions = const []; + + @override + void dispose() { + _path.dispose(); + super.dispose(); } - void _submit() { - final model = _model.text.trim(); - if (model.isEmpty) return; - final title = _title.text.trim(); - Navigator.pop( - context, - _AgentDraft( - title: title.isEmpty ? 'Coding session' : title, - providerConnectionId: _connectionId, - model: model, - reasoningEffort: _reasoningEffort, - permissionMode: _permission, + @override + Widget build(BuildContext context) => AlertDialog( + title: const Text('Daemon의 폴더 선택'), + content: SizedBox( + width: 520, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TextField( + controller: _path, + autofocus: true, + decoration: const InputDecoration( + labelText: 'Daemon 경로', + hintText: '/srv/repositories/project', + ), + onChanged: _search, + ), + for (final suggestion in _suggestions) + ListTile( + dense: true, + leading: const Icon(Icons.folder_outlined), + title: Text(suggestion.name), + subtitle: Text(suggestion.path), + onTap: () => setState(() => _path.text = suggestion.path), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('취소'), + ), + FilledButton( + onPressed: () { + final value = _path.text.trim(); + if (value.isNotEmpty) Navigator.pop(context, value); + }, + child: const Text('등록'), ), + ], + ); + + Future _search(String query) async { + final suggestions = await widget.api.suggestDirectories(query); + if (mounted) setState(() => _suggestions = suggestions); + } +} + +class _SessionArea extends ConsumerStatefulWidget { + const _SessionArea({ + required this.selection, + this.requestedAgentId, + this.showBack = false, + }); + + final WorkspaceSelection selection; + final String? requestedAgentId; + final bool showBack; + + @override + ConsumerState<_SessionArea> createState() => _SessionAreaState(); +} + +class _SessionAreaState extends ConsumerState<_SessionArea> { + bool _requestedOpened = false; + + @override + Widget build(BuildContext context) { + final provider = sessionTabsControllerProvider(widget.selection); + final tabs = ref.watch(provider); + final state = tabs.asData?.value; + if (!_requestedOpened && + widget.requestedAgentId != null && + state != null && + state.sessions.any((item) => item.id == widget.requestedAgentId)) { + _requestedOpened = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + unawaited( + ref.read(provider.notifier).open(widget.requestedAgentId!), + ); + } + }); + } + return Column( + children: [ + SizedBox( + height: 48, + child: Row( + children: [ + if (widget.showBack) + IconButton( + onPressed: () => const WorkspaceHomeRoute().go(context), + icon: const Icon(Icons.arrow_back), + ), + Expanded( + child: state == null + ? const LinearProgressIndicator() + : ListView( + scrollDirection: Axis.horizontal, + children: [ + for (final id in state.openAgentIds) + _SessionTab( + agent: state.sessions + .where((item) => item.id == id) + .first, + selected: state.selectedAgentId == id, + onSelect: () => _select(id), + onClose: () => _close(id), + ), + ], + ), + ), + IconButton( + tooltip: '새 session', + onPressed: state == null ? null : _createSession, + icon: const Icon(Icons.add), + ), + if (state != null) + PopupMenuButton( + tooltip: '모든 session', + icon: const Icon(Icons.more_horiz), + onSelected: _open, + itemBuilder: (context) => >[ + for (final agent in state.sessions) + PopupMenuItem( + value: agent.id, + child: Text(agent.title), + ), + ], + ), + ], + ), + ), + const Divider(height: 1), + Expanded( + child: state?.selectedAgentId == null + ? _NoSession(onCreate: _createSession) + : _ConversationPane( + selection: widget.selection, + agent: state!.sessions + .where((item) => item.id == state.selectedAgentId) + .first, + ), + ), + ], + ); + } + + Future _select(String id) async { + await ref + .read(sessionTabsControllerProvider(widget.selection).notifier) + .select(id); + if (mounted) _goSession(context, widget.selection, id); + } + + Future _open(String id) async { + await ref + .read(sessionTabsControllerProvider(widget.selection).notifier) + .open(id); + if (mounted) _goSession(context, widget.selection, id); + } + + Future _close(String id) async { + final notifier = ref.read( + sessionTabsControllerProvider(widget.selection).notifier, ); + await notifier.close(id); + if (!mounted) return; + final selected = ref + .read(sessionTabsControllerProvider(widget.selection)) + .requireValue + .selectedAgentId; + if (selected == null) { + _goWorktree(context, widget.selection); + } else { + _goSession(context, widget.selection, selected); + } + } + + Future _createSession() async { + final providerState = await ref.read( + providerSettingsControllerProvider(widget.selection.hostId).future, + ); + final connections = providerState?.connections + .where( + (item) => + item.status == ProviderConnectionStatus.connected || + item.status == ProviderConnectionStatus.degraded, + ) + .toList(growable: false); + if (connections == null || connections.isEmpty || !mounted) return; + final defaultConnection = + connections.where((item) => item.isDefault).firstOrNull ?? + connections.first; + final title = await showDialog( + context: context, + builder: (context) => const _SessionNameDialog(), + ); + if (title == null || !mounted) return; + final model = defaultConnection.defaultModelId; + if (model == null) return; + final agent = await ref + .read( + agentsControllerProvider( + widget.selection.hostId, + widget.selection.worktreeId, + ).notifier, + ) + .create( + title: title, + providerConnectionId: defaultConnection.id, + model: model, + reasoningEffort: 'medium', + permissionMode: PermissionMode.ask, + ); + await ref + .read(sessionTabsControllerProvider(widget.selection).notifier) + .add(agent); + if (mounted) _goSession(context, widget.selection, agent.id); + } +} + +class _SessionTab extends StatelessWidget { + const _SessionTab({ + required this.agent, + required this.selected, + required this.onSelect, + required this.onClose, + }); + + final AgentDto agent; + final bool selected; + final VoidCallback onSelect; + final VoidCallback onClose; + + @override + Widget build(BuildContext context) => Material( + color: selected + ? Theme.of(context).colorScheme.secondaryContainer + : Colors.transparent, + child: InkWell( + onTap: onSelect, + child: Padding( + padding: const EdgeInsets.only(left: 14), + child: Row( + children: [ + Text(agent.title), + IconButton( + visualDensity: VisualDensity.compact, + tooltip: '탭 닫기', + onPressed: onClose, + icon: const Icon(Icons.close, size: 16), + ), + ], + ), + ), + ), + ); +} + +class _SessionNameDialog extends StatefulWidget { + const _SessionNameDialog(); + + @override + State<_SessionNameDialog> createState() => _SessionNameDialogState(); +} + +class _SessionNameDialogState extends State<_SessionNameDialog> { + final _title = TextEditingController(text: 'Coding session'); + + @override + void dispose() { + _title.dispose(); + super.dispose(); } + + @override + Widget build(BuildContext context) => AlertDialog( + title: const Text('새 session'), + content: TextField( + controller: _title, + autofocus: true, + decoration: const InputDecoration(labelText: '이름'), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('취소'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, _title.text.trim()), + child: const Text('생성'), + ), + ], + ); +} + +class _NoSession extends StatelessWidget { + const _NoSession({required this.onCreate}); + + final VoidCallback onCreate; + + @override + Widget build(BuildContext context) => Center( + child: FilledButton.icon( + onPressed: onCreate, + icon: const Icon(Icons.add), + label: const Text('새 session 시작'), + ), + ); } class _ConversationPane extends ConsumerStatefulWidget { - const _ConversationPane({required this.workspaceId, required this.agentId}); + const _ConversationPane({required this.selection, required this.agent}); - final String? workspaceId; - final String? agentId; + final WorkspaceSelection selection; + final AgentDto agent; @override ConsumerState<_ConversationPane> createState() => _ConversationPaneState(); @@ -764,78 +1295,76 @@ class _ConversationPaneState extends ConsumerState<_ConversationPane> { @override Widget build(BuildContext context) { - final agents = ref - .watch(agentsControllerProvider(widget.workspaceId)) - .asData - ?.value; - final selected = (agents ?? const []) - .where((item) => item.id == widget.agentId) - .firstOrNull; - if (widget.agentId == null || selected == null) { - return const Center(child: Text('Agent를 선택하세요.')); - } + final current = + ref + .watch( + agentsControllerProvider( + widget.selection.hostId, + widget.selection.worktreeId, + ), + ) + .asData + ?.value + .where((item) => item.id == widget.agent.id) + .firstOrNull ?? + widget.agent; final busy = - selected.status == AgentStatus.running || - selected.status == AgentStatus.waitingForApproval; + current.status == AgentStatus.running || + current.status == AgentStatus.waitingForApproval; final conversation = ref.watch( - conversationControllerProvider(widget.agentId), + conversationControllerProvider(widget.selection.hostId, current.id), ); - final conversationState = conversation.asData?.value; - final displayTimeline = _coalesceAssistantDeltas( - conversationState?.timeline ?? const [], + final value = conversation.asData?.value; + final timeline = _coalesceAssistantDeltas( + value?.timeline ?? const [], ); return Column( children: [ ListTile( - title: Text(selected.title), + title: Text(current.title), subtitle: Text( - '${selected.providerConnectionId}/${selected.model} · ' - '${selected.reasoningEffort} · ${selected.permissionMode.name}', + '${current.providerConnectionId}/${current.model} · ' + '${current.permissionMode.name}', ), trailing: busy - ? TextButton.icon( + ? IconButton( + tooltip: '중지', onPressed: () => ref .read( - conversationControllerProvider(widget.agentId).notifier, + conversationControllerProvider( + widget.selection.hostId, + current.id, + ).notifier, ) .cancelTurn(), icon: const Icon(Icons.stop_circle_outlined), - label: const Text('중지'), - ) - : displayTimeline.isEmpty - ? IconButton( - tooltip: 'Agent 모델 설정', - onPressed: () => _editAgentConfiguration(selected), - icon: const Icon(Icons.tune), ) : null, ), - const Divider(height: 1), Expanded( - child: displayTimeline.isEmpty - ? const Center(child: Text('요청을 입력해 coding agent를 시작하세요.')) + child: timeline.isEmpty + ? const Center(child: Text('코딩 요청을 입력하세요.')) : ListView.separated( reverse: true, padding: const EdgeInsets.all(20), - itemCount: displayTimeline.length, + itemCount: timeline.length, separatorBuilder: (_, _) => const SizedBox(height: 10), - itemBuilder: (context, index) { - final event = - displayTimeline[displayTimeline.length - index - 1]; - return TimelineCard(event: event); - }, + itemBuilder: (context, index) => TimelineCard( + event: timeline[timeline.length - index - 1], + ), ), ), for (final approval - in conversationState?.approvals.values ?? - const []) - ApprovalCard(approval: approval), + in value?.approvals.values ?? const []) + ApprovalCard( + hostId: widget.selection.hostId, + approval: approval, + ), SafeArea( top: false, child: Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), child: Row( - crossAxisAlignment: CrossAxisAlignment.end, children: [ Expanded( child: TextField( @@ -847,12 +1376,12 @@ class _ConversationPaneState extends ConsumerState<_ConversationPane> { hintText: '코딩 요청을 입력하세요…', border: OutlineInputBorder(), ), - onSubmitted: busy ? null : (_) => _send(), + onSubmitted: busy ? null : (_) => _send(current.id), ), ), const SizedBox(width: 8), IconButton.filled( - onPressed: busy ? null : _send, + onPressed: busy ? null : () => _send(current.id), icon: const Icon(Icons.arrow_upward), ), ], @@ -863,225 +1392,77 @@ class _ConversationPaneState extends ConsumerState<_ConversationPane> { ); } - Future _editAgentConfiguration(AgentDto agent) async { - final workspaceId = widget.workspaceId; - if (workspaceId == null) return; - final controller = ref.read(agentsControllerProvider(workspaceId).notifier); - final providerController = ref.read( - providerSettingsControllerProvider.notifier, - ); - final providerState = await ref.read( - providerSettingsControllerProvider.future, - ); - if (providerState == null) return; - await providerController.loadModels(agent.providerConnectionId); - if (!mounted) return; - final models = - ref - .read(providerSettingsControllerProvider) - .asData - ?.value - ?.models[agent.providerConnectionId] ?? - const []; - final draft = await showDialog<_AgentConfigurationDraft>( - context: context, - builder: (context) => _AgentConfigurationDialog( - agent: agent, - connections: providerState.connections, - models: models, - loadModels: (connectionId) async { - await providerController.loadModels(connectionId); - return ref - .read(providerSettingsControllerProvider) - .asData - ?.value - ?.models[connectionId] ?? - const []; - }, - ), - ); - if (draft == null) return; - await controller.updateConfiguration( - agentId: agent.id, - providerConnectionId: draft.providerConnectionId, - model: draft.model, - reasoningEffort: draft.reasoningEffort, - ); - } - - Future _send() async { - final text = _composer.text; - if (text.trim().isEmpty) return; + Future _send(String agentId) async { + final text = _composer.text.trim(); + if (text.isEmpty) return; _composer.clear(); await ref - .read(conversationControllerProvider(widget.agentId).notifier) + .read( + conversationControllerProvider( + widget.selection.hostId, + agentId, + ).notifier, + ) .startTurn(text); } +} - List _coalesceAssistantDeltas( - List events, - ) { - final result = []; - for (final event in events) { - if (event.type == 'assistant.delta' && - result.isNotEmpty && - result.last.type == 'assistant.delta' && - result.last.turnId == event.turnId) { - final previous = result.removeLast(); - result.add( - previous.copyWith( - data: { - 'text': - '${previous.data['text'] as String? ?? ''}' - '${event.data['text'] as String? ?? ''}', - }, - ), - ); - } else { - result.add(event); - } +List _coalesceAssistantDeltas( + List events, +) { + final result = []; + for (final event in events) { + if (event.type == 'assistant.delta' && + result.isNotEmpty && + result.last.type == 'assistant.delta' && + result.last.turnId == event.turnId) { + final previous = result.removeLast(); + result.add( + previous.copyWith( + data: { + 'text': + '${previous.data['text'] as String? ?? ''}' + '${event.data['text'] as String? ?? ''}', + }, + ), + ); + } else { + result.add(event); } - return result; } + return result; } -final class _AgentConfigurationDraft { - const _AgentConfigurationDraft({ - required this.providerConnectionId, - required this.model, - required this.reasoningEffort, - }); - - final String providerConnectionId; - final String model; - final String reasoningEffort; +void _goWorktree(BuildContext context, WorkspaceSelection selection) { + WorktreeRoute( + hostId: selection.hostId, + workspaceId: selection.workspaceId, + worktreeId: selection.worktreeId, + ).go(context); } -class _AgentConfigurationDialog extends StatefulWidget { - const _AgentConfigurationDialog({ - required this.agent, - required this.connections, - required this.models, - required this.loadModels, - }); - - final AgentDto agent; - final List connections; - final List models; - final _ModelLoader loadModels; - - @override - State<_AgentConfigurationDialog> createState() => - _AgentConfigurationDialogState(); +void _goSession( + BuildContext context, + WorkspaceSelection selection, + String agentId, +) { + SessionRoute( + hostId: selection.hostId, + workspaceId: selection.workspaceId, + worktreeId: selection.worktreeId, + agentId: agentId, + ).go(context); } -class _AgentConfigurationDialogState extends State<_AgentConfigurationDialog> { - late final TextEditingController _model; - late String _connectionId; - late String _reasoningEffort; - late List _models; - - @override - void initState() { - super.initState(); - _connectionId = widget.agent.providerConnectionId; - _reasoningEffort = widget.agent.reasoningEffort; - _models = widget.models; - _model = TextEditingController(text: widget.agent.model); - } - - @override - void dispose() { - _model.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) => AlertDialog( - title: const Text('Agent 모델 설정'), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - DropdownButtonFormField( - initialValue: _connectionId, - decoration: const InputDecoration(labelText: 'API provider'), - items: widget.connections - .where( - (item) => - item.status == ProviderConnectionStatus.connected || - item.status == ProviderConnectionStatus.degraded, - ) - .map( - (item) => DropdownMenuItem( - value: item.id, - child: Text(item.displayName), - ), - ) - .toList(), - onChanged: _selectProvider, - ), - const SizedBox(height: 12), - DropdownMenu( - controller: _model, - enableFilter: true, - expandedInsets: EdgeInsets.zero, - label: const Text('Model ID'), - dropdownMenuEntries: _models - .map( - (item) => DropdownMenuEntry(value: item.id, label: item.label), - ) - .toList(), - ), - const SizedBox(height: 12), - DropdownButtonFormField( - initialValue: _reasoningEffort, - decoration: const InputDecoration(labelText: 'Reasoning effort'), - items: const ['none', 'low', 'medium', 'high', 'xhigh'] - .map( - (item) => DropdownMenuItem(value: item, child: Text(item)), - ) - .toList(), - onChanged: (value) => - setState(() => _reasoningEffort = value ?? _reasoningEffort), - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('취소'), - ), - FilledButton(onPressed: _submit, child: const Text('저장')), - ], - ); - - Future _selectProvider(String? value) async { - if (value == null) return; - final models = await widget.loadModels(value); - if (!mounted) return; - final provider = widget.connections - .where((item) => item.id == value) - .firstOrNull; - setState(() { - _connectionId = value; - _models = models; - _model.text = provider?.defaultModelId ?? ''; - }); - } - - void _submit() { - final model = _model.text.trim(); - if (model.isEmpty) return; - Navigator.pop( - context, - _AgentConfigurationDraft( - providerConnectionId: _connectionId, - model: model, - reasoningEffort: _reasoningEffort, - ), - ); - } -} +String _hostStatusLabel(HostRuntimeStatus status) => switch (status) { + HostRuntimeStatus.online => '온라인', + HostRuntimeStatus.connecting => '연결 중', + HostRuntimeStatus.reconnecting => '재연결 중', + HostRuntimeStatus.offline => '오프라인', + HostRuntimeStatus.error => '오류', + HostRuntimeStatus.conflict => '중복 daemon', + HostRuntimeStatus.idle => '자동 연결 꺼짐', +}; /// Renders one persisted timeline event. class TimelineCard extends StatelessWidget { @@ -1134,7 +1515,10 @@ class TimelineCard extends StatelessWidget { /// Renders an actionable tool approval request. class ApprovalCard extends ConsumerWidget { /// Creates an [ApprovalCard]. - const ApprovalCard({required this.approval, super.key}); + const ApprovalCard({required this.hostId, required this.approval, super.key}); + + /// Stable host profile containing the approval's agent. + final String hostId; /// The pending approval rendered by this card. final ApprovalRequestDto approval; @@ -1169,6 +1553,7 @@ class ApprovalCard extends ConsumerWidget { onPressed: () => ref .read( conversationControllerProvider( + hostId, approval.agentId, ).notifier, ) @@ -1180,6 +1565,7 @@ class ApprovalCard extends ConsumerWidget { onPressed: () => ref .read( conversationControllerProvider( + hostId, approval.agentId, ).notifier, ) diff --git a/apps/coder_app/lib/src/app.g.dart b/apps/coder_app/lib/src/app.g.dart index e78424e..d147302 100644 --- a/apps/coder_app/lib/src/app.g.dart +++ b/apps/coder_app/lib/src/app.g.dart @@ -7,21 +7,24 @@ part of 'app.dart'; // ************************************************************************** List get $appRoutes => [ - $hostRoute, - $dashboardRoute, - $settingsRoute, - $workspaceRoute, - $agentRoute, + $workspaceHomeRoute, + $worktreeRoute, + $sessionRoute, + $providerSettingsRoute, + $daemonSettingsRoute, + $newHostRoute, + $editHostRoute, ]; -RouteBase get $hostRoute => GoRouteData.$route( +RouteBase get $workspaceHomeRoute => GoRouteData.$route( path: '/', hasOverriddenOnExit: false, - factory: $HostRoute._fromState, + factory: $WorkspaceHomeRoute._fromState, ); -mixin $HostRoute on GoRouteData { - static HostRoute _fromState(GoRouterState state) => const HostRoute(); +mixin $WorkspaceHomeRoute on GoRouteData { + static WorkspaceHomeRoute _fromState(GoRouterState state) => + const WorkspaceHomeRoute(); @override String get location => GoRouteData.$location('/'); @@ -40,21 +43,25 @@ mixin $HostRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -RouteBase get $dashboardRoute => GoRouteData.$route( - path: '/hosts/:hostId', +RouteBase get $worktreeRoute => GoRouteData.$route( + path: '/workspaces/:hostId/:workspaceId/:worktreeId', hasOverriddenOnExit: false, - factory: $DashboardRoute._fromState, + factory: $WorktreeRoute._fromState, ); -mixin $DashboardRoute on GoRouteData { - static DashboardRoute _fromState(GoRouterState state) => - DashboardRoute(hostId: state.pathParameters['hostId']!); +mixin $WorktreeRoute on GoRouteData { + static WorktreeRoute _fromState(GoRouterState state) => WorktreeRoute( + hostId: state.pathParameters['hostId']!, + workspaceId: state.pathParameters['workspaceId']!, + worktreeId: state.pathParameters['worktreeId']!, + ); - DashboardRoute get _self => this as DashboardRoute; + WorktreeRoute get _self => this as WorktreeRoute; @override - String get location => - GoRouteData.$location('/hosts/${Uri.encodeComponent(_self.hostId)}'); + String get location => GoRouteData.$location( + '/workspaces/${Uri.encodeComponent(_self.hostId)}/${Uri.encodeComponent(_self.workspaceId)}/${Uri.encodeComponent(_self.worktreeId)}', + ); @override void go(BuildContext context) => context.go(location); @@ -70,21 +77,25 @@ mixin $DashboardRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -RouteBase get $settingsRoute => GoRouteData.$route( - path: '/hosts/:hostId/settings', +RouteBase get $sessionRoute => GoRouteData.$route( + path: '/workspaces/:hostId/:workspaceId/:worktreeId/sessions/:agentId', hasOverriddenOnExit: false, - factory: $SettingsRoute._fromState, + factory: $SessionRoute._fromState, ); -mixin $SettingsRoute on GoRouteData { - static SettingsRoute _fromState(GoRouterState state) => - SettingsRoute(hostId: state.pathParameters['hostId']!); +mixin $SessionRoute on GoRouteData { + static SessionRoute _fromState(GoRouterState state) => SessionRoute( + hostId: state.pathParameters['hostId']!, + workspaceId: state.pathParameters['workspaceId']!, + worktreeId: state.pathParameters['worktreeId']!, + agentId: state.pathParameters['agentId']!, + ); - SettingsRoute get _self => this as SettingsRoute; + SessionRoute get _self => this as SessionRoute; @override String get location => GoRouteData.$location( - '/hosts/${Uri.encodeComponent(_self.hostId)}/settings', + '/workspaces/${Uri.encodeComponent(_self.hostId)}/${Uri.encodeComponent(_self.workspaceId)}/${Uri.encodeComponent(_self.worktreeId)}/sessions/${Uri.encodeComponent(_self.agentId)}', ); @override @@ -101,23 +112,22 @@ mixin $SettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -RouteBase get $workspaceRoute => GoRouteData.$route( - path: '/hosts/:hostId/workspaces/:workspaceId', +RouteBase get $providerSettingsRoute => GoRouteData.$route( + path: '/settings/providers', hasOverriddenOnExit: false, - factory: $WorkspaceRoute._fromState, + factory: $ProviderSettingsRoute._fromState, ); -mixin $WorkspaceRoute on GoRouteData { - static WorkspaceRoute _fromState(GoRouterState state) => WorkspaceRoute( - hostId: state.pathParameters['hostId']!, - workspaceId: state.pathParameters['workspaceId']!, - ); +mixin $ProviderSettingsRoute on GoRouteData { + static ProviderSettingsRoute _fromState(GoRouterState state) => + ProviderSettingsRoute(hostId: state.uri.queryParameters['host-id']); - WorkspaceRoute get _self => this as WorkspaceRoute; + ProviderSettingsRoute get _self => this as ProviderSettingsRoute; @override String get location => GoRouteData.$location( - '/hosts/${Uri.encodeComponent(_self.hostId)}/workspaces/${Uri.encodeComponent(_self.workspaceId)}', + '/settings/providers', + queryParams: {if (_self.hostId != null) 'host-id': _self.hostId}, ); @override @@ -134,24 +144,74 @@ mixin $WorkspaceRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } -RouteBase get $agentRoute => GoRouteData.$route( - path: '/hosts/:hostId/workspaces/:workspaceId/agents/:agentId', +RouteBase get $daemonSettingsRoute => GoRouteData.$route( + path: '/settings/daemons', hasOverriddenOnExit: false, - factory: $AgentRoute._fromState, + factory: $DaemonSettingsRoute._fromState, ); -mixin $AgentRoute on GoRouteData { - static AgentRoute _fromState(GoRouterState state) => AgentRoute( - hostId: state.pathParameters['hostId']!, - workspaceId: state.pathParameters['workspaceId']!, - agentId: state.pathParameters['agentId']!, - ); +mixin $DaemonSettingsRoute on GoRouteData { + static DaemonSettingsRoute _fromState(GoRouterState state) => + const DaemonSettingsRoute(); + + @override + String get location => GoRouteData.$location('/settings/daemons'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $newHostRoute => GoRouteData.$route( + path: '/settings/daemons/new', + hasOverriddenOnExit: false, + factory: $NewHostRoute._fromState, +); + +mixin $NewHostRoute on GoRouteData { + static NewHostRoute _fromState(GoRouterState state) => const NewHostRoute(); + + @override + String get location => GoRouteData.$location('/settings/daemons/new'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $editHostRoute => GoRouteData.$route( + path: '/settings/daemons/:hostId', + hasOverriddenOnExit: false, + factory: $EditHostRoute._fromState, +); + +mixin $EditHostRoute on GoRouteData { + static EditHostRoute _fromState(GoRouterState state) => + EditHostRoute(hostId: state.pathParameters['hostId']!); - AgentRoute get _self => this as AgentRoute; + EditHostRoute get _self => this as EditHostRoute; @override String get location => GoRouteData.$location( - '/hosts/${Uri.encodeComponent(_self.hostId)}/workspaces/${Uri.encodeComponent(_self.workspaceId)}/agents/${Uri.encodeComponent(_self.agentId)}', + '/settings/daemons/${Uri.encodeComponent(_self.hostId)}', ); @override diff --git a/apps/coder_app/lib/src/app_services.dart b/apps/coder_app/lib/src/app_services.dart new file mode 100644 index 0000000..ac6849e --- /dev/null +++ b/apps/coder_app/lib/src/app_services.dart @@ -0,0 +1,101 @@ +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_client/coder_client.dart'; + +/// Opens and handshakes one daemon client. +typedef CoderClientOpener = + Future Function({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }); + +/// Composition-root dependencies required by the daemon-independent app shell. +final class AppServices { + /// Creates application services. + const AppServices({ + required this.settings, + required this.profiles, + required this.credentials, + required this.clients, + required this.clientKind, + this.embeddedLauncher, + this.delay = const SystemAppDelay(), + }); + + /// Device-local app settings repository. + final AppSettingsRepository settings; + + /// Non-secret remote profile repository. + final RemoteHostRepository profiles; + + /// Secure remote bearer-token store. + final RemoteHostCredentialStore credentials; + + /// WebSocket client factory. + final HostClientFactory clients; + + /// Handshake client kind for diagnostics and feature policy. + final String clientKind; + + /// Desktop-only app-owned daemon launcher. + final EmbeddedDaemonLauncher? embeddedLauncher; + + /// Delay adapter used by independent initial reconnect loops. + final AppDelay delay; + + /// Whether this platform can own an embedded daemon. + bool get supportsEmbeddedDaemon => embeddedLauncher != null; +} + +/// Production WebSocket implementation of [HostClientFactory]. +final class WebSocketHostClientFactory implements HostClientFactory { + /// Creates the production host client factory. + const WebSocketHostClientFactory({this.openClient = _openCoderClient}); + + /// Injected typed client opener. + final CoderClientOpener openClient; + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) async { + try { + return await openClient( + endpoint: endpoint, + credentials: credentials, + clientId: clientId, + clientKind: clientKind, + ); + } on CoderClientException catch (error) { + if (error.code == 'protocol_mismatch') { + throw HostConnectionFailure.protocolMismatch(error.message); + } + rethrow; + } on Exception catch (error) { + final message = '$error'; + if (message.contains('401') || message.contains('403')) { + throw const HostConnectionFailure.authentication( + 'Daemon이 bearer token을 거부했습니다.', + ); + } + throw HostConnectionFailure.network(message); + } + } +} + +Future _openCoderClient({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, +}) => CoderClient.connect( + endpoint: endpoint, + credentials: credentials, + clientId: clientId, + clientKind: clientKind, +); diff --git a/apps/coder_app/lib/src/app_settings_page.dart b/apps/coder_app/lib/src/app_settings_page.dart new file mode 100644 index 0000000..d79321d --- /dev/null +++ b/apps/coder_app/lib/src/app_settings_page.dart @@ -0,0 +1,425 @@ +import 'package:coder_app/src/controller.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +/// Daemon-independent app settings and remote host management. +class AppSettingsPage extends ConsumerWidget { + /// Creates the global application settings page. + const AppSettingsPage({this.embedded = false, super.key}); + + /// Whether the unified settings shell supplies navigation chrome. + final bool embedded; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final state = ref.watch(hostRegistryControllerProvider); + final supportsEmbedded = ref + .read(appServicesProvider) + .supportsEmbeddedDaemon; + final body = state.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) => Center(child: Text('$error')), + data: (registry) => _settingsBody( + context, + ref, + registry, + supportsEmbedded: supportsEmbedded, + ), + ); + if (embedded) return body; + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => context.go('/'), + icon: const Icon(Icons.arrow_back), + ), + title: const Text('앱 설정'), + ), + body: body, + ); + } + + Widget _settingsBody( + BuildContext context, + WidgetRef ref, + HostRegistryState registry, { + required bool supportsEmbedded, + }) => ListView( + padding: const EdgeInsets.all(24), + children: [ + if (supportsEmbedded) ...[ + Text( + '로컬 실행', + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: 8), + Card( + child: SwitchListTile( + title: const Text('내장 daemon'), + subtitle: const Text( + '앱과 함께 시작하고 앱 종료 시 중지합니다. 시작 실패는 앱 사용을 막지 않습니다.', + ), + value: registry.settings.embeddedDaemonEnabled, + onChanged: (enabled) => _toggleEmbedded( + context, + ref, + currentlyEnabled: registry.settings.embeddedDaemonEnabled, + enabled: enabled, + ), + ), + ), + const SizedBox(height: 24), + ], + Row( + children: [ + Expanded( + child: Text( + '원격 daemons', + style: Theme.of(context).textTheme.titleLarge, + ), + ), + FilledButton.icon( + onPressed: () => context.go('/settings/daemons/new'), + icon: const Icon(Icons.add), + label: const Text('원격 daemon 추가'), + ), + ], + ), + const SizedBox(height: 8), + if (registry.profiles.isEmpty) + const Card( + child: Padding( + padding: EdgeInsets.all(24), + child: Text('저장된 원격 daemon이 없습니다.'), + ), + ), + for (final profile in registry.profiles) + _RemoteHostCard( + profile: profile, + runtime: registry.runtimes[profile.id], + ), + ], + ); + + Future _toggleEmbedded( + BuildContext context, + WidgetRef ref, { + required bool currentlyEnabled, + required bool enabled, + }) async { + if (currentlyEnabled && !enabled) { + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('내장 daemon을 중지할까요?'), + content: const Text( + '이 앱이 소유한 daemon과 연결만 중지합니다. 원격 및 standalone daemon은 영향을 받지 않습니다.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('취소'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('중지'), + ), + ], + ), + ); + if (confirmed != true) return; + } + await ref + .read(hostRegistryControllerProvider.notifier) + .setEmbeddedDaemonEnabled(enabled: enabled); + } +} + +class _RemoteHostCard extends ConsumerWidget { + const _RemoteHostCard({required this.profile, required this.runtime}); + + final RemoteDaemonProfile profile; + final HostRuntimeSnapshot? runtime; + + @override + Widget build(BuildContext context, WidgetRef ref) => Card( + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + children: [ + ListTile( + leading: Icon(_statusIcon(runtime?.status)), + title: Text(profile.label), + subtitle: Text( + '${profile.websocketUri}\n${_statusText(runtime)}', + ), + isThreeLine: true, + trailing: IconButton( + tooltip: '연결 편집', + onPressed: () => context.go('/settings/daemons/${profile.id}'), + icon: const Icon(Icons.edit_outlined), + ), + ), + SwitchListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + title: const Text('앱 시작 시 자동 연결'), + value: profile.autoConnect, + onChanged: (enabled) => ref + .read(hostRegistryControllerProvider.notifier) + .setRemoteAutoConnect(profile.id, enabled: enabled), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + onPressed: () => ref + .read(hostRegistryControllerProvider.notifier) + .reconnect(profile.id), + icon: const Icon(Icons.refresh), + label: const Text('다시 연결'), + ), + if (runtime?.connected == true) + TextButton.icon( + onPressed: () => context.go( + '/settings/providers?hostId=${profile.id}', + ), + icon: const Icon(Icons.hub_outlined), + label: const Text('Provider 설정'), + ), + ], + ), + ], + ), + ), + ); +} + +/// Add/edit form for one remote daemon profile. +class RemoteHostEditPage extends ConsumerStatefulWidget { + /// Creates an add form when [hostId] is null, otherwise an edit form. + const RemoteHostEditPage({this.hostId, super.key}); + + /// Existing profile ID for edit mode. + final String? hostId; + + @override + ConsumerState createState() => _RemoteHostEditPageState(); +} + +class _RemoteHostEditPageState extends ConsumerState { + final _label = TextEditingController(); + final _address = TextEditingController(); + final _token = TextEditingController(); + bool _autoConnect = true; + bool _initialized = false; + bool _saving = false; + String? _error; + + @override + void initState() { + super.initState(); + _address.addListener(_addressChanged); + } + + void _addressChanged() { + if (mounted) setState(() {}); + } + + @override + void dispose() { + _address.removeListener(_addressChanged); + _label.dispose(); + _address.dispose(); + _token.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final registry = ref.watch(hostRegistryControllerProvider).asData?.value; + final existing = registry?.profiles + .where((profile) => profile.id == widget.hostId) + .firstOrNull; + if (!_initialized && (widget.hostId == null || registry != null)) { + _initialized = true; + if (existing != null) { + _label.text = existing.label; + _address.text = existing.websocketUri.toString(); + _autoConnect = existing.autoConnect; + } + } + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () => context.go('/settings/daemons'), + icon: const Icon(Icons.arrow_back), + ), + title: Text(existing == null ? '원격 daemon 추가' : '원격 daemon 편집'), + ), + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 620), + child: ListView( + padding: const EdgeInsets.all(24), + children: [ + TextField( + key: const ValueKey('remote-host-label'), + controller: _label, + decoration: const InputDecoration( + labelText: '이름', + hintText: 'Production daemon', + ), + ), + const SizedBox(height: 12), + TextField( + key: const ValueKey('remote-host-address'), + controller: _address, + keyboardType: TextInputType.url, + decoration: const InputDecoration( + labelText: 'WebSocket 주소', + hintText: 'wss://coder.example.com/ws', + ), + ), + if (_isInsecureRemote(_address.text)) + const Padding( + padding: EdgeInsets.only(top: 8), + child: Text( + '경고: 원격 ws:// 연결은 암호화되지 않습니다. reverse proxy에서 TLS를 종료한 wss:// 주소를 권장합니다.', + ), + ), + const SizedBox(height: 12), + TextField( + key: const ValueKey('remote-host-token'), + controller: _token, + obscureText: true, + decoration: InputDecoration( + labelText: existing == null + ? 'Bearer token' + : '새 Bearer token (변경할 때만 입력)', + ), + ), + const SizedBox(height: 8), + SwitchListTile( + contentPadding: EdgeInsets.zero, + title: const Text('앱 시작 시 자동 연결'), + value: _autoConnect, + onChanged: (value) => setState(() => _autoConnect = value), + ), + if (_error case final error?) + Text( + error, + style: TextStyle(color: Theme.of(context).colorScheme.error), + ), + const SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (existing != null) + TextButton( + onPressed: _saving ? null : () => _delete(existing), + child: const Text('삭제'), + ), + const SizedBox(width: 8), + FilledButton( + onPressed: _saving ? null : () => _save(existing), + child: Text(_saving ? '저장 중…' : '저장'), + ), + ], + ), + ], + ), + ), + ), + ); + } + + Future _save(RemoteDaemonProfile? existing) async { + setState(() { + _saving = true; + _error = null; + }); + try { + final controller = ref.read(hostRegistryControllerProvider.notifier); + if (existing == null) { + await controller.addRemote( + label: _label.text, + address: _address.text, + bearerToken: _token.text, + autoConnect: _autoConnect, + ); + } else { + await controller.updateRemote( + profileId: existing.id, + label: _label.text, + address: _address.text, + autoConnect: _autoConnect, + replacementBearerToken: _token.text.trim().isEmpty + ? null + : _token.text, + ); + } + if (mounted) context.go('/settings/daemons'); + } on Exception catch (error) { + if (mounted) setState(() => _error = '$error'); + } finally { + if (mounted) setState(() => _saving = false); + } + } + + Future _delete(RemoteDaemonProfile profile) async { + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('${profile.label}을 삭제할까요?'), + content: const Text('연결과 저장된 bearer token도 이 기기에서 제거됩니다.'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('취소'), + ), + FilledButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('삭제'), + ), + ], + ), + ); + if (confirmed != true) return; + await ref + .read(hostRegistryControllerProvider.notifier) + .removeRemote(profile.id); + if (mounted) context.go('/'); + } +} + +IconData _statusIcon(HostRuntimeStatus? status) => switch (status) { + HostRuntimeStatus.online => Icons.check_circle_outline, + HostRuntimeStatus.connecting || HostRuntimeStatus.reconnecting => Icons.sync, + HostRuntimeStatus.offline => Icons.cloud_off_outlined, + HostRuntimeStatus.conflict => Icons.call_split, + HostRuntimeStatus.error => Icons.error_outline, + HostRuntimeStatus.idle || null => Icons.pause_circle_outline, +}; + +String _statusText(HostRuntimeSnapshot? runtime) { + if (runtime == null) return '대기 중'; + return switch (runtime.status) { + HostRuntimeStatus.online => '온라인', + HostRuntimeStatus.connecting => '연결 중', + HostRuntimeStatus.reconnecting => '재연결 중', + HostRuntimeStatus.offline => runtime.error ?? '오프라인', + HostRuntimeStatus.error => runtime.error ?? '오류', + HostRuntimeStatus.conflict => runtime.error ?? '중복 daemon', + HostRuntimeStatus.idle => '자동 연결 꺼짐', + }; +} + +bool _isInsecureRemote(String source) { + final uri = Uri.tryParse(source.trim()); + if (uri == null || uri.scheme != 'ws') return false; + return uri.host != 'localhost' && + uri.host != '127.0.0.1' && + uri.host != '::1'; +} diff --git a/apps/coder_app/lib/src/app_storage.dart b/apps/coder_app/lib/src/app_storage.dart new file mode 100644 index 0000000..9269e47 --- /dev/null +++ b/apps/coder_app/lib/src/app_storage.dart @@ -0,0 +1,266 @@ +import 'dart:convert'; + +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_client/coder_client.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// Versioned device-local storage for non-secret app and host settings. +final class SharedPreferencesAppStore + implements AppSettingsRepository, RemoteHostRepository { + /// Creates a preferences-backed app store. + SharedPreferencesAppStore(this._preferences); + + /// Single versioned document key; legacy singleton keys are not read. + static const String documentKey = 'tinyrack_coder.app_document_v2'; + + final SharedPreferences _preferences; + Future _writes = Future.value(); + + @override + Future loadSettings() async => (await _read()).settings; + + @override + Future> listProfiles() async => + List.unmodifiable((await _read()).profiles); + + @override + Future saveSettings(AppSettings settings) => + _enqueue((document) => document.copyWith(settings: settings)); + + @override + Future upsertProfile(RemoteDaemonProfile profile) => + _enqueue((document) { + final profiles = List.of(document.profiles); + final index = profiles.indexWhere((item) => item.id == profile.id); + if (index < 0) { + profiles.add(profile); + } else { + profiles[index] = profile; + } + return document.copyWith(profiles: profiles); + }); + + @override + Future deleteProfile(String profileId) => _enqueue( + (document) => document.copyWith( + profiles: document.profiles + .where((profile) => profile.id != profileId) + .toList(growable: false), + ), + ); + + Future _enqueue( + _AppDocument Function(_AppDocument current) update, + ) { + final completer = _writes.then((_) async { + final next = update(await _read()); + await _preferences.setString(documentKey, jsonEncode(next.toJson())); + }); + _writes = completer; + return completer; + } + + Future<_AppDocument> _read() async { + final source = _preferences.getString(documentKey); + if (source == null) return const _AppDocument(); + final decoded = jsonDecode(source); + if (decoded is! Map) { + throw const FormatException('Invalid app settings document.'); + } + return _AppDocument.fromJson(decoded); + } +} + +/// Secure-storage adapter containing only remote bearer tokens. +final class SecureRemoteHostCredentialStore + implements RemoteHostCredentialStore { + /// Creates a secure remote host credential store. + const SecureRemoteHostCredentialStore(this._storage); + + static const String _prefix = 'tinyrack_coder.remote_host_token.'; + final FlutterSecureStorage _storage; + + @override + Future deleteBearerToken(String profileId) => + _storage.delete(key: '$_prefix$profileId'); + + @override + Future readBearerToken(String profileId) => + _storage.read(key: '$_prefix$profileId'); + + @override + Future writeBearerToken(String profileId, String token) => + _storage.write(key: '$_prefix$profileId', value: token); +} + +final class _AppDocument { + const _AppDocument({ + this.settings = const AppSettings(), + this.profiles = const [], + }); + + factory _AppDocument.fromJson(Map json) { + if (json['version'] != 2) { + throw const FormatException( + 'Incompatible app settings. Remove the app_document_v2 preference ' + 'to reset development data.', + ); + } + final settingsJson = json['settings']; + final profilesJson = json['profiles']; + if (settingsJson is! Map || profilesJson is! List) { + throw const FormatException('Invalid app settings document.'); + } + return _AppDocument( + settings: _settingsFromJson(settingsJson), + profiles: profilesJson + .map((item) { + if (item is! Map) { + throw const FormatException('Invalid remote host profile.'); + } + return _profileFromJson(item); + }) + .toList(growable: false), + ); + } + + final AppSettings settings; + final List profiles; + + _AppDocument copyWith({ + AppSettings? settings, + List? profiles, + }) => _AppDocument( + settings: settings ?? this.settings, + profiles: profiles ?? this.profiles, + ); + + Map toJson() => { + 'version': 2, + 'settings': { + 'embeddedDaemonEnabled': settings.embeddedDaemonEnabled, + 'lastActiveHostId': settings.lastActiveHostId, + 'lastWorktree': _selectionToJson(settings.lastWorktree), + 'sessionTabs': settings.sessionTabs.entries + .map( + (entry) => { + 'key': entry.key, + 'openAgentIds': entry.value.openAgentIds, + 'selectedAgentId': entry.value.selectedAgentId, + }, + ) + .toList(growable: false), + }, + 'profiles': profiles.map(_profileToJson).toList(growable: false), + }; +} + +AppSettings _settingsFromJson(Map json) { + final embedded = json['embeddedDaemonEnabled']; + final lastHost = json['lastActiveHostId']; + final lastWorktree = json['lastWorktree']; + final tabs = json['sessionTabs']; + if (embedded is! bool || + (lastHost != null && lastHost is! String) || + (lastWorktree != null && lastWorktree is! Map) || + tabs is! List) { + throw const FormatException('Invalid app settings values.'); + } + final sessionTabs = {}; + for (final item in tabs) { + if (item is! Map) { + throw const FormatException('Invalid session tab preference.'); + } + final key = item['key']; + final openAgentIds = item['openAgentIds']; + final selectedAgentId = item['selectedAgentId']; + if (key is! String || + openAgentIds is! List || + openAgentIds.any((id) => id is! String) || + (selectedAgentId != null && selectedAgentId is! String)) { + throw const FormatException('Invalid session tab preference values.'); + } + sessionTabs[key] = SessionTabPreference( + openAgentIds: openAgentIds.cast(), + selectedAgentId: selectedAgentId as String?, + ); + } + return AppSettings( + embeddedDaemonEnabled: embedded, + lastActiveHostId: lastHost as String?, + lastWorktree: lastWorktree == null + ? null + : _selectionFromJson(lastWorktree as Map), + sessionTabs: Map.unmodifiable(sessionTabs), + ); +} + +WorkspaceSelection _selectionFromJson(Map json) { + final hostId = json['hostId']; + final workspaceId = json['workspaceId']; + final worktreeId = json['worktreeId']; + if (hostId is! String || workspaceId is! String || worktreeId is! String) { + throw const FormatException('Invalid workspace selection.'); + } + return WorkspaceSelection( + hostId: hostId, + workspaceId: workspaceId, + worktreeId: worktreeId, + ); +} + +Map? _selectionToJson(WorkspaceSelection? selection) => + selection == null + ? null + : { + 'hostId': selection.hostId, + 'workspaceId': selection.workspaceId, + 'worktreeId': selection.worktreeId, + }; + +RemoteDaemonProfile _profileFromJson(Map json) { + final id = json['id']; + final label = json['label']; + final address = json['websocketUri']; + final autoConnect = json['autoConnect']; + final serverId = json['serverId']; + final createdAt = json['createdAt']; + final updatedAt = json['updatedAt']; + final lastConnectedAt = json['lastConnectedAt']; + if (id is! String || + label is! String || + address is! String || + autoConnect is! bool || + (serverId != null && serverId is! String) || + createdAt is! String || + updatedAt is! String || + (lastConnectedAt != null && lastConnectedAt is! String)) { + throw const FormatException('Invalid remote host profile values.'); + } + return RemoteDaemonProfile( + id: id, + label: label, + websocketUri: HostEndpoint.parse(address).websocketUri, + autoConnect: autoConnect, + serverId: serverId as String?, + createdAt: DateTime.parse(createdAt).toUtc(), + updatedAt: DateTime.parse(updatedAt).toUtc(), + lastConnectedAt: lastConnectedAt == null + ? null + : DateTime.parse(lastConnectedAt as String).toUtc(), + ); +} + +Map _profileToJson(RemoteDaemonProfile profile) => + { + 'id': profile.id, + 'label': profile.label, + 'websocketUri': profile.websocketUri.toString(), + 'autoConnect': profile.autoConnect, + 'serverId': profile.serverId, + 'createdAt': profile.createdAt.toIso8601String(), + 'updatedAt': profile.updatedAt.toIso8601String(), + 'lastConnectedAt': profile.lastConnectedAt?.toIso8601String(), + }; diff --git a/apps/coder_app/lib/src/bootstrap.dart b/apps/coder_app/lib/src/bootstrap.dart deleted file mode 100644 index 55bdf95..0000000 --- a/apps/coder_app/lib/src/bootstrap.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:coder_client/coder_client.dart'; - -/// Opens the transport-neutral daemon API used by application bootstraps. -abstract interface class AppClientConnector { - /// Connects one client identity to [endpoint]. - Future connect({ - required HostEndpoint endpoint, - required String clientId, - required String clientKind, - }); -} - -/// Production [AppClientConnector] backed by a WebSocket [CoderClient]. -final class WebSocketAppClientConnector implements AppClientConnector { - /// Creates the production client connector. - const WebSocketAppClientConnector(); - - @override - Future connect({ - required HostEndpoint endpoint, - required String clientId, - required String clientKind, - }) => CoderClient.connect( - endpoint: endpoint, - clientId: clientId, - clientKind: clientKind, - ); -} - -/// BootstrapConnection defines a public contract. -class BootstrapConnection { - /// Creates a [BootstrapConnection]. - const BootstrapConnection({required this.client, required this.endpoint}); - - /// The client public API member. - final CoderApi client; - - /// The endpoint public API member. - final HostEndpoint endpoint; -} - -/// Public API exposed by this library. -abstract interface class AppBootstrap { - /// The canRegisterLocalWorkspace public API member. - bool get canRegisterLocalWorkspace; - - /// The autoConnect public API member. - Future autoConnect(); - - /// The connectRemote public API member. - Future connectRemote(HostEndpoint endpoint); - - /// The close public API member. - Future close(); -} diff --git a/apps/coder_app/lib/src/controller.dart b/apps/coder_app/lib/src/controller.dart index 45400b0..3fe0d44 100644 --- a/apps/coder_app/lib/src/controller.dart +++ b/apps/coder_app/lib/src/controller.dart @@ -1,7 +1,9 @@ import 'dart:async'; -import 'package:coder_app/src/bootstrap.dart'; -import 'package:coder_app/src/ports.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_app/src/host_registry.dart'; import 'package:coder_client/coder_client.dart'; import 'package:coder_protocol/coder_protocol.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,9 +11,9 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'controller.g.dart'; -/// The bootstrapProvider public API member. -final bootstrapProvider = Provider( - (ref) => throw StateError('AppBootstrap must be overridden.'), +/// Platform composition supplied by desktop or mobile entrypoints. +final appServicesProvider = Provider( + (ref) => throw StateError('AppServices must be overridden.'), ); /// The appClockProvider public API member. @@ -22,135 +24,174 @@ final appIdGeneratorProvider = Provider( (ref) => const UuidAppIdGenerator(), ); -/// ConnectionSnapshot defines a public contract. -final class ConnectionSnapshot { - /// Creates a [ConnectionSnapshot]. - const ConnectionSnapshot({ - required this.api, - required this.endpoint, - required this.connectionState, - }); - - /// The api public API member. - final CoderApi api; +Future _requireHostApi(Ref ref, String hostId) async { + final runtime = (await ref.read( + hostRegistryControllerProvider.future, + )).runtimes[hostId]; + final api = runtime?.api; + if (api == null || runtime?.connected != true) { + throw StateError('Online daemon connection required.'); + } + return api; +} - /// The endpoint public API member. - final HostEndpoint endpoint; +@Riverpod(keepAlive: true) +/// Riverpod bridge exposing the independently testable [HostRegistry]. +class HostRegistryController extends _$HostRegistryController { + StreamSubscription? _changes; + late HostRegistry _registry; - /// The connectionState public API member. - final ClientConnectionState connectionState; + @override + Future build() async { + final services = ref.watch(appServicesProvider); + _registry = HostRegistry( + store: services.settings, + profiles: services.profiles, + credentials: services.credentials, + clientFactory: services.clients, + embeddedLauncher: services.embeddedLauncher, + ids: ref.watch(appIdGeneratorProvider), + clock: ref.watch(appClockProvider), + delay: services.delay, + clientKind: services.clientKind, + ); + ref.onDispose(() => unawaited(_dispose())); + final initial = await _registry.load(); + _changes = _registry.changes.listen((next) { + state = AsyncData(next); + }); + return initial; + } + + /// Adds a remote daemon without requiring it to be online. + Future addRemote({ + required String label, + required String address, + required String bearerToken, + required bool autoConnect, + }) => _registry.addRemote( + label: label, + address: address, + bearerToken: bearerToken, + autoConnect: autoConnect, + ); - /// The serverInfo public API member. - ServerInfoDto get serverInfo => api.serverInfo; + /// Updates one remote profile. + Future updateRemote({ + required String profileId, + required String label, + required String address, + required bool autoConnect, + String? replacementBearerToken, + }) => _registry.updateRemote( + profileId: profileId, + label: label, + address: address, + autoConnect: autoConnect, + replacementBearerToken: replacementBearerToken, + ); - /// The connected public API member. - bool get connected => connectionState == ClientConnectionState.connected; + /// Removes one remote host and its secret. + Future removeRemote(String profileId) => + _registry.removeRemote(profileId); - /// The connecting public API member. - bool get connecting => - connectionState == ClientConnectionState.connecting || - connectionState == ClientConnectionState.reconnecting; + /// Connects one host immediately. + Future reconnect(String hostId) => _registry.reconnect(hostId); - /// The label public API member. - String get label => endpoint.websocketUri.authority; + /// Enables or disables startup connection for one remote host. + Future setRemoteAutoConnect( + String hostId, { + required bool enabled, + }) => _registry.setAutoConnect(hostId, enabled: enabled); - /// The copyWith public API member. - ConnectionSnapshot copyWith({ClientConnectionState? connectionState}) => - ConnectionSnapshot( - api: api, - endpoint: endpoint, - connectionState: connectionState ?? this.connectionState, - ); -} + /// Selects one host without requiring an online connection. + Future selectHost(String hostId) => _registry.selectHost(hostId); -@Riverpod(keepAlive: true) -/// ConnectionController defines a public contract. -class ConnectionController extends _$ConnectionController { - StreamSubscription? _states; - CoderApi? _api; - late AppBootstrap _bootstrap; + /// Enables or disables the app-owned desktop daemon. + Future setEmbeddedDaemonEnabled({required bool enabled}) => + _registry.setEmbeddedDaemonEnabled(enabled: enabled); - /// The canRegisterLocalWorkspace public API member. - bool get canRegisterLocalWorkspace => _bootstrap.canRegisterLocalWorkspace; + /// Persists a checkout selection and its visible session tabs. + Future saveWorkspaceUi({ + required WorkspaceSelection selection, + required SessionTabPreference tabs, + }) => _registry.saveWorkspaceUi(selection: selection, tabs: tabs); - @override - Future build() async { - _bootstrap = ref.watch(bootstrapProvider); - ref.onDispose(() => unawaited(_dispose())); - final connection = await _bootstrap.autoConnect(); - return connection == null ? null : _attach(connection); + Future _dispose() async { + await _changes?.cancel(); + await _registry.close(); } +} - /// The connect public API member. - Future connect(String address, String token) async { - state = const AsyncLoading(); - try { - final endpoint = HostEndpoint.parse(address.trim(), token: token.trim()); - final connection = await _bootstrap.connectRemote(endpoint); - state = AsyncData(await _attach(connection)); - } on Exception catch (error, stackTrace) { - state = AsyncError(error, stackTrace); - } - } +/// Catalogs from every host, kept separate by app-local host identity. +final class UnifiedWorkspaceCatalogState { + /// Creates a unified catalog snapshot. + const UnifiedWorkspaceCatalogState({ + required this.hosts, + required this.catalogs, + }); - Future _attach(BootstrapConnection connection) async { - await _states?.cancel(); - if (!identical(_api, connection.client)) await _api?.close(); - _api = connection.client; - _states = connection.client.states.listen((connectionState) { - final current = state.asData?.value; - if (current != null) { - state = AsyncData( - current.copyWith(connectionState: connectionState), - ); - } - }); - return ConnectionSnapshot( - api: connection.client, - endpoint: connection.endpoint, - connectionState: ClientConnectionState.connected, - ); - } + /// Every host runtime, including offline hosts. + final Map hosts; - Future _dispose() async { - await _states?.cancel(); - await _api?.close(); - await _bootstrap.close(); - } + /// Online daemon catalogs keyed by host profile ID. + final Map catalogs; } @Riverpod(keepAlive: true) -/// WorkspacesController defines a public contract. -class WorkspacesController extends _$WorkspacesController { +/// Loads every online daemon catalog without merging daemon-local IDs. +class WorkspaceCatalogController extends _$WorkspaceCatalogController { @override - Future> build() async { - final connection = await ref.watch(connectionControllerProvider.future); - return connection == null - ? const [] - : connection.api.listWorkspaces(); - } - - /// The register public API member. - Future register(String rootPath) async { - final connection = await ref.read(connectionControllerProvider.future); - if (connection == null) throw StateError('Daemon connection required.'); - final previous = state.asData?.value ?? const []; - state = const AsyncLoading>(); - try { - final workspace = await connection.api.registerWorkspace( - id: ref.read(appIdGeneratorProvider).generate(), - rootPath: rootPath, - name: rootPath.split(RegExp(r'[/\\]')).last, - ); - state = AsyncData>([ - ...previous, - workspace, - ]); - return workspace; - } catch (error, stackTrace) { - state = AsyncError>(error, stackTrace); - rethrow; - } + Future build() async { + final registry = await ref.watch(hostRegistryControllerProvider.future); + final entries = await Future.wait( + registry.runtimes.values.where((item) => item.connected).map(( + runtime, + ) async { + final catalog = await runtime.api!.getWorkspaceCatalog(); + return MapEntry(runtime.id, catalog); + }), + ); + return UnifiedWorkspaceCatalogState( + hosts: registry.runtimes, + catalogs: Map.unmodifiable( + Map.fromEntries(entries), + ), + ); + } + + /// Registers a folder on the selected daemon and refreshes its catalog. + Future register( + String hostId, + String rootPath, + ) async { + final api = await _requireHostApi(ref, hostId); + final result = await api.registerWorkspace( + workspaceId: ref.read(appIdGeneratorProvider).generate(), + checkoutId: ref.read(appIdGeneratorProvider).generate(), + rootPath: rootPath, + name: rootPath.split(RegExp(r'[/\\]')).last, + ); + await refreshHost(hostId); + return result; + } + + /// Refreshes one daemon catalog without affecting other hosts. + Future refreshHost(String hostId) async { + final api = await _requireHostApi(ref, hostId); + final catalog = await api.getWorkspaceCatalog(); + final current = state.requireValue; + state = AsyncData( + UnifiedWorkspaceCatalogState( + hosts: current.hosts, + catalogs: Map.unmodifiable( + { + ...current.catalogs, + hostId: catalog, + }, + ), + ), + ); } } @@ -158,18 +199,21 @@ class WorkspacesController extends _$WorkspacesController { /// AgentsController defines a public contract. class AgentsController extends _$AgentsController { StreamSubscription? _events; - late String? _workspaceId; + late String? _worktreeId; @override - Future> build(String? workspaceId) async { - _workspaceId = workspaceId; - final connection = await ref.watch(connectionControllerProvider.future); - if (connection == null || workspaceId == null) { + Future> build(String hostId, String? worktreeId) async { + _worktreeId = worktreeId; + final runtime = (await ref.watch( + hostRegistryControllerProvider.future, + )).runtimes[hostId]; + if (runtime?.connected != true || worktreeId == null) { return const []; } - _events = connection.api.events.listen(_handleEvent); + final api = runtime!.api!; + _events = api.events.listen(_handleEvent); ref.onDispose(() => unawaited(_events?.cancel())); - return connection.api.listAgents(workspaceId: workspaceId); + return api.listAgents(worktreeId: worktreeId); } /// The create public API member. @@ -180,17 +224,17 @@ class AgentsController extends _$AgentsController { required String reasoningEffort, required PermissionMode permissionMode, }) async { - final workspaceId = _workspaceId; - final connection = await ref.read(connectionControllerProvider.future); - if (connection == null || workspaceId == null) { - throw StateError('Workspace selection and daemon connection required.'); + final worktreeId = _worktreeId; + if (worktreeId == null) { + throw StateError('Worktree selection and daemon connection required.'); } + final api = await _requireHostApi(ref, hostId); final previous = state.asData?.value ?? const []; state = const AsyncLoading>(); try { - final agent = await connection.api.createAgent( + final agent = await api.createAgent( id: ref.read(appIdGeneratorProvider).generate(), - workspaceId: workspaceId, + worktreeId: worktreeId, title: title, providerConnectionId: providerConnectionId, model: model, @@ -212,9 +256,8 @@ class AgentsController extends _$AgentsController { required String model, required String reasoningEffort, }) async { - final connection = await ref.read(connectionControllerProvider.future); - if (connection == null) throw StateError('Daemon connection required.'); - final updated = await connection.api.updateAgentConfiguration( + final api = await _requireHostApi(ref, hostId); + final updated = await api.updateAgentConfiguration( agentId: agentId, providerConnectionId: providerConnectionId, model: model, @@ -227,7 +270,7 @@ class AgentsController extends _$AgentsController { void _handleEvent(ClientEvent event) { if (event case AgentUpdatedClientEvent( :final agent, - ) when agent.workspaceId == _workspaceId) { + ) when agent.worktreeId == _worktreeId) { _replace(agent); } } @@ -242,6 +285,119 @@ class AgentsController extends _$AgentsController { } } +/// Visible and selected session tabs for one worktree. +final class SessionTabsState { + /// Creates immutable tab state. + const SessionTabsState({ + required this.sessions, + required this.openAgentIds, + this.selectedAgentId, + }); + + /// All daemon sessions available to the overflow picker. + final List sessions; + + /// Session IDs visible in the tab strip. + final List openAgentIds; + + /// Currently active tab. + final String? selectedAgentId; +} + +@riverpod +/// Owns local tab visibility independently for each host worktree. +class SessionTabsController extends _$SessionTabsController { + late WorkspaceSelection _selection; + + @override + Future build(WorkspaceSelection selection) async { + _selection = selection; + final sessions = await ref.watch( + agentsControllerProvider(selection.hostId, selection.worktreeId).future, + ); + final settings = (await ref.watch( + hostRegistryControllerProvider.future, + )).settings; + final saved = settings.sessionTabs[selection.storageKey]; + final existingIds = sessions.map((item) => item.id).toSet(); + final open = + saved?.openAgentIds + .where(existingIds.contains) + .toList(growable: false) ?? + [if (sessions.isNotEmpty) sessions.first.id]; + final selected = open.contains(saved?.selectedAgentId) + ? saved?.selectedAgentId + : open.firstOrNull; + return SessionTabsState( + sessions: sessions, + openAgentIds: open, + selectedAgentId: selected, + ); + } + + /// Opens and selects a session from the overflow picker. + Future open(String agentId) async { + final current = state.requireValue; + final open = [ + ...current.openAgentIds.where((id) => id != agentId), + agentId, + ]; + await _set(current, open, agentId); + } + + /// Selects an already-open session. + Future select(String agentId) => + _set(state.requireValue, state.requireValue.openAgentIds, agentId); + + /// Hides a tab without deleting its daemon session or history. + Future close(String agentId) async { + final current = state.requireValue; + final open = current.openAgentIds + .where((id) => id != agentId) + .toList(growable: false); + final selected = current.selectedAgentId == agentId + ? open.lastOrNull + : current.selectedAgentId; + await _set(current, open, selected); + } + + /// Adds a newly-created daemon session to the tab strip. + Future add(AgentDto agent) async { + final current = state.requireValue; + await _set( + SessionTabsState( + sessions: [agent, ...current.sessions], + openAgentIds: current.openAgentIds, + selectedAgentId: current.selectedAgentId, + ), + [...current.openAgentIds, agent.id], + agent.id, + ); + } + + Future _set( + SessionTabsState current, + List open, + String? selected, + ) async { + final next = SessionTabsState( + sessions: current.sessions, + openAgentIds: List.unmodifiable(open), + selectedAgentId: selected, + ); + state = AsyncData(next); + await ref + .read(hostRegistryControllerProvider.notifier) + .saveWorkspaceUi( + selection: _selection, + tabs: SessionTabPreference( + openAgentIds: next.openAgentIds, + selectedAgentId: next.selectedAgentId, + ), + ); + } +} + /// ConversationState defines a public contract. final class ConversationState { /// Creates a [ConversationState]. @@ -273,14 +429,17 @@ class ConversationController extends _$ConversationController { late String? _agentId; @override - Future build(String? agentId) async { + Future build(String hostId, String? agentId) async { _agentId = agentId; - final connection = await ref.watch(connectionControllerProvider.future); - if (connection == null || agentId == null) { + final runtime = (await ref.watch( + hostRegistryControllerProvider.future, + )).runtimes[hostId]; + if (runtime?.connected != true || agentId == null) { return const ConversationState(); } - final timeline = await connection.api.subscribeTimeline(agentId); - _events = connection.api.events.listen(_handleEvent); + final api = runtime!.api!; + final timeline = await api.subscribeTimeline(agentId); + _events = api.events.listen(_handleEvent); ref.onDispose(() => unawaited(_events?.cancel())); return ConversationState( timeline: timeline, @@ -291,9 +450,9 @@ class ConversationController extends _$ConversationController { /// The startTurn public API member. Future startTurn(String prompt) async { final agentId = _agentId; - final connection = await ref.read(connectionControllerProvider.future); - if (connection == null || agentId == null || prompt.trim().isEmpty) return; - await connection.api.startTurn( + if (agentId == null || prompt.trim().isEmpty) return; + final api = await _requireHostApi(ref, hostId); + await api.startTurn( agentId: agentId, turnId: ref.read(appIdGeneratorProvider).generate(), prompt: prompt.trim(), @@ -303,9 +462,9 @@ class ConversationController extends _$ConversationController { /// The cancelTurn public API member. Future cancelTurn() async { final agentId = _agentId; - final connection = await ref.read(connectionControllerProvider.future); - if (connection != null && agentId != null) { - await connection.api.cancelTurn(agentId); + if (agentId != null) { + final api = await _requireHostApi(ref, hostId); + await api.cancelTurn(agentId); } } @@ -314,9 +473,8 @@ class ConversationController extends _$ConversationController { String approvalId, { required bool approved, }) async { - final connection = await ref.read(connectionControllerProvider.future); - if (connection == null) throw StateError('Daemon connection required.'); - await connection.api.resolveApproval( + final api = await _requireHostApi(ref, hostId); + await api.resolveApproval( approvalId: approvalId, approved: approved, ); @@ -438,31 +596,37 @@ class ProviderSettingsController extends _$ProviderSettingsController { /// The canManage public API member. bool get canManage => ref - .read(connectionControllerProvider) + .read(hostRegistryControllerProvider) .asData ?.value + .runtimes[hostId] ?.serverInfo - .features['providerAdmin'] == + ?.features['providerAdmin'] == true; @override - Future build() async { - final connection = await ref.watch(connectionControllerProvider.future); - if (connection == null) return null; - _events = connection.api.events.listen(_handleEvent); + Future build(String hostId) async { + final runtime = (await ref.watch( + hostRegistryControllerProvider.future, + )).runtimes[hostId]; + if (runtime?.connected != true) return null; + final api = runtime!.api!; + _events = api.events.listen(_handleEvent); ref.onDispose(() => unawaited(_events?.cancel())); return ProviderSettingsState( - catalog: await connection.api.listProviderCatalog(), - connections: await connection.api.listProviderConnections(), + catalog: await api.listProviderCatalog(), + connections: await api.listProviderConnections(), ); } /// The loadModels public API member. Future loadModels(String connectionId) async { - final connection = await ref.read(connectionControllerProvider.future); + final runtime = (await ref.read( + hostRegistryControllerProvider.future, + )).runtimes[hostId]; final current = state.asData?.value; - if (connection == null || current == null) return; - final models = await connection.api.listProviderModels(connectionId); + if (runtime?.connected != true || current == null) return; + final models = await runtime!.api!.listProviderModels(connectionId); state = AsyncData( current.copyWith( models: >{ @@ -479,13 +643,13 @@ class ProviderSettingsController extends _$ProviderSettingsController { String apiKey, { bool makeDefault = false, }) async { - final connection = await _requireConnection(); - final result = await connection.api.connectProviderApiKey( + final api = await _requireConnection(); + final result = await api.connectProviderApiKey( definitionId, apiKey, makeDefault: makeDefault, ); - await _reload(connection); + await _reload(api); return result; } @@ -494,12 +658,12 @@ class ProviderSettingsController extends _$ProviderSettingsController { String definitionId, { bool makeDefault = false, }) async { - final connection = await _requireConnection(); - final result = await connection.api.connectProviderNone( + final api = await _requireConnection(); + final result = await api.connectProviderNone( definitionId, makeDefault: makeDefault, ); - await _reload(connection); + await _reload(api); return result; } @@ -509,8 +673,8 @@ class ProviderSettingsController extends _$ProviderSettingsController { String methodId, { bool makeDefault = false, }) async { - final connection = await _requireConnection(); - final attempt = await connection.api.startProviderAuth( + final api = await _requireConnection(); + final attempt = await api.startProviderAuth( definitionId, methodId, makeDefault: makeDefault, @@ -531,22 +695,22 @@ class ProviderSettingsController extends _$ProviderSettingsController { /// Cancels an interactive authorization attempt. Future cancelAuth(String attemptId) async { - final connection = await _requireConnection(); - await connection.api.cancelProviderAuth(attemptId); + final api = await _requireConnection(); + await api.cancelProviderAuth(attemptId); } /// Disconnects a provider connection while retaining history. Future disconnect(String connectionId) async { - final connection = await _requireConnection(); - await connection.api.disconnectProvider(connectionId); - await _reload(connection); + final api = await _requireConnection(); + await api.disconnectProvider(connectionId); + await _reload(api); } /// Selects a connection as the daemon default. Future setDefault(String connectionId) async { - final connection = await _requireConnection(); - await connection.api.setDefaultProvider(connectionId); - await _reload(connection); + final api = await _requireConnection(); + await api.setDefaultProvider(connectionId); + await _reload(api); } /// Selects the connection's default model. @@ -554,18 +718,18 @@ class ProviderSettingsController extends _$ProviderSettingsController { String connectionId, String modelId, ) async { - final connection = await _requireConnection(); - await connection.api.setDefaultProviderModel( + final api = await _requireConnection(); + await api.setDefaultProviderModel( connectionId, modelId, ); - await _reload(connection); + await _reload(api); } /// Explicitly refreshes catalog metadata. Future refreshCatalog() async { - final connection = await _requireConnection(); - final catalog = await connection.api.refreshProviderCatalog(); + final api = await _requireConnection(); + final catalog = await api.refreshProviderCatalog(); final current = state.asData?.value; if (current != null) { state = AsyncData( @@ -581,14 +745,14 @@ class ProviderSettingsController extends _$ProviderSettingsController { String? apiKey, bool makeDefault = false, }) async { - final connection = await _requireConnection(); - final result = await connection.api.createCustomProvider( + final api = await _requireConnection(); + final result = await api.createCustomProvider( id, config, apiKey: apiKey, makeDefault: makeDefault, ); - await _reload(connection); + await _reload(api); return result; } @@ -598,35 +762,31 @@ class ProviderSettingsController extends _$ProviderSettingsController { CustomProviderConfigDto config, { String? apiKey, }) async { - final connection = await _requireConnection(); - final result = await connection.api.updateCustomProvider( + final api = await _requireConnection(); + final result = await api.updateCustomProvider( connectionId, config, apiKey: apiKey, ); - await _reload(connection); + await _reload(api); return result; } /// Deletes an advanced custom connection. Future deleteCustom(String connectionId) async { - final connection = await _requireConnection(); - await connection.api.deleteCustomProvider(connectionId); - await _reload(connection); + final api = await _requireConnection(); + await api.deleteCustomProvider(connectionId); + await _reload(api); } - Future _requireConnection() async { - final connection = await ref.read(connectionControllerProvider.future); - if (connection == null) throw StateError('Daemon connection required.'); - return connection; - } + Future _requireConnection() => _requireHostApi(ref, hostId); - Future _reload(ConnectionSnapshot connection) async { + Future _reload(CoderApi api) async { final current = state.asData?.value; state = AsyncData( ProviderSettingsState( - catalog: await connection.api.listProviderCatalog(), - connections: await connection.api.listProviderConnections(), + catalog: await api.listProviderCatalog(), + connections: await api.listProviderConnections(), models: current?.models ?? const >{}, authAttempts: current?.authAttempts ?? const {}, diff --git a/apps/coder_app/lib/src/controller.g.dart b/apps/coder_app/lib/src/controller.g.dart index 2143263..0d781f0 100644 --- a/apps/coder_app/lib/src/controller.g.dart +++ b/apps/coder_app/lib/src/controller.g.dart @@ -8,52 +8,52 @@ part of 'controller.dart'; // GENERATED CODE - DO NOT MODIFY BY HAND // ignore_for_file: type=lint, type=warning -/// ConnectionController defines a public contract. +/// Riverpod bridge exposing the independently testable [HostRegistry]. -@ProviderFor(ConnectionController) -final connectionControllerProvider = ConnectionControllerProvider._(); +@ProviderFor(HostRegistryController) +final hostRegistryControllerProvider = HostRegistryControllerProvider._(); -/// ConnectionController defines a public contract. -final class ConnectionControllerProvider - extends $AsyncNotifierProvider { - /// ConnectionController defines a public contract. - ConnectionControllerProvider._() +/// Riverpod bridge exposing the independently testable [HostRegistry]. +final class HostRegistryControllerProvider + extends $AsyncNotifierProvider { + /// Riverpod bridge exposing the independently testable [HostRegistry]. + HostRegistryControllerProvider._() : super( from: null, argument: null, retry: null, - name: r'connectionControllerProvider', + name: r'hostRegistryControllerProvider', isAutoDispose: false, dependencies: null, $allTransitiveDependencies: null, ); @override - String debugGetCreateSourceHash() => _$connectionControllerHash(); + String debugGetCreateSourceHash() => _$hostRegistryControllerHash(); @$internal @override - ConnectionController create() => ConnectionController(); + HostRegistryController create() => HostRegistryController(); } -String _$connectionControllerHash() => - r'93b00ba6e8cd21f9fe002a68d5a543fe6a49a8bf'; +String _$hostRegistryControllerHash() => + r'1cd01bbf7380d379c71d714129609bf780001fa9'; -/// ConnectionController defines a public contract. +/// Riverpod bridge exposing the independently testable [HostRegistry]. -abstract class _$ConnectionController - extends $AsyncNotifier { - FutureOr build(); +abstract class _$HostRegistryController + extends $AsyncNotifier { + FutureOr build(); @$mustCallSuper @override WhenComplete runBuild() { final ref = - this.ref as $Ref, ConnectionSnapshot?>; + this.ref as $Ref, HostRegistryState>; final element = ref.element as $ClassProviderElement< - AnyNotifier, ConnectionSnapshot?>, - AsyncValue, + AnyNotifier, HostRegistryState>, + AsyncValue, Object?, Object? >; @@ -61,52 +61,64 @@ abstract class _$ConnectionController } } -/// WorkspacesController defines a public contract. +/// Loads every online daemon catalog without merging daemon-local IDs. -@ProviderFor(WorkspacesController) -final workspacesControllerProvider = WorkspacesControllerProvider._(); +@ProviderFor(WorkspaceCatalogController) +final workspaceCatalogControllerProvider = + WorkspaceCatalogControllerProvider._(); -/// WorkspacesController defines a public contract. -final class WorkspacesControllerProvider - extends $AsyncNotifierProvider> { - /// WorkspacesController defines a public contract. - WorkspacesControllerProvider._() +/// Loads every online daemon catalog without merging daemon-local IDs. +final class WorkspaceCatalogControllerProvider + extends + $AsyncNotifierProvider< + WorkspaceCatalogController, + UnifiedWorkspaceCatalogState + > { + /// Loads every online daemon catalog without merging daemon-local IDs. + WorkspaceCatalogControllerProvider._() : super( from: null, argument: null, retry: null, - name: r'workspacesControllerProvider', + name: r'workspaceCatalogControllerProvider', isAutoDispose: false, dependencies: null, $allTransitiveDependencies: null, ); @override - String debugGetCreateSourceHash() => _$workspacesControllerHash(); + String debugGetCreateSourceHash() => _$workspaceCatalogControllerHash(); @$internal @override - WorkspacesController create() => WorkspacesController(); + WorkspaceCatalogController create() => WorkspaceCatalogController(); } -String _$workspacesControllerHash() => - r'90cd73f2dc7e2aedfa9f2d4478976102ea05e83e'; +String _$workspaceCatalogControllerHash() => + r'baf0d71d616d191aa43113e382375c640bc17e7f'; -/// WorkspacesController defines a public contract. +/// Loads every online daemon catalog without merging daemon-local IDs. -abstract class _$WorkspacesController - extends $AsyncNotifier> { - FutureOr> build(); +abstract class _$WorkspaceCatalogController + extends $AsyncNotifier { + FutureOr build(); @$mustCallSuper @override WhenComplete runBuild() { final ref = - this.ref as $Ref>, List>; + this.ref + as $Ref< + AsyncValue, + UnifiedWorkspaceCatalogState + >; final element = ref.element as $ClassProviderElement< - AnyNotifier>, List>, - AsyncValue>, + AnyNotifier< + AsyncValue, + UnifiedWorkspaceCatalogState + >, + AsyncValue, Object?, Object? >; @@ -125,7 +137,7 @@ final class AgentsControllerProvider /// AgentsController defines a public contract. AgentsControllerProvider._({ required AgentsControllerFamily super.from, - required String? super.argument, + required (String, String?) super.argument, }) : super( retry: null, name: r'agentsControllerProvider', @@ -141,7 +153,7 @@ final class AgentsControllerProvider String toString() { return r'agentsControllerProvider' '' - '($argument)'; + '$argument'; } @$internal @@ -159,7 +171,7 @@ final class AgentsControllerProvider } } -String _$agentsControllerHash() => r'0bde409a8933464a780ecc05ce339d457e1254e5'; +String _$agentsControllerHash() => r'567a999c8471bd70b04ec43eb2f064f838bb5d59'; /// AgentsController defines a public contract. @@ -170,7 +182,7 @@ final class AgentsControllerFamily extends $Family AsyncValue>, List, FutureOr>, - String? + (String, String?) > { AgentsControllerFamily._() : super( @@ -183,8 +195,8 @@ final class AgentsControllerFamily extends $Family /// AgentsController defines a public contract. - AgentsControllerProvider call(String? workspaceId) => - AgentsControllerProvider._(argument: workspaceId, from: this); + AgentsControllerProvider call(String hostId, String? worktreeId) => + AgentsControllerProvider._(argument: (hostId, worktreeId), from: this); @override String toString() => r'agentsControllerProvider'; @@ -193,10 +205,11 @@ final class AgentsControllerFamily extends $Family /// AgentsController defines a public contract. abstract class _$AgentsController extends $AsyncNotifier> { - late final _$args = ref.$arg as String?; - String? get workspaceId => _$args; + late final _$args = ref.$arg as (String, String?); + String get hostId => _$args.$1; + String? get worktreeId => _$args.$2; - FutureOr> build(String? workspaceId); + FutureOr> build(String hostId, String? worktreeId); @$mustCallSuper @override WhenComplete runBuild() { @@ -209,6 +222,108 @@ abstract class _$AgentsController extends $AsyncNotifier> { Object?, Object? >; + return element.handleCreate(ref, () => build(_$args.$1, _$args.$2)); + } +} + +/// Owns local tab visibility independently for each host worktree. + +@ProviderFor(SessionTabsController) +final sessionTabsControllerProvider = SessionTabsControllerFamily._(); + +/// Owns local tab visibility independently for each host worktree. +final class SessionTabsControllerProvider + extends $AsyncNotifierProvider { + /// Owns local tab visibility independently for each host worktree. + SessionTabsControllerProvider._({ + required SessionTabsControllerFamily super.from, + required WorkspaceSelection super.argument, + }) : super( + retry: null, + name: r'sessionTabsControllerProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$sessionTabsControllerHash(); + + @override + String toString() { + return r'sessionTabsControllerProvider' + '' + '($argument)'; + } + + @$internal + @override + SessionTabsController create() => SessionTabsController(); + + @override + bool operator ==(Object other) { + return other is SessionTabsControllerProvider && other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$sessionTabsControllerHash() => + r'11c6749b8fc3bfb3c0a415c0dfe102860f2ffc07'; + +/// Owns local tab visibility independently for each host worktree. + +final class SessionTabsControllerFamily extends $Family + with + $ClassFamilyOverride< + SessionTabsController, + AsyncValue, + SessionTabsState, + FutureOr, + WorkspaceSelection + > { + SessionTabsControllerFamily._() + : super( + retry: null, + name: r'sessionTabsControllerProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + /// Owns local tab visibility independently for each host worktree. + + SessionTabsControllerProvider call(WorkspaceSelection selection) => + SessionTabsControllerProvider._(argument: selection, from: this); + + @override + String toString() => r'sessionTabsControllerProvider'; +} + +/// Owns local tab visibility independently for each host worktree. + +abstract class _$SessionTabsController + extends $AsyncNotifier { + late final _$args = ref.$arg as WorkspaceSelection; + WorkspaceSelection get selection => _$args; + + FutureOr build(WorkspaceSelection selection); + @$mustCallSuper + @override + WhenComplete runBuild() { + final ref = + this.ref as $Ref, SessionTabsState>; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, SessionTabsState>, + AsyncValue, + Object?, + Object? + >; return element.handleCreate(ref, () => build(_$args)); } } @@ -224,7 +339,7 @@ final class ConversationControllerProvider /// ConversationController defines a public contract. ConversationControllerProvider._({ required ConversationControllerFamily super.from, - required String? super.argument, + required (String, String?) super.argument, }) : super( retry: null, name: r'conversationControllerProvider', @@ -240,7 +355,7 @@ final class ConversationControllerProvider String toString() { return r'conversationControllerProvider' '' - '($argument)'; + '$argument'; } @$internal @@ -260,7 +375,7 @@ final class ConversationControllerProvider } String _$conversationControllerHash() => - r'f17c25b3790e1e504e8928df9ff1dc1c4915fa23'; + r'81662b5dd7bb8d0129cde3e09f6a5085f33ea1ea'; /// ConversationController defines a public contract. @@ -271,7 +386,7 @@ final class ConversationControllerFamily extends $Family AsyncValue, ConversationState, FutureOr, - String? + (String, String?) > { ConversationControllerFamily._() : super( @@ -284,8 +399,8 @@ final class ConversationControllerFamily extends $Family /// ConversationController defines a public contract. - ConversationControllerProvider call(String? agentId) => - ConversationControllerProvider._(argument: agentId, from: this); + ConversationControllerProvider call(String hostId, String? agentId) => + ConversationControllerProvider._(argument: (hostId, agentId), from: this); @override String toString() => r'conversationControllerProvider'; @@ -295,10 +410,11 @@ final class ConversationControllerFamily extends $Family abstract class _$ConversationController extends $AsyncNotifier { - late final _$args = ref.$arg as String?; - String? get agentId => _$args; + late final _$args = ref.$arg as (String, String?); + String get hostId => _$args.$1; + String? get agentId => _$args.$2; - FutureOr build(String? agentId); + FutureOr build(String hostId, String? agentId); @$mustCallSuper @override WhenComplete runBuild() { @@ -312,15 +428,14 @@ abstract class _$ConversationController Object?, Object? >; - return element.handleCreate(ref, () => build(_$args)); + return element.handleCreate(ref, () => build(_$args.$1, _$args.$2)); } } /// ProviderSettingsController defines a public contract. @ProviderFor(ProviderSettingsController) -final providerSettingsControllerProvider = - ProviderSettingsControllerProvider._(); +final providerSettingsControllerProvider = ProviderSettingsControllerFamily._(); /// ProviderSettingsController defines a public contract. final class ProviderSettingsControllerProvider @@ -330,33 +445,83 @@ final class ProviderSettingsControllerProvider ProviderSettingsState? > { /// ProviderSettingsController defines a public contract. - ProviderSettingsControllerProvider._() - : super( - from: null, - argument: null, - retry: null, - name: r'providerSettingsControllerProvider', - isAutoDispose: false, - dependencies: null, - $allTransitiveDependencies: null, - ); + ProviderSettingsControllerProvider._({ + required ProviderSettingsControllerFamily super.from, + required String super.argument, + }) : super( + retry: null, + name: r'providerSettingsControllerProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); @override String debugGetCreateSourceHash() => _$providerSettingsControllerHash(); + @override + String toString() { + return r'providerSettingsControllerProvider' + '' + '($argument)'; + } + @$internal @override ProviderSettingsController create() => ProviderSettingsController(); + + @override + bool operator ==(Object other) { + return other is ProviderSettingsControllerProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } } String _$providerSettingsControllerHash() => - r'03621d9e39f39cefe65006e2dc39074be9526428'; + r'd058f4875231242b0c6162cf7576675f71ee62d1'; + +/// ProviderSettingsController defines a public contract. + +final class ProviderSettingsControllerFamily extends $Family + with + $ClassFamilyOverride< + ProviderSettingsController, + AsyncValue, + ProviderSettingsState?, + FutureOr, + String + > { + ProviderSettingsControllerFamily._() + : super( + retry: null, + name: r'providerSettingsControllerProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: false, + ); + + /// ProviderSettingsController defines a public contract. + + ProviderSettingsControllerProvider call(String hostId) => + ProviderSettingsControllerProvider._(argument: hostId, from: this); + + @override + String toString() => r'providerSettingsControllerProvider'; +} /// ProviderSettingsController defines a public contract. abstract class _$ProviderSettingsController extends $AsyncNotifier { - FutureOr build(); + late final _$args = ref.$arg as String; + String get hostId => _$args; + + FutureOr build(String hostId); @$mustCallSuper @override WhenComplete runBuild() { @@ -374,6 +539,6 @@ abstract class _$ProviderSettingsController Object?, Object? >; - return element.handleCreate(ref, build); + return element.handleCreate(ref, () => build(_$args)); } } diff --git a/apps/coder_app/lib/src/desktop_bootstrap.dart b/apps/coder_app/lib/src/desktop_bootstrap.dart index 90cfbf2..3f2d6da 100644 --- a/apps/coder_app/lib/src/desktop_bootstrap.dart +++ b/apps/coder_app/lib/src/desktop_bootstrap.dart @@ -1,132 +1,84 @@ -import 'package:coder_app/src/bootstrap.dart'; -import 'package:coder_app/src/ports.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/app_storage.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; import 'package:coder_client/coder_client.dart'; import 'package:coder_daemon/coder_daemon.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -/// A running embedded daemon exposed without leaking its concrete handle. -abstract interface class EmbeddedDaemonSession { - /// The WebSocket endpoint bound by the daemon. - Uri get boundEndpoint; - - /// The bearer token generated for this daemon. - String get bearerToken; - - /// Stops the embedded daemon. - Future stop(); -} - -/// Starts an embedded daemon session for the desktop bootstrap. -abstract interface class EmbeddedDaemonLauncher { - /// Starts a daemon using the current process environment. - Future start(); +import 'package:shared_preferences/shared_preferences.dart'; + +/// Starts one embedded daemon from a resolved configuration. +typedef EmbeddedDaemonStarter = + Future Function(DaemonConfig config); + +/// Creates production desktop services after local settings storage is ready. +Future createDesktopServices({ + EmbeddedDaemonLauncher embeddedLauncher = + const IsolateEmbeddedDaemonLauncher(), + HostClientFactory clients = const WebSocketHostClientFactory(), + FlutterSecureStorage secureStorage = const FlutterSecureStorage(), +}) async { + final store = SharedPreferencesAppStore( + await SharedPreferences.getInstance(), + ); + return AppServices( + settings: store, + profiles: store, + credentials: SecureRemoteHostCredentialStore(secureStorage), + clients: clients, + clientKind: 'desktop', + embeddedLauncher: embeddedLauncher, + ); } -/// Production launcher backed by [EmbeddedDaemonHandle]. +/// Starts an embedded daemon isolate without connecting the GUI client. final class IsolateEmbeddedDaemonLauncher implements EmbeddedDaemonLauncher { /// Creates the production embedded daemon launcher. - const IsolateEmbeddedDaemonLauncher(); + const IsolateEmbeddedDaemonLauncher({ + this.config, + this.startDaemon = _startEmbeddedDaemon, + }); - @override - Future start() async => _DaemonSession( - await EmbeddedDaemonHandle.start(DaemonConfig.fromEnvironment()), - ); -} - -final class _DaemonSession implements EmbeddedDaemonSession { - const _DaemonSession(this._handle); - - final EmbeddedDaemonHandle _handle; + /// Explicit configuration used by deterministic integration tests. + final DaemonConfig? config; - @override - String get bearerToken => _handle.bearerToken; - - @override - Uri get boundEndpoint => _handle.boundEndpoint; + /// Injected isolate starter used by deterministic tests. + final EmbeddedDaemonStarter startDaemon; @override - Future stop() => _handle.stop(); + Future start() async { + try { + return _EmbeddedSession( + await startDaemon(config ?? DaemonConfig.fromEnvironment()), + ); + } on Exception catch (error) { + throw HostConnectionFailure.network('$error'); + } + } } -/// DesktopBootstrap defines a public contract. -class DesktopBootstrap implements AppBootstrap { - /// Creates a [DesktopBootstrap]. - DesktopBootstrap({ - FlutterSecureStorage? storage, - this._ids = const UuidAppIdGenerator(), - this._connector = const WebSocketAppClientConnector(), - this._launcher = const IsolateEmbeddedDaemonLauncher(), - }) : _storage = storage ?? const FlutterSecureStorage(); +Future _startEmbeddedDaemon(DaemonConfig config) => + EmbeddedDaemonHandle.start(config); - static const String _addressKey = 'tinyrack_coder.host_address'; - static const String _tokenKey = 'tinyrack_coder.host_token'; +final class _EmbeddedSession implements EmbeddedDaemonSession { + const _EmbeddedSession(this._handle); - final FlutterSecureStorage _storage; - final AppIdGenerator _ids; - final AppClientConnector _connector; - final EmbeddedDaemonLauncher _launcher; - EmbeddedDaemonSession? _embedded; + final DaemonHandle _handle; @override - bool get canRegisterLocalWorkspace => true; + DaemonCredentials get credentials => DaemonCredentials( + bearerToken: _handle.bearerToken, + adminToken: _handle.adminToken, + ); @override - Future autoConnect() async { - final savedAddress = await _storage.read(key: _addressKey); - final savedToken = await _storage.read(key: _tokenKey); - if (savedAddress != null && savedToken != null) { - try { - return await _connect( - HostEndpoint.parse(savedAddress, token: savedToken), - persist: false, - ); - } on Exception { - // A previous embedded process is expected to be gone after an app - // restart. - } - } - _embedded = await _launcher.start(); - final endpoint = HostEndpoint( - websocketUri: _embedded!.boundEndpoint, - token: _embedded!.bearerToken, - ); - await _storage.write( - key: _addressKey, - value: endpoint.websocketUri.toString(), - ); - await _storage.write(key: _tokenKey, value: endpoint.token); - return _connect(endpoint, persist: false); - } + HostEndpoint get endpoint => HostEndpoint( + websocketUri: _handle.boundEndpoint, + ); @override - Future connectRemote(HostEndpoint endpoint) async { - await _embedded?.stop(); - _embedded = null; - return _connect(endpoint, persist: true); - } - - Future _connect( - HostEndpoint endpoint, { - required bool persist, - }) async { - final client = await _connector.connect( - endpoint: endpoint, - clientId: _ids.generate(), - clientKind: 'desktop', - ); - if (persist) { - await _storage.write( - key: _addressKey, - value: endpoint.websocketUri.toString(), - ); - await _storage.write(key: _tokenKey, value: endpoint.token); - } - return BootstrapConnection(client: client, endpoint: endpoint); - } + String get serverId => _handle.serverId; @override - Future close() async { - await _embedded?.stop(); - _embedded = null; - } + Future stop() => _handle.stop(); } diff --git a/apps/coder_app/lib/src/host_models.dart b/apps/coder_app/lib/src/host_models.dart new file mode 100644 index 0000000..efa0c90 --- /dev/null +++ b/apps/coder_app/lib/src/host_models.dart @@ -0,0 +1,333 @@ +import 'package:coder_client/coder_client.dart'; +import 'package:coder_protocol/coder_protocol.dart'; +import 'package:meta/meta.dart'; + +/// Stable identifier reserved for the app-owned desktop daemon. +const String embeddedHostId = 'embedded'; + +/// Settings that are meaningful before any daemon connection exists. +final class AppSettings { + /// Creates application settings. + const AppSettings({ + this.embeddedDaemonEnabled = true, + this.lastActiveHostId, + this.lastWorktree, + this.sessionTabs = const {}, + }); + + /// Whether desktop should manage an app-owned daemon. + final bool embeddedDaemonEnabled; + + /// Last host selected by the user, including an offline host. + final String? lastActiveHostId; + + /// Last selected worktree in the unified workspace tree. + final WorkspaceSelection? lastWorktree; + + /// Locally-open session tabs keyed by [WorkspaceSelection.storageKey]. + final Map sessionTabs; + + /// Returns settings with selected fields replaced. + AppSettings copyWith({ + bool? embeddedDaemonEnabled, + String? lastActiveHostId, + bool clearLastActiveHost = false, + WorkspaceSelection? lastWorktree, + bool clearLastWorktree = false, + Map? sessionTabs, + }) => AppSettings( + embeddedDaemonEnabled: embeddedDaemonEnabled ?? this.embeddedDaemonEnabled, + lastActiveHostId: clearLastActiveHost + ? null + : lastActiveHostId ?? this.lastActiveHostId, + lastWorktree: clearLastWorktree ? null : lastWorktree ?? this.lastWorktree, + sessionTabs: sessionTabs ?? this.sessionTabs, + ); +} + +/// Composite identity for a checkout owned by one daemon profile. +@immutable +final class WorkspaceSelection { + /// Creates a worktree selection. + const WorkspaceSelection({ + required this.hostId, + required this.workspaceId, + required this.worktreeId, + }); + + /// App-local daemon profile identity. + final String hostId; + + /// Daemon-local repository identity. + final String workspaceId; + + /// Daemon-local checkout identity. + final String worktreeId; + + /// Stable device-local key for tab preferences. + String get storageKey => '$hostId\u0000$workspaceId\u0000$worktreeId'; + + @override + bool operator ==(Object other) => + other is WorkspaceSelection && + other.hostId == hostId && + other.workspaceId == workspaceId && + other.worktreeId == worktreeId; + + @override + int get hashCode => Object.hash(hostId, workspaceId, worktreeId); +} + +/// Device-local visible session tabs for one checkout. +final class SessionTabPreference { + /// Creates session-tab preferences. + const SessionTabPreference({ + this.openAgentIds = const [], + this.selectedAgentId, + }); + + /// Session IDs visible as tabs, in display order. + final List openAgentIds; + + /// Active session tab. + final String? selectedAgentId; +} + +/// Persisted, non-secret configuration for one remote daemon. +final class RemoteDaemonProfile { + /// Creates a remote daemon profile. + const RemoteDaemonProfile({ + required this.id, + required this.label, + required this.websocketUri, + required this.autoConnect, + required this.createdAt, + required this.updatedAt, + this.serverId, + this.lastConnectedAt, + }); + + /// App-generated identity used by routes even while offline. + final String id; + + /// User-visible daemon label. + final String label; + + /// WebSocket endpoint without credentials. + final Uri websocketUri; + + /// Whether this daemon connects when the app starts. + final bool autoConnect; + + /// Authoritative identity learned from a successful handshake. + final String? serverId; + + /// Creation time in UTC. + final DateTime createdAt; + + /// Last profile update time in UTC. + final DateTime updatedAt; + + /// Last successful handshake time in UTC. + final DateTime? lastConnectedAt; + + /// Returns a profile with selected fields replaced. + RemoteDaemonProfile copyWith({ + String? label, + Uri? websocketUri, + bool? autoConnect, + String? serverId, + DateTime? updatedAt, + DateTime? lastConnectedAt, + }) => RemoteDaemonProfile( + id: id, + label: label ?? this.label, + websocketUri: websocketUri ?? this.websocketUri, + autoConnect: autoConnect ?? this.autoConnect, + serverId: serverId ?? this.serverId, + createdAt: createdAt, + updatedAt: updatedAt ?? this.updatedAt, + lastConnectedAt: lastConnectedAt ?? this.lastConnectedAt, + ); +} + +/// Origin of a daemon runtime displayed by the app. +enum HostKind { + /// App-owned desktop isolate. + embedded, + + /// User-configured WebSocket endpoint. + remote, +} + +/// Connection lifecycle for one independent host runtime. +enum HostRuntimeStatus { + /// Persisted but not currently configured to connect. + idle, + + /// Initial connection is in progress. + connecting, + + /// Handshake completed and RPCs are available. + online, + + /// An established client is reconnecting. + reconnecting, + + /// A retryable connection failed. + offline, + + /// A permanent configuration or authentication error occurred. + error, + + /// Another profile already resolved to the same daemon server ID. + conflict, +} + +/// Immutable state for one embedded or remote daemon runtime. +final class HostRuntimeSnapshot { + /// Creates one host runtime snapshot. + const HostRuntimeSnapshot({ + required this.id, + required this.label, + required this.kind, + required this.status, + this.endpoint, + this.api, + this.serverInfo, + this.error, + this.conflictingHostId, + }); + + /// Stable app host ID. + final String id; + + /// User-visible label. + final String label; + + /// Embedded or remote origin. + final HostKind kind; + + /// Current lifecycle state. + final HostRuntimeStatus status; + + /// Transport endpoint when known. + final HostEndpoint? endpoint; + + /// Connected daemon API, available only while online or reconnecting. + final CoderApi? api; + + /// Handshake metadata from the daemon. + final ServerInfoDto? serverInfo; + + /// Safe user-facing failure message. + final String? error; + + /// Existing profile that resolved to the same server ID. + final String? conflictingHostId; + + /// Whether RPC calls may currently be issued. + bool get connected => status == HostRuntimeStatus.online && api != null; + + /// Returns a snapshot with selected fields replaced. + HostRuntimeSnapshot copyWith({ + String? label, + HostRuntimeStatus? status, + HostEndpoint? endpoint, + CoderApi? api, + ServerInfoDto? serverInfo, + String? error, + String? conflictingHostId, + bool clearApi = false, + bool clearError = false, + bool clearConflict = false, + }) => HostRuntimeSnapshot( + id: id, + label: label ?? this.label, + kind: kind, + status: status ?? this.status, + endpoint: endpoint ?? this.endpoint, + api: clearApi ? null : api ?? this.api, + serverInfo: serverInfo ?? this.serverInfo, + error: clearError ? null : error ?? this.error, + conflictingHostId: clearConflict + ? null + : conflictingHostId ?? this.conflictingHostId, + ); +} + +/// Complete daemon-independent state consumed by the app shell. +final class HostRegistryState { + /// Creates registry state. + const HostRegistryState({ + required this.settings, + required this.profiles, + required this.runtimes, + }); + + /// Device-local app settings. + final AppSettings settings; + + /// Persisted remote daemon profiles. + final List profiles; + + /// Runtime state keyed by stable app host ID. + final Map runtimes; + + /// Returns state with selected fields replaced. + HostRegistryState copyWith({ + AppSettings? settings, + List? profiles, + Map? runtimes, + }) => HostRegistryState( + settings: settings ?? this.settings, + profiles: profiles ?? this.profiles, + runtimes: runtimes ?? this.runtimes, + ); +} + +/// Stable classification for failures before an API connection exists. +enum HostConnectionFailureKind { + /// Invalid user-entered endpoint. + invalidEndpoint, + + /// Bearer authentication was rejected. + authentication, + + /// Daemon and client protocol versions differ. + protocolMismatch, + + /// Retryable socket, DNS, or service availability failure. + network, +} + +/// Typed failure used by connection adapters and retry policy. +final class HostConnectionFailure implements Exception { + /// Creates an invalid-endpoint failure. + const HostConnectionFailure.invalidEndpoint(this.message) + : kind = HostConnectionFailureKind.invalidEndpoint; + + /// Creates an authentication failure. + const HostConnectionFailure.authentication(this.message) + : kind = HostConnectionFailureKind.authentication; + + /// Creates a protocol mismatch failure. + const HostConnectionFailure.protocolMismatch(this.message) + : kind = HostConnectionFailureKind.protocolMismatch; + + /// Creates a retryable network failure. + const HostConnectionFailure.network(this.message) + : kind = HostConnectionFailureKind.network; + + /// Failure category. + final HostConnectionFailureKind kind; + + /// Safe display message. + final String message; + + /// Whether automatic connection attempts may continue. + bool get retryable => kind == HostConnectionFailureKind.network; + + @override + String toString() => message; +} diff --git a/apps/coder_app/lib/src/host_ports.dart b/apps/coder_app/lib/src/host_ports.dart new file mode 100644 index 0000000..a625971 --- /dev/null +++ b/apps/coder_app/lib/src/host_ports.dart @@ -0,0 +1,165 @@ +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_client/coder_client.dart'; + +export 'package:coder_app/src/ports.dart'; + +/// Stores daemon-independent application settings. +abstract interface class AppSettingsRepository { + /// Loads settings, returning stable defaults for a fresh install. + Future loadSettings(); + + /// Persists the complete settings value. + Future saveSettings(AppSettings settings); +} + +/// Stores non-secret remote daemon profiles. +abstract interface class RemoteHostRepository { + /// Lists every configured remote daemon. + Future> listProfiles(); + + /// Creates or replaces one profile by ID. + Future upsertProfile(RemoteDaemonProfile profile); + + /// Deletes one profile by ID. + Future deleteProfile(String profileId); +} + +/// Stores bearer tokens separately from non-secret profiles. +abstract interface class RemoteHostCredentialStore { + /// Reads the bearer token for one profile. + Future readBearerToken(String profileId); + + /// Writes the bearer token for one profile. + Future writeBearerToken(String profileId, String token); + + /// Deletes the bearer token for one profile. + Future deleteBearerToken(String profileId); +} + +/// Opens one typed daemon API without owning profile persistence. +abstract interface class HostClientFactory { + /// Connects and completes after the daemon handshake succeeds. + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }); +} + +/// Running app-owned daemon information passed to the host registry. +abstract interface class EmbeddedDaemonSession { + /// Bound local endpoint. + HostEndpoint get endpoint; + + /// Bearer and local-administration credentials. + DaemonCredentials get credentials; + + /// Daemon identity known before the client handshake. + String get serverId; + + /// Stops only this app-owned daemon. + Future stop(); +} + +/// Optional desktop port for starting an app-owned daemon. +abstract interface class EmbeddedDaemonLauncher { + /// Starts one daemon session. + Future start(); +} + +/// Injectable asynchronous delay used by reconnect loops. +abstract interface class AppDelay { + /// Completes after [duration]. + Future wait(Duration duration); +} + +/// Production wall-clock delay. +final class SystemAppDelay implements AppDelay { + /// Creates the system delay adapter. + const SystemAppDelay(); + + @override + Future wait(Duration duration) => Future.delayed(duration); +} + +/// Produces a capped retry delay for one-based attempts. +abstract interface class RetryDelayPolicy { + /// Returns the delay for [attempt]. + Duration delayFor(int attempt); +} + +/// Capped exponential retry policy shared by every host runtime. +final class ExponentialRetryDelayPolicy implements RetryDelayPolicy { + /// Creates the default retry policy. + const ExponentialRetryDelayPolicy(); + + @override + Duration delayFor(int attempt) => Duration( + seconds: (1 << (attempt - 1).clamp(0, 5)).clamp(1, 30), + ); +} + +/// Deterministic in-memory adapter used by unit and widget compositions. +final class MemoryAppStore + implements + AppSettingsRepository, + RemoteHostRepository, + RemoteHostCredentialStore { + /// Creates an in-memory store. + MemoryAppStore({ + this.settings = const AppSettings(), + List profiles = const [], + Map tokens = const {}, + }) : profiles = List.of(profiles), + tokens = Map.of(tokens); + + /// Current settings value. + AppSettings settings; + + /// Current profile values. + final List profiles; + + /// Current bearer tokens keyed by profile ID. + final Map tokens; + + @override + Future deleteBearerToken(String profileId) async { + tokens.remove(profileId); + } + + @override + Future deleteProfile(String profileId) async { + profiles.removeWhere((profile) => profile.id == profileId); + } + + @override + Future> listProfiles() async => + List.unmodifiable(profiles); + + @override + Future loadSettings() async => settings; + + @override + Future readBearerToken(String profileId) async => tokens[profileId]; + + @override + Future saveSettings(AppSettings settings) async { + this.settings = settings; + } + + @override + Future upsertProfile(RemoteDaemonProfile profile) async { + final index = profiles.indexWhere((item) => item.id == profile.id); + if (index < 0) { + profiles.add(profile); + } else { + profiles[index] = profile; + } + } + + @override + Future writeBearerToken(String profileId, String token) async { + tokens[profileId] = token; + } +} diff --git a/apps/coder_app/lib/src/host_registry.dart b/apps/coder_app/lib/src/host_registry.dart new file mode 100644 index 0000000..46b6b96 --- /dev/null +++ b/apps/coder_app/lib/src/host_registry.dart @@ -0,0 +1,630 @@ +import 'dart:async'; + +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_client/coder_client.dart'; + +final class _RuntimeResource { + _RuntimeResource({required this.generation}); + + int generation; + int retryAttempt = 0; + CoderApi? api; + // The owning HostRegistry cancels this subscription in _stopRuntime and + // close. + // ignore: cancel_subscriptions + StreamSubscription? states; +} + +/// Owns every daemon runtime while keeping connection failures independent. +final class HostRegistry { + /// Creates a host registry from typed persistence and transport ports. + factory HostRegistry({ + required AppSettingsRepository store, + required HostClientFactory clientFactory, + required AppIdGenerator ids, + required AppClock clock, + required AppDelay delay, + required String clientKind, + RemoteHostRepository? profiles, + RemoteHostCredentialStore? credentials, + EmbeddedDaemonLauncher? embeddedLauncher, + RetryDelayPolicy retryPolicy = const ExponentialRetryDelayPolicy(), + }) => HostRegistry._( + settings: store, + profiles: profiles ?? _requireProfiles(store), + credentials: credentials ?? _requireCredentials(store), + clientFactory: clientFactory, + embeddedLauncher: embeddedLauncher, + ids: ids, + clock: clock, + delay: delay, + clientKind: clientKind, + retryPolicy: retryPolicy, + ); + + HostRegistry._({ + required this._settings, + required this._profiles, + required this._credentials, + required this._clientFactory, + required this._embeddedLauncher, + required this._ids, + required this._clock, + required this._delay, + required this._clientKind, + required this._retryPolicy, + }); + + final AppSettingsRepository _settings; + final RemoteHostRepository _profiles; + final RemoteHostCredentialStore _credentials; + final HostClientFactory _clientFactory; + final EmbeddedDaemonLauncher? _embeddedLauncher; + final AppIdGenerator _ids; + final AppClock _clock; + final AppDelay _delay; + final String _clientKind; + final RetryDelayPolicy _retryPolicy; + final Map _resources = {}; + final Map _serverOwners = {}; + final StreamController _changes = + StreamController.broadcast(sync: true); + EmbeddedDaemonSession? _embeddedSession; + HostRegistryState? _state; + bool _closed = false; + + /// Latest loaded registry state. + HostRegistryState get value => + _state ?? (throw StateError('HostRegistry.load must complete first.')); + + /// State changes after the initial [load]. + Stream get changes => _changes.stream; + + /// Hydrates local settings without awaiting daemon startup or connections. + Future load() async { + if (_state != null) return value; + final settings = await _settings.loadSettings(); + final profiles = await _profiles.listProfiles(); + final runtimes = { + if (settings.embeddedDaemonEnabled && _embeddedLauncher != null) + embeddedHostId: const HostRuntimeSnapshot( + id: embeddedHostId, + label: '내장 daemon', + kind: HostKind.embedded, + status: HostRuntimeStatus.connecting, + ), + for (final profile in profiles) + profile.id: HostRuntimeSnapshot( + id: profile.id, + label: profile.label, + kind: HostKind.remote, + status: profile.autoConnect + ? HostRuntimeStatus.connecting + : HostRuntimeStatus.idle, + endpoint: HostEndpoint(websocketUri: profile.websocketUri), + ), + }; + _state = HostRegistryState( + settings: settings, + profiles: List.unmodifiable(profiles), + runtimes: Map.unmodifiable(runtimes), + ); + scheduleMicrotask(() { + if (_closed) return; + if (settings.embeddedDaemonEnabled && _embeddedLauncher != null) { + unawaited(_startEmbedded()); + } + for (final profile in profiles.where((profile) => profile.autoConnect)) { + unawaited(_connectRemote(profile.id)); + } + }); + return value; + } + + /// Saves an offline-capable remote profile and optionally starts connecting. + Future addRemote({ + required String label, + required String address, + required String bearerToken, + required bool autoConnect, + }) async { + _ensureLoaded(); + final endpoint = _parseEndpoint(address); + final token = bearerToken.trim(); + if (token.isEmpty) { + throw const HostConnectionFailure.authentication( + 'Bearer token을 입력하세요.', + ); + } + final now = _clock.nowUtc(); + final profile = RemoteDaemonProfile( + id: _ids.generate(), + label: label.trim().isEmpty + ? endpoint.websocketUri.authority + : label.trim(), + websocketUri: endpoint.websocketUri, + autoConnect: autoConnect, + createdAt: now, + updatedAt: now, + ); + await _credentials.writeBearerToken(profile.id, token); + try { + await _profiles.upsertProfile(profile); + } on Exception { + await _credentials.deleteBearerToken(profile.id); + rethrow; + } + final nextProfiles = [...value.profiles, profile]; + final nextRuntimes = Map.of(value.runtimes) + ..[profile.id] = HostRuntimeSnapshot( + id: profile.id, + label: profile.label, + kind: HostKind.remote, + status: autoConnect + ? HostRuntimeStatus.connecting + : HostRuntimeStatus.idle, + endpoint: endpoint, + ); + _emit( + value.copyWith( + profiles: List.unmodifiable(nextProfiles), + runtimes: Map.unmodifiable(nextRuntimes), + ), + ); + if (autoConnect) unawaited(_connectRemote(profile.id)); + return profile; + } + + /// Updates one remote profile and restarts only its runtime. + Future updateRemote({ + required String profileId, + required String label, + required String address, + required bool autoConnect, + String? replacementBearerToken, + }) async { + final previous = _profile(profileId); + final endpoint = _parseEndpoint(address); + if (replacementBearerToken case final token? when token.trim().isNotEmpty) { + await _credentials.writeBearerToken(profileId, token.trim()); + } + final updated = previous.copyWith( + label: label.trim().isEmpty + ? endpoint.websocketUri.authority + : label.trim(), + websocketUri: endpoint.websocketUri, + autoConnect: autoConnect, + updatedAt: _clock.nowUtc(), + ); + await _profiles.upsertProfile(updated); + await _stopRuntime(profileId); + _replaceProfile(updated); + _replaceRuntime( + HostRuntimeSnapshot( + id: profileId, + label: updated.label, + kind: HostKind.remote, + status: autoConnect + ? HostRuntimeStatus.connecting + : HostRuntimeStatus.idle, + endpoint: endpoint, + ), + ); + if (autoConnect) unawaited(_connectRemote(profileId)); + } + + /// Enables or disables startup connection for one remote profile. + Future setAutoConnect( + String profileId, { + required bool enabled, + }) async { + final profile = _profile(profileId); + await updateRemote( + profileId: profileId, + label: profile.label, + address: profile.websocketUri.toString(), + autoConnect: enabled, + ); + } + + /// Retries one host immediately and resets its backoff. + Future reconnect(String hostId) async { + if (hostId == embeddedHostId) { + await _stopEmbedded(); + _replaceRuntime( + const HostRuntimeSnapshot( + id: embeddedHostId, + label: '내장 daemon', + kind: HostKind.embedded, + status: HostRuntimeStatus.connecting, + ), + ); + await _startEmbedded(); + return; + } + await _stopRuntime(hostId); + final profile = _profile(hostId); + _replaceRuntime( + HostRuntimeSnapshot( + id: hostId, + label: profile.label, + kind: HostKind.remote, + status: HostRuntimeStatus.connecting, + endpoint: HostEndpoint(websocketUri: profile.websocketUri), + ), + ); + await _connectRemote(hostId, manual: true); + } + + /// Deletes one remote profile, runtime, and secret credential. + Future removeRemote(String profileId) async { + await _stopRuntime(profileId); + await _profiles.deleteProfile(profileId); + await _credentials.deleteBearerToken(profileId); + var settings = value.settings; + if (settings.lastActiveHostId == profileId) { + settings = settings.copyWith(clearLastActiveHost: true); + await _settings.saveSettings(settings); + } + if (settings.lastWorktree?.hostId == profileId || + settings.sessionTabs.keys.any( + (key) => key.startsWith('$profileId\u0000'), + )) { + settings = settings.copyWith( + clearLastWorktree: settings.lastWorktree?.hostId == profileId, + sessionTabs: Map.unmodifiable( + Map.of(settings.sessionTabs) + ..removeWhere((key, value) => key.startsWith('$profileId\u0000')), + ), + ); + await _settings.saveSettings(settings); + } + _emit( + value.copyWith( + settings: settings, + profiles: List.unmodifiable( + value.profiles.where((profile) => profile.id != profileId), + ), + runtimes: Map.unmodifiable( + Map.of(value.runtimes) + ..remove(profileId), + ), + ), + ); + } + + /// Persists a host selection without requiring it to be online. + Future selectHost(String hostId) async { + final settings = value.settings.copyWith(lastActiveHostId: hostId); + await _settings.saveSettings(settings); + _emit(value.copyWith(settings: settings)); + } + + /// Persists the selected checkout and its locally-visible session tabs. + Future saveWorkspaceUi({ + required WorkspaceSelection selection, + required SessionTabPreference tabs, + }) async { + final settings = value.settings.copyWith( + lastActiveHostId: selection.hostId, + lastWorktree: selection, + sessionTabs: Map.unmodifiable( + { + ...value.settings.sessionTabs, + selection.storageKey: tabs, + }, + ), + ); + await _settings.saveSettings(settings); + _emit(value.copyWith(settings: settings)); + } + + /// Starts or stops only the app-owned desktop daemon. + Future setEmbeddedDaemonEnabled({required bool enabled}) async { + if (_embeddedLauncher == null) return; + final settings = value.settings.copyWith( + embeddedDaemonEnabled: enabled, + clearLastActiveHost: + !enabled && value.settings.lastActiveHostId == embeddedHostId, + ); + await _settings.saveSettings(settings); + if (!enabled) { + await _stopEmbedded(); + final runtimes = Map.of(value.runtimes) + ..remove(embeddedHostId); + _emit( + value.copyWith( + settings: settings, + runtimes: Map.unmodifiable(runtimes), + ), + ); + return; + } + _emit( + value.copyWith( + settings: settings, + runtimes: Map.unmodifiable( + { + ...value.runtimes, + embeddedHostId: const HostRuntimeSnapshot( + id: embeddedHostId, + label: '내장 daemon', + kind: HostKind.embedded, + status: HostRuntimeStatus.connecting, + ), + }, + ), + ), + ); + await _startEmbedded(); + } + + Future _startEmbedded() async { + final launcher = _embeddedLauncher; + if (launcher == null || _closed) return; + try { + final session = await launcher.start(); + if (_closed || !value.settings.embeddedDaemonEnabled) { + await session.stop(); + return; + } + _embeddedSession = session; + await _connect( + hostId: embeddedHostId, + endpoint: session.endpoint, + credentials: session.credentials, + retry: false, + ); + } on Exception catch (error) { + if (_closed) return; + _setFailure(embeddedHostId, _failureFrom(error), retry: false); + } + } + + Future _connectRemote(String profileId, {bool manual = false}) async { + if (_closed) return; + final profile = _profileOrNull(profileId); + if (profile == null || (!manual && !profile.autoConnect)) return; + final token = await _credentials.readBearerToken(profileId); + if (token == null || token.isEmpty) { + _setFailure( + profileId, + const HostConnectionFailure.authentication('Bearer token이 없습니다.'), + retry: false, + ); + return; + } + await _connect( + hostId: profileId, + endpoint: HostEndpoint(websocketUri: profile.websocketUri), + credentials: DaemonCredentials(bearerToken: token), + retry: profile.autoConnect, + ); + } + + Future _connect({ + required String hostId, + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required bool retry, + }) async { + final resource = _resources.putIfAbsent( + hostId, + () => _RuntimeResource(generation: 0), + ); + final generation = resource.generation; + _updateRuntime( + hostId, + (runtime) => runtime.copyWith( + status: HostRuntimeStatus.connecting, + endpoint: endpoint, + clearError: true, + clearConflict: true, + ), + ); + try { + final api = await _clientFactory.connect( + endpoint: endpoint, + credentials: credentials, + clientId: _ids.generate(), + clientKind: _clientKind, + ); + if (_closed || resource.generation != generation) { + await api.close(); + return; + } + final conflictingHostId = _serverOwners[api.serverInfo.serverId]; + if (conflictingHostId != null && conflictingHostId != hostId) { + await api.close(); + _updateRuntime( + hostId, + (runtime) => runtime.copyWith( + status: HostRuntimeStatus.conflict, + conflictingHostId: conflictingHostId, + error: '같은 daemon이 이미 등록되어 있습니다.', + clearApi: true, + ), + ); + return; + } + _serverOwners[api.serverInfo.serverId] = hostId; + resource + ..api = api + ..retryAttempt = 0; + await resource.states?.cancel(); + resource.states = api.states.listen( + (state) => _handleClientState(hostId, api, state), + ); + _updateRuntime( + hostId, + (runtime) => runtime.copyWith( + status: HostRuntimeStatus.online, + api: api, + serverInfo: api.serverInfo, + clearError: true, + ), + ); + if (hostId != embeddedHostId) { + final profile = _profile(hostId).copyWith( + serverId: api.serverInfo.serverId, + lastConnectedAt: _clock.nowUtc(), + updatedAt: _clock.nowUtc(), + ); + await _profiles.upsertProfile(profile); + _replaceProfile(profile); + } + } on Exception catch (error) { + if (_closed || resource.generation != generation) return; + final failure = _failureFrom(error); + _setFailure(hostId, failure, retry: retry && failure.retryable); + if (retry && failure.retryable) { + resource.retryAttempt += 1; + await _delay.wait(_retryPolicy.delayFor(resource.retryAttempt)); + if (_closed || resource.generation != generation) return; + await _connectRemote(hostId); + } + } + } + + void _handleClientState( + String hostId, + CoderApi api, + ClientConnectionState state, + ) { + if (_closed || value.runtimes[hostId]?.api != api) return; + final status = switch (state) { + ClientConnectionState.connected => HostRuntimeStatus.online, + ClientConnectionState.connecting || + ClientConnectionState.reconnecting => HostRuntimeStatus.reconnecting, + ClientConnectionState.disconnected => HostRuntimeStatus.offline, + }; + _updateRuntime(hostId, (runtime) => runtime.copyWith(status: status)); + } + + void _setFailure( + String hostId, + HostConnectionFailure failure, { + required bool retry, + }) { + _updateRuntime( + hostId, + (runtime) => runtime.copyWith( + status: retry ? HostRuntimeStatus.offline : HostRuntimeStatus.error, + error: failure.message, + clearApi: true, + ), + ); + } + + HostConnectionFailure _failureFrom(Object error) { + if (error is HostConnectionFailure) return error; + if (error is CoderClientException && error.code == 'protocol_mismatch') { + return HostConnectionFailure.protocolMismatch(error.message); + } + return HostConnectionFailure.network('$error'); + } + + HostEndpoint _parseEndpoint(String address) { + try { + return HostEndpoint.parse(address); + } on FormatException catch (error) { + throw HostConnectionFailure.invalidEndpoint(error.message); + } + } + + Future _stopEmbedded() async { + await _stopRuntime(embeddedHostId); + final session = _embeddedSession; + _embeddedSession = null; + await session?.stop(); + } + + Future _stopRuntime(String hostId) async { + final resource = _resources.remove(hostId); + if (resource == null) return; + resource.generation += 1; + await resource.states?.cancel(); + await resource.api?.close(); + _serverOwners.removeWhere((serverId, owner) => owner == hostId); + } + + void _replaceProfile(RemoteDaemonProfile profile) { + _emit( + value.copyWith( + profiles: List.unmodifiable([ + for (final item in value.profiles) + if (item.id == profile.id) profile else item, + ]), + ), + ); + } + + void _replaceRuntime(HostRuntimeSnapshot runtime) { + final runtimes = Map.of(value.runtimes) + ..[runtime.id] = runtime; + _emit( + value.copyWith( + runtimes: Map.unmodifiable(runtimes), + ), + ); + } + + void _updateRuntime( + String hostId, + HostRuntimeSnapshot Function(HostRuntimeSnapshot current) update, + ) { + final current = value.runtimes[hostId]; + if (current == null) return; + _replaceRuntime(update(current)); + } + + void _emit(HostRegistryState next) { + if (_closed) return; + _state = next; + _changes.add(next); + } + + RemoteDaemonProfile _profile(String id) => + _profileOrNull(id) ?? (throw StateError('Unknown remote host: $id')); + + RemoteDaemonProfile? _profileOrNull(String id) { + for (final profile in value.profiles) { + if (profile.id == id) return profile; + } + return null; + } + + void _ensureLoaded() { + if (_state == null) { + throw StateError('HostRegistry.load must complete first.'); + } + } + + /// Closes every client and the app-owned daemon. + Future close() async { + if (_closed) return; + _closed = true; + for (final hostId in List.of(_resources.keys)) { + await _stopRuntime(hostId); + } + final session = _embeddedSession; + _embeddedSession = null; + await session?.stop(); + await _changes.close(); + } + + static RemoteHostRepository _requireProfiles(AppSettingsRepository store) { + if (store case final RemoteHostRepository profiles) return profiles; + throw ArgumentError('A RemoteHostRepository must be provided.'); + } + + static RemoteHostCredentialStore _requireCredentials( + AppSettingsRepository store, + ) { + if (store case final RemoteHostCredentialStore credentials) { + return credentials; + } + throw ArgumentError('A RemoteHostCredentialStore must be provided.'); + } +} diff --git a/apps/coder_app/lib/src/remote_bootstrap.dart b/apps/coder_app/lib/src/remote_bootstrap.dart index 22e5eda..bbff48b 100644 --- a/apps/coder_app/lib/src/remote_bootstrap.dart +++ b/apps/coder_app/lib/src/remote_bootstrap.dart @@ -1,58 +1,22 @@ -import 'package:coder_app/src/bootstrap.dart'; -import 'package:coder_app/src/ports.dart'; -import 'package:coder_client/coder_client.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/app_storage.dart'; +import 'package:coder_app/src/host_ports.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; - -/// RemoteBootstrap defines a public contract. -class RemoteBootstrap implements AppBootstrap { - /// Creates a [RemoteBootstrap]. - RemoteBootstrap({ - FlutterSecureStorage? storage, - this._ids = const UuidAppIdGenerator(), - this._connector = const WebSocketAppClientConnector(), - }) : _storage = storage ?? const FlutterSecureStorage(); - - static const String _addressKey = 'tinyrack_coder.host_address'; - static const String _tokenKey = 'tinyrack_coder.host_token'; - - final FlutterSecureStorage _storage; - final AppIdGenerator _ids; - final AppClientConnector _connector; - - @override - bool get canRegisterLocalWorkspace => false; - - @override - Future autoConnect() async { - final address = await _storage.read(key: _addressKey); - final token = await _storage.read(key: _tokenKey); - if (address == null || token == null) return null; - return _connect(HostEndpoint.parse(address, token: token), persist: false); - } - - @override - Future connectRemote(HostEndpoint endpoint) => - _connect(endpoint, persist: true); - - Future _connect( - HostEndpoint endpoint, { - required bool persist, - }) async { - final client = await _connector.connect( - endpoint: endpoint, - clientId: _ids.generate(), - clientKind: 'mobile', - ); - if (persist) { - await _storage.write( - key: _addressKey, - value: endpoint.websocketUri.toString(), - ); - await _storage.write(key: _tokenKey, value: endpoint.token); - } - return BootstrapConnection(client: client, endpoint: endpoint); - } - - @override - Future close() async {} +import 'package:shared_preferences/shared_preferences.dart'; + +/// Creates remote-only mobile services; no daemon launcher is reachable. +Future createRemoteServices({ + HostClientFactory clients = const WebSocketHostClientFactory(), + FlutterSecureStorage secureStorage = const FlutterSecureStorage(), +}) async { + final store = SharedPreferencesAppStore( + await SharedPreferences.getInstance(), + ); + return AppServices( + settings: store, + profiles: store, + credentials: SecureRemoteHostCredentialStore(secureStorage), + clients: clients, + clientKind: 'mobile', + ); } diff --git a/apps/coder_app/lib/src/settings_page.dart b/apps/coder_app/lib/src/settings_page.dart index a2e74c9..42ec50c 100644 --- a/apps/coder_app/lib/src/settings_page.dart +++ b/apps/coder_app/lib/src/settings_page.dart @@ -15,11 +15,18 @@ final externalUrlOpenerProvider = Provider( /// Provider connection settings for one daemon host. class SettingsPage extends ConsumerStatefulWidget { /// Creates a provider connection settings page. - const SettingsPage({required this.hostId, super.key}); + const SettingsPage({ + required this.hostId, + this.embedded = false, + super.key, + }); /// Route host identifier. final String hostId; + /// Whether the unified settings shell supplies navigation chrome. + final bool embedded; + @override ConsumerState createState() => _SettingsPageState(); } @@ -27,9 +34,20 @@ class SettingsPage extends ConsumerStatefulWidget { class _SettingsPageState extends ConsumerState { final Set _loadingModels = {}; + ProviderSettingsControllerProvider get _provider => + providerSettingsControllerProvider(widget.hostId); + @override Widget build(BuildContext context) { - final asyncState = ref.watch(providerSettingsControllerProvider); + final asyncState = ref.watch(_provider); + final body = asyncState.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stackTrace) => Center(child: Text('$error')), + data: (state) => state == null + ? const Center(child: Text('Daemon 연결이 필요합니다.')) + : _body(state), + ); + if (widget.embedded) return body; return Scaffold( appBar: AppBar( leading: IconButton( @@ -42,27 +60,17 @@ class _SettingsPageState extends ConsumerState { tooltip: 'Catalog 갱신', onPressed: asyncState.asData?.value == null ? null - : () => ref - .read(providerSettingsControllerProvider.notifier) - .refreshCatalog(), + : () => ref.read(_provider.notifier).refreshCatalog(), icon: const Icon(Icons.refresh), ), ], ), - body: asyncState.when( - loading: () => const Center(child: CircularProgressIndicator()), - error: (error, stackTrace) => Center(child: Text('$error')), - data: (state) => state == null - ? const Center(child: Text('Daemon 연결이 필요합니다.')) - : _body(state), - ), + body: body, ); } Widget _body(ProviderSettingsState state) { - final canManage = ref - .read(providerSettingsControllerProvider.notifier) - .canManage; + final canManage = ref.read(_provider.notifier).canManage; final activeConnections = state.connections .where( (connection) => @@ -74,9 +82,7 @@ class _SettingsPageState extends ConsumerState { WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; unawaited( - ref - .read(providerSettingsControllerProvider.notifier) - .loadModels(connection.id), + ref.read(_provider.notifier).loadModels(connection.id), ); }); } @@ -85,11 +91,9 @@ class _SettingsPageState extends ConsumerState { models: state.models, canManage: canManage, onDisconnect: _disconnect, - onSetDefault: (id) => - ref.read(providerSettingsControllerProvider.notifier).setDefault(id), - onSetDefaultModel: (id, model) => ref - .read(providerSettingsControllerProvider.notifier) - .setDefaultModel(id, model), + onSetDefault: (id) => ref.read(_provider.notifier).setDefault(id), + onSetDefaultModel: (id, model) => + ref.read(_provider.notifier).setDefaultModel(id, model), onEditCustom: _editCustom, ); final catalog = _ProviderCatalog( @@ -140,9 +144,8 @@ class _SettingsPageState extends ConsumerState { attempt.status == ProviderAuthAttemptStatus.exchanging) _AuthAttemptBar( attempt: attempt, - onCancel: () => ref - .read(providerSettingsControllerProvider.notifier) - .cancelAuth(attempt.id), + onCancel: () => + ref.read(_provider.notifier).cancelAuth(attempt.id), ), ], ); @@ -155,9 +158,7 @@ class _SettingsPageState extends ConsumerState { } final method = definition.authMethods.single; if (method.flow == ProviderAuthFlow.none) { - await ref - .read(providerSettingsControllerProvider.notifier) - .connectNone(definition.id); + await ref.read(_provider.notifier).connectNone(definition.id); return; } await _showApiKey(definition); @@ -210,7 +211,7 @@ class _SettingsPageState extends ConsumerState { return; } final attempt = await ref - .read(providerSettingsControllerProvider.notifier) + .read(_provider.notifier) .startAuth(definition.id, methodId); final authorizationUrl = attempt.authorizationUrl; if (methodId == 'chatgpt-browser' && authorizationUrl != null) { @@ -226,9 +227,7 @@ class _SettingsPageState extends ConsumerState { builder: (context) => _ApiKeyDialog(providerName: definition.name), ); if (apiKey == null || apiKey.isEmpty) return; - await ref - .read(providerSettingsControllerProvider.notifier) - .connectApiKey(definition.id, apiKey); + await ref.read(_provider.notifier).connectApiKey(definition.id, apiKey); } Future _addCustom() async { @@ -238,7 +237,7 @@ class _SettingsPageState extends ConsumerState { ); if (draft == null) return; final connection = await ref - .read(providerSettingsControllerProvider.notifier) + .read(_provider.notifier) .createCustom( ref.read(appIdGeneratorProvider).generate(), draft.config, @@ -251,7 +250,7 @@ class _SettingsPageState extends ConsumerState { ); if (!mounted || manualModels == null || manualModels.isEmpty) return; await ref - .read(providerSettingsControllerProvider.notifier) + .read(_provider.notifier) .updateCustom( connection.id, draft.config.copyWith(manualModelIds: manualModels), @@ -266,7 +265,7 @@ class _SettingsPageState extends ConsumerState { ); if (draft == null) return; await ref - .read(providerSettingsControllerProvider.notifier) + .read(_provider.notifier) .updateCustom(connection.id, draft.config, apiKey: draft.apiKey); } @@ -291,9 +290,7 @@ class _SettingsPageState extends ConsumerState { ), ); if (confirmed != true) return; - await ref - .read(providerSettingsControllerProvider.notifier) - .disconnect(connection.id); + await ref.read(_provider.notifier).disconnect(connection.id); } } diff --git a/apps/coder_app/macos/Flutter/GeneratedPluginRegistrant.swift b/apps/coder_app/macos/Flutter/GeneratedPluginRegistrant.swift index 37de091..fb39c7e 100644 --- a/apps/coder_app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/apps/coder_app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,10 +7,12 @@ import Foundation import file_selector_macos import flutter_secure_storage_darwin +import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/apps/coder_app/pubspec.yaml b/apps/coder_app/pubspec.yaml index 5ff14c0..0262a85 100644 --- a/apps/coder_app/pubspec.yaml +++ b/apps/coder_app/pubspec.yaml @@ -18,7 +18,9 @@ dependencies: flutter_riverpod: 3.3.2 flutter_secure_storage: ^10.3.1 go_router: ^17.3.0 + meta: ^1.17.0 riverpod_annotation: 4.0.3 + shared_preferences: ^2.5.4 url_launcher: ^6.3.2 uuid: ^4.6.0 diff --git a/apps/coder_app/test/app_bootstrap_test.dart b/apps/coder_app/test/app_bootstrap_test.dart deleted file mode 100644 index c919e15..0000000 --- a/apps/coder_app/test/app_bootstrap_test.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'package:coder_app/src/app.dart'; -import 'package:coder_app/src/bootstrap.dart'; -import 'package:coder_app/src/controller.dart'; -import 'package:coder_client/coder_client.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:go_router/go_router.dart'; - -import 'support/fake_coder_api.dart'; - -void main() { - test('typed routes build canonical nested locations', () { - expect( - const SettingsRoute(hostId: 'server').location, - '/hosts/server/settings', - ); - expect( - const AgentRoute( - hostId: 'server', - workspaceId: 'workspace', - agentId: 'agent', - ).location, - '/hosts/server/workspaces/workspace/agents/agent', - ); - }); - - testWidgets( - 'remote-only bootstrap shows host connection without starting a daemon', - (tester) async { - final bootstrap = _RemoteOnlyFakeBootstrap(); - await tester.pumpWidget(CoderApp(bootstrap: bootstrap)); - await tester.pump(); - - expect(find.text('모바일은 원격 daemon에만 연결합니다.'), findsOneWidget); - expect(bootstrap.autoConnectCalls, 1); - }, - ); - - testWidgets('settings button opens the provider settings screen', ( - tester, - ) async { - final router = GoRouter( - initialLocation: const DashboardRoute(hostId: 'server').location, - routes: $appRoutes, - ); - addTearDown(router.dispose); - await tester.pumpWidget( - ProviderScope( - overrides: [ - bootstrapProvider.overrideWithValue( - FakeAppBootstrap(api: FakeCoderApi()), - ), - ], - child: MaterialApp.router(routerConfig: router), - ), - ); - await tester.pumpAndSettle(); - expect(find.byTooltip('설정'), findsOneWidget); - await tester.tap(find.byTooltip('설정')); - await tester.pumpAndSettle(); - - expect(find.text('Provider 설정'), findsOneWidget); - expect(find.text('기본 모델'), findsOneWidget); - expect(find.text('OpenAI'), findsWidgets); - expect(find.text('Provider 추가'), findsOneWidget); - }); -} - -class _RemoteOnlyFakeBootstrap implements AppBootstrap { - int autoConnectCalls = 0; - - @override - bool get canRegisterLocalWorkspace => false; - - @override - Future autoConnect() async { - autoConnectCalls += 1; - return null; - } - - @override - Future close() async {} - - @override - Future connectRemote(HostEndpoint endpoint) { - throw UnimplementedError(); - } -} diff --git a/apps/coder_app/test/app_flow_test.dart b/apps/coder_app/test/app_flow_test.dart index 6c017dd..766942f 100644 --- a/apps/coder_app/test/app_flow_test.dart +++ b/apps/coder_app/test/app_flow_test.dart @@ -1,6 +1,5 @@ import 'package:coder_app/src/app.dart'; import 'package:coder_app/src/controller.dart'; -import 'package:coder_client/coder_client.dart'; import 'package:coder_protocol/coder_protocol.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -10,398 +9,371 @@ import 'package:go_router/go_router.dart'; import 'support/fake_coder_api.dart'; void main() { - final now = DateTime.utc(2026, 8, 2); + final now = DateTime.utc(2026, 8, 3); final workspace = WorkspaceDto( id: 'workspace', - name: 'Workspace', - rootPath: '/workspace', + name: 'Coder', + rootPath: '/repos/coder', + kind: WorkspaceKind.git, createdAt: now, ); - AgentDto agent( - String id, - AgentStatus status, { - String model = 'gpt-5.6-sol', - }) => AgentDto( - id: id, + final checkout = WorktreeDto( + id: 'checkout', workspaceId: workspace.id, - title: 'Agent $id', + name: 'main', + path: workspace.rootPath, + branch: 'main', + head: 'abc', + kind: WorktreeKind.checkout, + isCoderOwned: false, + createdAt: now, + ); + AgentDto session(String id) => AgentDto( + id: id, + worktreeId: checkout.id, + title: 'Session $id', providerConnectionId: 'openai', - model: model, - status: status, + model: 'gpt-5.6-sol', + status: AgentStatus.idle, permissionMode: PermissionMode.ask, createdAt: now, updatedAt: now, ); - final selected = agent('selected', AgentStatus.idle); - final approval = ApprovalRequestDto( - id: 'approval', - agentId: selected.id, - turnId: 'turn', - toolCallId: 'call', - toolName: 'apply_patch', - risk: ToolRisk.write, - arguments: const {'patch': 'diff'}, - status: ApprovalStatus.pending, - createdAt: now, + + testWidgets( + 'desktop workspace uses host repository tree and session tabs', + (tester) async { + await tester.binding.setSurfaceSize(const Size(1280, 800)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + final first = session('one'); + final second = session('two'); + final api = FakeCoderApi( + workspaces: [workspace], + worktrees: [checkout], + agents: [first, second], + ); + final router = await _pumpRoute( + tester, + api, + SessionRoute( + hostId: 'server', + workspaceId: workspace.id, + worktreeId: checkout.id, + agentId: first.id, + ).location, + ); + addTearDown(router.dispose); + + expect(find.text('Workspaces'), findsOneWidget); + expect(find.text('Test daemon'), findsOneWidget); + expect(find.text('Coder'), findsOneWidget); + expect(find.text('main'), findsOneWidget); + expect(find.text('Agents'), findsNothing); + expect(find.text('Session one'), findsWidgets); + expect(find.byTooltip('새 worktree'), findsOneWidget); + + await tester.tap(find.byTooltip('모든 session')); + await tester.pumpAndSettle(); + expect(find.text('Session two'), findsOneWidget); + await tester.tap(find.text('Session two')); + await tester.pumpAndSettle(); + expect(router.routeInformationProvider.value.uri.path, contains('two')); + }, ); - final timeline = [ - TimelineEventDto( - agentId: selected.id, - sequence: 1, - turnId: 'turn', - type: 'user.message', - data: const {'text': 'Please inspect'}, - createdAt: now, - ), - TimelineEventDto( - agentId: selected.id, - sequence: 2, - turnId: 'turn', - type: 'assistant.delta', - data: const {'text': 'Hello '}, - createdAt: now, - ), - TimelineEventDto( - agentId: selected.id, - sequence: 3, - turnId: 'turn', - type: 'assistant.delta', - data: const {'text': 'world'}, - createdAt: now, - ), - TimelineEventDto( - agentId: selected.id, - sequence: 4, - turnId: 'turn', - type: 'tool.completed', - data: const {'name': 'read_file', 'isError': false}, - createdAt: now, - ), - TimelineEventDto( - agentId: selected.id, - sequence: 5, - turnId: 'turn', - type: 'approval.requested', - data: {'approval': approval.toJson()}, - createdAt: now, - ), - ]; - testWidgets('desktop dashboard renders all panes and conversation commands', ( + testWidgets('session tabs close locally and reopen from the picker', ( tester, ) async { - await tester.binding.setSurfaceSize(const Size(1400, 900)); + await tester.binding.setSurfaceSize(const Size(1100, 760)); addTearDown(() => tester.binding.setSurfaceSize(null)); + final first = session('one'); final api = FakeCoderApi( workspaces: [workspace], - agents: [ - selected, - agent('running', AgentStatus.running), - agent('approval', AgentStatus.waitingForApproval), - agent('failed', AgentStatus.failed), - ], - timelines: >{selected.id: timeline}, + worktrees: [checkout], + agents: [first], ); final router = await _pumpRoute( tester, api, - AgentRoute( + SessionRoute( hostId: 'server', workspaceId: workspace.id, - agentId: selected.id, + worktreeId: checkout.id, + agentId: first.id, ).location, ); addTearDown(router.dispose); - expect(find.text('Workspaces'), findsOneWidget); - expect(find.text('Agents'), findsOneWidget); - expect( - find.text('Agent selected'), - findsWidgets, - reason: _visibleText(tester), - ); - await tester.drag(find.byType(ListView).last, const Offset(0, 500)); + await tester.tap(find.byTooltip('탭 닫기')); await tester.pumpAndSettle(); - expect(find.text('Hello world', findRichText: true), findsOneWidget); - expect(find.text('승인 필요 · apply_patch'), findsOneWidget); + expect(find.text('새 session 시작'), findsOneWidget); + expect(await api.listAgents(worktreeId: checkout.id), [first]); - await tester.enterText( - find.byType(TextField).last, - ' run tests ', - ); - await tester.tap(find.byIcon(Icons.arrow_upward)); - await tester.pump(); - expect(api.startedPrompts, ['run tests']); - await tester.tap(find.text('승인')); - await tester.pump(); - expect(api.approvalDecisions.single.approved, isTrue); - - api.emit( - AgentUpdatedClientEvent(selected.copyWith(status: AgentStatus.running)), - ); - await tester.pump(); - expect(find.text('중지'), findsOneWidget); - await tester.tap(find.text('중지')); - await tester.pump(); - expect(api.cancelledAgents, [selected.id]); + await tester.tap(find.byTooltip('모든 session')); + await tester.pumpAndSettle(); + await tester.tap(find.text('Session one')); + await tester.pumpAndSettle(); + expect(find.byTooltip('탭 닫기'), findsOneWidget); }); - testWidgets('responsive agent creation and pre-turn configuration work', ( + testWidgets('creates and archives a managed worktree from the repository', ( tester, ) async { - await tester.binding.setSurfaceSize(const Size(700, 900)); + await tester.binding.setSurfaceSize(const Size(1100, 760)); addTearDown(() => tester.binding.setSurfaceSize(null)); - final customProvider = ProviderConnectionDto( - id: 'custom', - definitionId: 'custom', - displayName: 'Custom API', - status: ProviderConnectionStatus.connected, - authKind: ProviderAuthKind.apiKey, - credentialOrigin: ProviderCredentialOrigin.stored, - isDefault: false, - defaultModelId: 'custom-model', - customConfig: const CustomProviderConfigDto( - name: 'Custom API', - baseUrl: 'http://localhost:8080/v1', - apiFormat: ProviderApiFormat.chatCompletions, - authenticationRequired: true, - manualModelIds: ['custom-model'], - ), - createdAt: now, - updatedAt: now, - ); final api = FakeCoderApi( workspaces: [workspace], - catalog: ProviderCatalogDto( - definitions: const [ - ProviderDefinitionDto( - id: 'openai', - name: 'OpenAI', - description: 'OpenAI Platform API.', - authMethods: [ - ProviderAuthMethodDto( - id: 'api-key', - label: 'API key', - kind: ProviderAuthKind.apiKey, - flow: ProviderAuthFlow.apiKey, - ), - ], - recommendedModelIds: ['gpt-5.6-sol'], - ), - ], - source: ProviderCatalogSource.bundled, - updatedAt: now, - ), - connections: [ - ProviderConnectionDto( - id: 'openai', - definitionId: 'openai', - displayName: 'OpenAI', - status: ProviderConnectionStatus.connected, - authKind: ProviderAuthKind.apiKey, - credentialOrigin: ProviderCredentialOrigin.environment, - isDefault: true, - defaultModelId: 'gpt-5.6-sol', - createdAt: now, - updatedAt: now, - ), - customProvider, - ], - models: const >{ - 'custom': [ - ProviderModelDto( - connectionId: 'custom', - id: 'custom-model', - label: 'custom-model', - source: ProviderModelSource.manual, - capabilities: ModelCapabilitiesDto( - streaming: CapabilitySupport.supported, - toolCalling: CapabilitySupport.supported, - source: CapabilitySource.manual, - ), - ), - ], - }, + worktrees: [checkout], ); final router = await _pumpRoute( tester, api, - WorkspaceRoute(hostId: 'server', workspaceId: workspace.id).location, + WorktreeRoute( + hostId: 'server', + workspaceId: workspace.id, + worktreeId: checkout.id, + ).location, ); addTearDown(router.dispose); - expect( - find.text('새 agent를 만들어 시작하세요.'), - findsOneWidget, - reason: _visibleText(tester), - ); - await tester.tap(find.byTooltip('Agent 생성')); - await tester.pumpAndSettle(); - expect(find.text('새 agent'), findsOneWidget); - expect(find.text('gpt-5.6-sol'), findsWidgets); - await tester.tap(_dropdown('API provider')); - await tester.pumpAndSettle(); - await tester.tap(find.text('Custom API').last); + await tester.tap(find.byTooltip('새 worktree')); await tester.pumpAndSettle(); - expect(find.text('custom-model'), findsWidgets); - await tester.enterText(_field('이름'), ''); - await tester.tap(_dropdown('Reasoning effort')); - await tester.pumpAndSettle(); - await tester.tap(find.text('high').last); + await tester.enterText( + find.widgetWithText(TextField, '새 branch 이름'), + 'feature/settings', + ); + await tester.tap(find.widgetWithText(FilledButton, '생성')); await tester.pumpAndSettle(); - await tester.ensureVisible(_dropdown('Permission mode')); - await tester.tap(_dropdown('Permission mode')); + expect(find.text('feature/settings'), findsWidgets); + + final menus = find.byTooltip('Worktree 메뉴'); + await tester.tap(menus.last); await tester.pumpAndSettle(); - await tester.tap(find.text('workspaceWrite').last); + await tester.tap(find.text('Archive')); await tester.pumpAndSettle(); - await tester.tap(find.text('생성')); + expect(find.textContaining('Archive할까요?'), findsOneWidget); + await tester.tap(find.widgetWithText(FilledButton, 'Archive')); await tester.pumpAndSettle(); - expect(find.text('요청을 입력해 coding agent를 시작하세요.'), findsOneWidget); - expect( - (await api.listAgents()).single.providerConnectionId, - customProvider.id, + expect(find.text('feature/settings'), findsNothing); + expect(router.routeInformationProvider.value.uri.path, '/'); + }); + + testWidgets('folder add selects a daemon and remote path before register', ( + tester, + ) async { + await tester.binding.setSurfaceSize(const Size(1100, 760)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + final api = FakeCoderApi(); + final router = await _pumpRoute( + tester, + api, + const WorkspaceHomeRoute().location, ); - expect((await api.listAgents()).single.title, 'Coding session'); + addTearDown(router.dispose); - await tester.tap(find.byTooltip('Agent 모델 설정')); + await tester.tap(find.byTooltip('폴더 추가').first); await tester.pumpAndSettle(); - expect(find.text('Agent 모델 설정'), findsOneWidget); - await tester.tap(_dropdown('API provider')); + await tester.tap( + find.descendant( + of: find.byType(SimpleDialog), + matching: find.text('Test daemon'), + ), + ); await tester.pumpAndSettle(); - await tester.tap(find.text('OpenAI').last); + final pathField = find.widgetWithText(TextField, 'Daemon 경로'); + await tester.enterText(pathField, '/srv/repositories/project'); await tester.pumpAndSettle(); - await tester.tap(find.text('저장')); + expect(find.text('project'), findsOneWidget); + await tester.tap(find.widgetWithText(FilledButton, '등록')); await tester.pumpAndSettle(); - expect(find.text('요청을 입력해 coding agent를 시작하세요.'), findsOneWidget); - expect((await api.listAgents()).single.providerConnectionId, 'openai'); + + expect(find.text('project'), findsWidgets); + expect( + router.routeInformationProvider.value.uri.path, + startsWith('/workspaces/server/'), + ); }); - testWidgets('host page validates and connects an explicit endpoint', ( + testWidgets('mobile opens selected worktree as a session-only detail', ( tester, ) async { - final api = FakeCoderApi(); - await tester.pumpWidget( - CoderApp( - bootstrap: FakeAppBootstrap( - api: api, - autoConnectEnabled: false, - connectFailures: 1, - ), - ), + await tester.binding.setSurfaceSize(const Size(390, 780)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + final api = FakeCoderApi( + workspaces: [workspace], + worktrees: [checkout], ); - await tester.pumpAndSettle(); - expect(find.text('로컬 daemon을 시작하거나 원격 host에 연결합니다.'), findsOneWidget); + final router = await _pumpRoute( + tester, + api, + WorktreeRoute( + hostId: 'server', + workspaceId: workspace.id, + worktreeId: checkout.id, + ).location, + ); + addTearDown(router.dispose); - await tester.enterText(_field('Daemon WebSocket 주소'), '127.0.0.1:7444'); - await tester.tap(find.text('연결')); + expect(find.text('Repositories'), findsNothing); + expect(find.text('새 session 시작'), findsOneWidget); + await tester.tap(find.byIcon(Icons.arrow_back)); await tester.pumpAndSettle(); - expect(find.textContaining('Invalid'), findsOneWidget); + expect(find.text('Repositories'), findsOneWidget); + }); + testWidgets('creates a session and sends a coding request', (tester) async { + await tester.binding.setSurfaceSize(const Size(1100, 760)); + addTearDown(() => tester.binding.setSurfaceSize(null)); + final api = FakeCoderApi( + workspaces: [workspace], + worktrees: [checkout], + ); + final router = await _pumpRoute( + tester, + api, + WorktreeRoute( + hostId: 'server', + workspaceId: workspace.id, + worktreeId: checkout.id, + ).location, + ); + addTearDown(router.dispose); + + await tester.tap(find.text('새 session 시작')); + await tester.pumpAndSettle(); await tester.enterText( - _field('Daemon WebSocket 주소'), - '127.0.0.1:7444', + find.widgetWithText(TextField, '이름'), + 'Refactor API', ); - await tester.enterText(_field('Bearer token'), 'token'); - await tester.tap(find.text('연결')); + await tester.tap(find.widgetWithText(FilledButton, '생성')); await tester.pumpAndSettle(); - expect(find.text('등록된 workspace가 없습니다.'), findsOneWidget); + expect(find.text('Refactor API'), findsWidgets); + + await tester.enterText( + find.widgetWithText(TextField, '코딩 요청을 입력하세요…'), + 'Run the tests', + ); + await tester.tap(find.byIcon(Icons.arrow_upward)); + await tester.pump(); + expect(api.startedPrompts, ['Run the tests']); }); - testWidgets('dashboard fallbacks cover disconnected and empty selections', ( + testWidgets('workspace shell is visible before any daemon exists', ( tester, ) async { - final disconnectedApi = FakeCoderApi(); + final api = FakeCoderApi(); + final router = GoRouter( + initialLocation: const WorkspaceHomeRoute().location, + routes: $appRoutes, + ); + addTearDown(router.dispose); await tester.pumpWidget( ProviderScope( overrides: [ - bootstrapProvider.overrideWithValue( - FakeAppBootstrap( - api: disconnectedApi, - autoConnectEnabled: false, - ), + appServicesProvider.overrideWithValue( + fakeAppServices(api, connected: false), ), ], - child: const MaterialApp( - home: DashboardPage(hostId: 'server'), - ), + child: MaterialApp.router(routerConfig: router), ), ); await tester.pump(); - expect(find.text('Host 연결로 돌아가기'), findsOneWidget); - await tester.pumpWidget(const SizedBox.shrink()); - await tester.pumpAndSettle(); - await disconnectedApi.close(); + expect(find.text('Workspaces'), findsOneWidget); + expect(find.byTooltip('설정'), findsOneWidget); + }); + testWidgets('settings combines Provider and Daemon categories', ( + tester, + ) async { + await tester.binding.setSurfaceSize(const Size(1100, 760)); + addTearDown(() => tester.binding.setSurfaceSize(null)); final api = FakeCoderApi(); final router = await _pumpRoute( tester, api, - const DashboardRoute(hostId: 'server').location, + const ProviderSettingsRoute(hostId: 'server').location, ); addTearDown(router.dispose); - expect(find.text('등록된 workspace가 없습니다.'), findsOneWidget); + expect(find.text('Provider'), findsOneWidget); + expect(find.text('Daemon'), findsWidgets); + expect(find.text('Test daemon'), findsOneWidget); + await tester.tap(find.text('Daemon').first); + await tester.pumpAndSettle(); + expect(find.text('원격 daemons'), findsOneWidget); }); - testWidgets( - 'timeline and approval cards render text and argument fallbacks', - ( - tester, - ) async { - await tester.binding.setSurfaceSize(const Size(800, 900)); - addTearDown(() => tester.binding.setSurfaceSize(null)); - final api = FakeCoderApi( - timelines: >{ - selected.id: [], - }, - ); - await tester.pumpWidget( - ProviderScope( - overrides: [ - bootstrapProvider.overrideWithValue(FakeAppBootstrap(api: api)), - ], - child: MaterialApp( - home: Scaffold( - body: ListView( - children: [ - TimelineCard(event: timeline.first), - TimelineCard(event: timeline[1]), - TimelineCard(event: timeline[3]), - ApprovalCard(approval: approval.copyWith(preview: null)), - ], - ), + testWidgets('timeline and approval cards render typed event content', ( + tester, + ) async { + final agent = session('approval'); + final approval = ApprovalRequestDto( + id: 'approval', + agentId: agent.id, + turnId: 'turn', + toolCallId: 'call', + toolName: 'apply_patch', + risk: ToolRisk.write, + arguments: const {'patch': 'diff'}, + status: ApprovalStatus.pending, + createdAt: now, + ); + final event = TimelineEventDto( + agentId: agent.id, + sequence: 1, + type: 'user.message', + data: const {'text': 'Inspect this'}, + createdAt: now, + ); + final api = FakeCoderApi(agents: [agent]); + await tester.pumpWidget( + ProviderScope( + overrides: [ + appServicesProvider.overrideWithValue(fakeAppServices(api)), + ], + child: MaterialApp( + home: Scaffold( + body: ListView( + children: [ + Consumer( + builder: (context, ref, child) => Text( + ref + .watch(hostRegistryControllerProvider) + .asData + ?.value + .runtimes['server'] + ?.connected == + true + ? 'ready' + : 'waiting', + ), + ), + TimelineCard(event: event), + ApprovalCard(hostId: 'server', approval: approval), + ], ), ), ), - ); - await tester.pumpAndSettle(); - expect(find.text('You'), findsOneWidget); - expect(find.text('Assistant'), findsOneWidget); - expect(find.text('tool.completed'), findsOneWidget); - expect(find.textContaining('"patch": "diff"'), findsOneWidget); - await tester.ensureVisible(find.text('거부')); - await tester.tap(find.text('거부')); - await tester.pump(); - expect(api.approvalDecisions.single.approved, isFalse); - }, - ); + ), + ); + await tester.pumpAndSettle(); + expect(find.text('ready'), findsOneWidget); + expect(find.text('You'), findsOneWidget); + expect(find.text('Inspect this', findRichText: true), findsOneWidget); + expect(find.text('승인 필요 · apply_patch'), findsOneWidget); + await tester.tap(find.widgetWithText(TextButton, '거부')); + await tester.pumpAndSettle(); + await tester.tap(find.widgetWithText(FilledButton, '승인')); + await tester.pumpAndSettle(); + expect( + api.approvalDecisions, + <({bool approved, String id})>[ + (id: 'approval', approved: false), + (id: 'approval', approved: true), + ], + ); + }); } -String _visibleText(WidgetTester tester) => tester - .widgetList(find.byType(Text)) - .map((widget) => widget.data) - .whereType() - .join(' | '); - -Finder _field(String label) => find.byWidgetPredicate( - (widget) => widget is TextField && widget.decoration?.labelText == label, -); - -Finder _dropdown(String label) => find.byWidgetPredicate( - (widget) => - widget is DropdownButtonFormField && - widget.decoration.labelText == label, -); - Future _pumpRoute( WidgetTester tester, FakeCoderApi api, @@ -411,7 +383,7 @@ Future _pumpRoute( await tester.pumpWidget( ProviderScope( overrides: [ - bootstrapProvider.overrideWithValue(FakeAppBootstrap(api: api)), + appServicesProvider.overrideWithValue(fakeAppServices(api)), ], child: MaterialApp.router(routerConfig: router), ), diff --git a/apps/coder_app/test/app_storage_test.dart b/apps/coder_app/test/app_storage_test.dart new file mode 100644 index 0000000..e5e2d2a --- /dev/null +++ b/apps/coder_app/test/app_storage_test.dart @@ -0,0 +1,180 @@ +import 'dart:convert'; + +import 'package:coder_app/src/app_storage.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + setUp(() { + SharedPreferences.setMockInitialValues({}); + FlutterSecureStorage.setMockInitialValues({}); + }); + + test('persists typed settings and profiles without bearer tokens', () async { + final preferences = await SharedPreferences.getInstance(); + final store = SharedPreferencesAppStore(preferences); + const secureStorage = FlutterSecureStorage(); + const credentials = SecureRemoteHostCredentialStore(secureStorage); + final now = DateTime.utc(2026, 8, 3); + final profile = RemoteDaemonProfile( + id: 'host-id', + label: 'Production', + websocketUri: Uri.parse('wss://coder.example.com/ws'), + autoConnect: true, + serverId: 'server-id', + createdAt: now, + updatedAt: now, + lastConnectedAt: now, + ); + + const selection = WorkspaceSelection( + hostId: 'host-id', + workspaceId: 'workspace-id', + worktreeId: 'worktree-id', + ); + await store.saveSettings( + AppSettings( + embeddedDaemonEnabled: false, + lastActiveHostId: 'host-id', + lastWorktree: selection, + sessionTabs: { + selection.storageKey: const SessionTabPreference( + openAgentIds: ['agent-1', 'agent-2'], + selectedAgentId: 'agent-2', + ), + }, + ), + ); + await store.upsertProfile(profile); + await credentials.writeBearerToken('host-id', 'bearer-secret'); + + final restored = await store.loadSettings(); + expect(restored.lastWorktree, selection); + expect( + restored.sessionTabs[selection.storageKey]?.openAgentIds, + ['agent-1', 'agent-2'], + ); + expect( + (await store.listProfiles()).single.websocketUri, + profile.websocketUri, + ); + expect(await credentials.readBearerToken('host-id'), 'bearer-secret'); + final document = preferences.getString( + SharedPreferencesAppStore.documentKey, + ); + expect(document, isNot(contains('bearer-secret'))); + }); + + test('fresh storage ignores legacy singleton host keys', () async { + SharedPreferences.setMockInitialValues({ + 'tinyrack_coder.host_address': 'ws://legacy.test/ws', + 'tinyrack_coder.host_token': 'legacy-token', + }); + final store = SharedPreferencesAppStore( + await SharedPreferences.getInstance(), + ); + + expect(await store.listProfiles(), isEmpty); + expect((await store.loadSettings()).embeddedDaemonEnabled, isTrue); + }); + + test('updates and removes profiles and their secure credentials', () async { + final preferences = await SharedPreferences.getInstance(); + final store = SharedPreferencesAppStore(preferences); + const credentials = SecureRemoteHostCredentialStore( + FlutterSecureStorage(), + ); + final createdAt = DateTime.utc(2026, 8, 3); + final original = RemoteDaemonProfile( + id: 'host-id', + label: 'Original', + websocketUri: Uri.parse('ws://127.0.0.1:7337/ws'), + autoConnect: true, + createdAt: createdAt, + updatedAt: createdAt, + ); + final updated = RemoteDaemonProfile( + id: original.id, + label: 'Updated', + websocketUri: original.websocketUri, + autoConnect: false, + createdAt: original.createdAt, + updatedAt: createdAt.add(const Duration(minutes: 1)), + ); + + await store.upsertProfile(original); + await store.upsertProfile(updated); + await credentials.writeBearerToken(original.id, 'secret'); + + expect((await store.listProfiles()).single.label, 'Updated'); + + await store.deleteProfile(original.id); + await credentials.deleteBearerToken(original.id); + + expect(await store.listProfiles(), isEmpty); + expect(await credentials.readBearerToken(original.id), isNull); + }); + + test('rejects incompatible and malformed settings documents', () async { + final preferences = await SharedPreferences.getInstance(); + final store = SharedPreferencesAppStore(preferences); + final invalidDocuments = [ + [], + { + 'version': 2, + 'settings': {}, + 'profiles': [], + }, + { + 'version': 1, + 'settings': 'invalid', + 'profiles': [], + }, + { + 'version': 1, + 'settings': { + 'embeddedDaemonEnabled': true, + 'lastActiveHostId': null, + }, + 'profiles': ['invalid'], + }, + { + 'version': 1, + 'settings': { + 'embeddedDaemonEnabled': 'invalid', + 'lastActiveHostId': null, + }, + 'profiles': [], + }, + { + 'version': 1, + 'settings': { + 'embeddedDaemonEnabled': true, + 'lastActiveHostId': null, + }, + 'profiles': [ + { + 'id': 7, + 'label': 'Invalid', + 'websocketUri': 'wss://coder.example.com/ws', + 'autoConnect': true, + 'serverId': null, + 'createdAt': '2026-08-03T00:00:00.000Z', + 'updatedAt': '2026-08-03T00:00:00.000Z', + 'lastConnectedAt': null, + }, + ], + }, + ]; + + for (final document in invalidDocuments) { + await preferences.setString( + SharedPreferencesAppStore.documentKey, + jsonEncode(document), + ); + await expectLater(store.loadSettings(), throwsFormatException); + } + }); +} diff --git a/apps/coder_app/test/bootstrap_ports_test.dart b/apps/coder_app/test/bootstrap_ports_test.dart deleted file mode 100644 index e32be41..0000000 --- a/apps/coder_app/test/bootstrap_ports_test.dart +++ /dev/null @@ -1,158 +0,0 @@ -import 'package:coder_app/src/bootstrap.dart'; -import 'package:coder_app/src/desktop_bootstrap.dart'; -import 'package:coder_app/src/ports.dart'; -import 'package:coder_app/src/remote_bootstrap.dart'; -import 'package:coder_client/coder_client.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'support/fake_coder_api.dart'; - -void main() { - setUp(() => FlutterSecureStorage.setMockInitialValues({})); - - test( - 'remote bootstrap is remote-only and persists explicit connections', - () async { - final api = FakeCoderApi(); - final connector = _Connector(api); - final bootstrap = RemoteBootstrap( - ids: const _Ids(), - connector: connector, - ); - addTearDown(bootstrap.close); - addTearDown(api.close); - - expect(bootstrap.canRegisterLocalWorkspace, isFalse); - expect(await bootstrap.autoConnect(), isNull); - final endpoint = HostEndpoint.parse( - 'ws://daemon.local:7337/ws', - token: 'remote-token', - ); - final connected = await bootstrap.connectRemote(endpoint); - expect(connected.client, same(api)); - expect(connected.endpoint, endpoint); - expect(connector.clientKind, 'mobile'); - expect(connector.clientId, 'fixed-id'); - - final restored = await RemoteBootstrap( - ids: const _Ids(), - connector: connector, - ).autoConnect(); - expect(restored!.endpoint.websocketUri, endpoint.websocketUri); - expect(restored.endpoint.token, endpoint.token); - }, - ); - - test('desktop reuses a saved daemon without launching an isolate', () async { - FlutterSecureStorage.setMockInitialValues({ - 'tinyrack_coder.host_address': 'ws://127.0.0.1:7444/ws', - 'tinyrack_coder.host_token': 'saved-token', - }); - final api = FakeCoderApi(); - final connector = _Connector(api); - final launcher = _Launcher(); - final bootstrap = DesktopBootstrap( - ids: const _Ids(), - connector: connector, - launcher: launcher, - ); - addTearDown(bootstrap.close); - addTearDown(api.close); - - final connection = await bootstrap.autoConnect(); - expect(connection!.endpoint.websocketUri.port, 7444); - expect(bootstrap.canRegisterLocalWorkspace, isTrue); - expect(connector.clientKind, 'desktop'); - expect(launcher.starts, 0); - }); - - test( - 'desktop falls back to embedded and stops it for a remote host', - () async { - FlutterSecureStorage.setMockInitialValues({ - 'tinyrack_coder.host_address': 'ws://stale.local/ws', - 'tinyrack_coder.host_token': 'stale-token', - }); - final api = FakeCoderApi(); - final connector = _Connector(api, failures: 1); - final launcher = _Launcher(); - final bootstrap = DesktopBootstrap( - ids: const _Ids(), - connector: connector, - launcher: launcher, - ); - addTearDown(bootstrap.close); - addTearDown(api.close); - - final embedded = await bootstrap.autoConnect(); - expect(launcher.starts, 1); - expect(embedded!.endpoint.websocketUri.port, 7338); - expect(connector.calls, 2); - final remote = HostEndpoint.parse( - 'ws://remote.local:9000/ws', - token: 'remote-token', - ); - expect((await bootstrap.connectRemote(remote)).endpoint, remote); - expect(launcher.session.stops, 1); - await bootstrap.close(); - expect(launcher.session.stops, 1); - }, - ); -} - -final class _Ids implements AppIdGenerator { - const _Ids(); - - @override - String generate() => 'fixed-id'; -} - -final class _Connector implements AppClientConnector { - _Connector(this.api, {this.failures = 0}); - - final FakeCoderApi api; - final int failures; - int calls = 0; - String? clientId; - String? clientKind; - - @override - Future connect({ - required HostEndpoint endpoint, - required String clientId, - required String clientKind, - }) async { - calls += 1; - this.clientId = clientId; - this.clientKind = clientKind; - if (calls <= failures) throw const FormatException('stale daemon'); - return api; - } -} - -final class _Launcher implements EmbeddedDaemonLauncher { - final _Session session = _Session(); - int starts = 0; - - @override - Future start() async { - starts += 1; - return session; - } -} - -final class _Session implements EmbeddedDaemonSession { - int stops = 0; - - @override - String get bearerToken => 'embedded-token'; - - @override - Uri get boundEndpoint => Uri.parse('ws://127.0.0.1:7338/ws'); - - @override - Future stop() async { - stops += 1; - } -} diff --git a/apps/coder_app/test/controller_test.dart b/apps/coder_app/test/controller_test.dart index 2492358..38523bf 100644 --- a/apps/coder_app/test/controller_test.dart +++ b/apps/coder_app/test/controller_test.dart @@ -1,7 +1,9 @@ import 'dart:async'; +import 'package:coder_app/src/app_services.dart'; import 'package:coder_app/src/controller.dart'; -import 'package:coder_app/src/ports.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; import 'package:coder_client/coder_client.dart'; import 'package:coder_protocol/coder_protocol.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -15,11 +17,21 @@ void main() { id: 'workspace', name: 'Workspace', rootPath: '/workspace', + kind: WorkspaceKind.directory, + createdAt: now, + ); + final worktree = WorktreeDto( + id: 'worktree', + workspaceId: workspace.id, + name: workspace.name, + path: workspace.rootPath, + kind: WorktreeKind.directory, + isCoderOwned: false, createdAt: now, ); final agent = AgentDto( id: 'agent', - workspaceId: workspace.id, + worktreeId: worktree.id, title: 'Agent', providerConnectionId: 'openai', model: 'gpt-5.6-sol', @@ -63,48 +75,54 @@ void main() { () async { final api = FakeCoderApi( workspaces: [workspace], + worktrees: [worktree], agents: [agent], ); final container = _container(api); addTearDown(container.dispose); - final connection = await container.read( - connectionControllerProvider.future, - ); - expect(connection, isNotNull); - expect(connection!.connected, isTrue); - expect(connection.connecting, isFalse); - expect(connection.label, '127.0.0.1:7337'); - expect(connection.serverInfo.serverId, 'server'); - expect( - container - .read(connectionControllerProvider.notifier) - .canRegisterLocalWorkspace, - isTrue, + await container.read( + hostRegistryControllerProvider.future, ); + await Future.delayed(Duration.zero); + final runtime = container + .read(hostRegistryControllerProvider) + .value! + .runtimes['server']!; + expect(runtime.connected, isTrue); + expect(runtime.serverInfo!.serverId, 'server'); api.emitState(ClientConnectionState.reconnecting); await Future.delayed(Duration.zero); expect( - container.read(connectionControllerProvider).value!.connecting, - isTrue, + container + .read(hostRegistryControllerProvider) + .value! + .runtimes['server']! + .status, + HostRuntimeStatus.reconnecting, ); api.emitState(ClientConnectionState.connected); - expect( - await container.read(workspacesControllerProvider.future), - [ - workspace, - ], + final catalog = await container.read( + workspaceCatalogControllerProvider.future, ); + expect(catalog.catalogs['server']?.workspaces, [workspace]); final registered = await container - .read(workspacesControllerProvider.notifier) - .register('/workspace/new'); - expect(registered.id, 'generated-id'); - expect(registered.name, 'new'); - expect(container.read(workspacesControllerProvider).value, hasLength(2)); + .read(workspaceCatalogControllerProvider.notifier) + .register('server', '/workspace/new'); + expect(registered.workspace.id, 'generated-id'); + expect(registered.workspace.name, 'new'); + expect( + container + .read(workspaceCatalogControllerProvider) + .value + ?.catalogs['server'] + ?.workspaces, + hasLength(2), + ); - final agentsProvider = agentsControllerProvider(workspace.id); + final agentsProvider = agentsControllerProvider('server', worktree.id); expect(await container.read(agentsProvider.future), [agent]); final created = await container .read(agentsProvider.notifier) @@ -135,12 +153,195 @@ void main() { ); expect( - await container.read(agentsControllerProvider(null).future), + await container.read(agentsControllerProvider('server', null).future), isEmpty, ); }, ); + test('feature families never mix state between connected hosts', () async { + WorkspaceDto hostWorkspace(String host) => WorkspaceDto( + id: 'workspace', + name: '$host workspace', + rootPath: '/$host', + kind: WorkspaceKind.directory, + createdAt: now, + ); + AgentDto hostAgent(String host) => agent.copyWith(title: '$host agent'); + TimelineEventDto hostEvent(String host) => TimelineEventDto( + agentId: agent.id, + sequence: 1, + turnId: 'turn', + type: 'assistant.delta', + data: {'text': host}, + createdAt: now, + ); + ProviderCatalogDto hostCatalog(String host) => ProviderCatalogDto( + definitions: [ + ProviderDefinitionDto( + id: host, + name: host, + description: host, + authMethods: const [], + ), + ], + source: ProviderCatalogSource.bundled, + updatedAt: now, + ); + final firstApi = FakeCoderApi( + serverInfo: _serverInfo('first-server'), + workspaces: [hostWorkspace('first')], + worktrees: [worktree], + agents: [hostAgent('first')], + timelines: >{ + agent.id: [hostEvent('first')], + }, + catalog: hostCatalog('first'), + ); + final secondApi = FakeCoderApi( + serverInfo: _serverInfo('second-server'), + workspaces: [hostWorkspace('second')], + worktrees: [worktree], + agents: [hostAgent('second')], + timelines: >{ + agent.id: [hostEvent('second')], + }, + catalog: hostCatalog('second'), + ); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [ + _profile('first', now), + _profile('second', now), + ], + tokens: const {'first': 'one', 'second': 'two'}, + ); + final container = ProviderContainer( + overrides: [ + appServicesProvider.overrideWithValue( + AppServices( + settings: store, + profiles: store, + credentials: store, + clients: _HostClients({ + 'first.test': firstApi, + 'second.test': secondApi, + }), + clientKind: 'test', + ), + ), + appIdGeneratorProvider.overrideWithValue(const _FixedIdGenerator()), + ], + ); + addTearDown(container.dispose); + await container.read(hostRegistryControllerProvider.future); + await Future.delayed(Duration.zero); + + final catalogs = await container.read( + workspaceCatalogControllerProvider.future, + ); + expect( + catalogs.catalogs['first']?.workspaces.single.name, + 'first workspace', + ); + expect( + catalogs.catalogs['second']?.workspaces.single.name, + 'second workspace', + ); + expect( + (await container.read( + agentsControllerProvider('first', 'worktree').future, + )).single.title, + 'first agent', + ); + expect( + (await container.read( + agentsControllerProvider('second', 'worktree').future, + )).single.title, + 'second agent', + ); + expect( + (await container.read( + conversationControllerProvider('first', agent.id).future, + )).timeline.single.data['text'], + 'first', + ); + expect( + (await container.read( + conversationControllerProvider('second', agent.id).future, + )).timeline.single.data['text'], + 'second', + ); + expect( + (await container.read( + providerSettingsControllerProvider('first').future, + ))!.catalog.definitions.single.id, + 'first', + ); + expect( + (await container.read( + providerSettingsControllerProvider('second').future, + ))!.catalog.definitions.single.id, + 'second', + ); + }); + + test( + 'session tabs close locally and persist independently per worktree', + () async { + final second = agent.copyWith(id: 'agent-2', title: 'Second'); + final api = FakeCoderApi( + workspaces: [workspace], + worktrees: [worktree], + agents: [agent, second], + ); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [_profile('server', now)], + tokens: const {'server': 'token'}, + ); + final container = ProviderContainer( + overrides: [ + appServicesProvider.overrideWithValue( + AppServices( + settings: store, + profiles: store, + credentials: store, + clients: _HostClients({ + 'server.test': api, + }), + clientKind: 'test', + ), + ), + ], + ); + addTearDown(container.dispose); + await container.read(hostRegistryControllerProvider.future); + await Future.delayed(Duration.zero); + const selection = WorkspaceSelection( + hostId: 'server', + workspaceId: 'workspace', + worktreeId: 'worktree', + ); + final provider = sessionTabsControllerProvider(selection); + expect((await container.read(provider.future)).openAgentIds, [ + 'agent', + ]); + + await container.read(provider.notifier).open(second.id); + await container.read(provider.notifier).close(agent.id); + + expect(container.read(provider).requireValue.openAgentIds, [ + second.id, + ]); + expect( + store.settings.sessionTabs[selection.storageKey]?.selectedAgentId, + second.id, + ); + expect(await api.listAgents(worktreeId: worktree.id), hasLength(2)); + }, + ); + test( 'conversation notifier deduplicates timeline and resolves approvals', () async { @@ -152,7 +353,9 @@ void main() { ); final container = _container(api); addTearDown(container.dispose); - final provider = conversationControllerProvider(agent.id); + await container.read(hostRegistryControllerProvider.future); + await Future.delayed(Duration.zero); + final provider = conversationControllerProvider('server', agent.id); final initial = await container.read(provider.future); expect(initial.timeline, [approvalEvent]); expect(initial.approvals[approval.id], approval); @@ -206,7 +409,9 @@ void main() { ); expect(container.read(provider).value!.approvals, isEmpty); expect( - await container.read(conversationControllerProvider(null).future), + await container.read( + conversationControllerProvider('server', null).future, + ), const ConversationState(), ); }, @@ -222,11 +427,12 @@ void main() { ); final container = _container(api); addTearDown(container.dispose); - final notifier = container.read( - providerSettingsControllerProvider.notifier, - ); + await container.read(hostRegistryControllerProvider.future); + await Future.delayed(Duration.zero); + final provider = providerSettingsControllerProvider('server'); + final notifier = container.read(provider.notifier); final initial = await container.read( - providerSettingsControllerProvider.future, + provider.future, ); expect(initial!.catalog.definitions.first.id, 'openai'); expect(initial.connections.single.isDefault, isTrue); @@ -234,10 +440,7 @@ void main() { await notifier.loadModels('openai'); expect( - container - .read(providerSettingsControllerProvider) - .value! - .models['openai'], + container.read(provider).value!.models['openai'], [model], ); final connected = await notifier.connectApiKey( @@ -257,11 +460,7 @@ void main() { await notifier.cancelAuth(attempt.id); await notifier.refreshCatalog(); expect( - container - .read(providerSettingsControllerProvider) - .value! - .catalog - .source, + container.read(provider).value!.catalog.source, ProviderCatalogSource.refreshed, ); final custom = await notifier.createCustom( @@ -283,7 +482,7 @@ void main() { await notifier.disconnect('deepseek'); expect( container - .read(providerSettingsControllerProvider) + .read(provider) .value! .connections .singleWhere((item) => item.id == 'deepseek') @@ -297,19 +496,19 @@ void main() { 'feature state value objects and production ports are deterministic', () { final api = FakeCoderApi(); - final endpoint = HostEndpoint.parse('ws://localhost/ws', token: 'token'); - final snapshot = ConnectionSnapshot( + final endpoint = HostEndpoint.parse('ws://localhost/ws'); + final snapshot = HostRuntimeSnapshot( + id: 'host', + label: 'Host', + kind: HostKind.remote, + status: HostRuntimeStatus.offline, api: api, endpoint: endpoint, - connectionState: ClientConnectionState.disconnected, ); expect(snapshot.connected, isFalse); - expect(snapshot.connecting, isFalse); expect( - snapshot - .copyWith(connectionState: ClientConnectionState.connecting) - .connecting, - isTrue, + snapshot.copyWith(status: HostRuntimeStatus.connecting).status, + HostRuntimeStatus.connecting, ); const conversation = ConversationState(); expect( @@ -333,6 +532,10 @@ void main() { ); expect(const SystemAppClock().nowUtc().isUtc, isTrue); expect(const UuidAppIdGenerator().generate(), isNotEmpty); + expect( + const HostConnectionFailure.network('offline').toString(), + 'offline', + ); unawaited(api.close()); }, ); @@ -340,11 +543,41 @@ void main() { ProviderContainer _container(FakeCoderApi api) => ProviderContainer( overrides: [ - bootstrapProvider.overrideWithValue(FakeAppBootstrap(api: api)), + appServicesProvider.overrideWithValue(fakeAppServices(api)), appIdGeneratorProvider.overrideWithValue(const _FixedIdGenerator()), ], ); +RemoteDaemonProfile _profile(String id, DateTime now) => RemoteDaemonProfile( + id: id, + label: id, + websocketUri: Uri.parse('ws://$id.test/ws'), + autoConnect: true, + createdAt: now, + updatedAt: now, +); + +ServerInfoDto _serverInfo(String id) => ServerInfoDto( + serverId: id, + version: 'test', + protocolVersion: coderProtocolVersion, + features: const {'providerAdmin': true}, +); + +final class _HostClients implements HostClientFactory { + const _HostClients(this.apis); + + final Map apis; + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) async => apis[endpoint.websocketUri.host]!; +} + final class _FixedIdGenerator implements AppIdGenerator { const _FixedIdGenerator(); diff --git a/apps/coder_app/test/entrypoint_test.dart b/apps/coder_app/test/entrypoint_test.dart index 92e175f..baa8b4c 100644 --- a/apps/coder_app/test/entrypoint_test.dart +++ b/apps/coder_app/test/entrypoint_test.dart @@ -28,19 +28,19 @@ void main() { expect(mobileCalls, 1); }); - testWidgets('desktop and mobile runners accept test bootstraps', ( + testWidgets('desktop and mobile runners accept test services', ( tester, ) async { final desktopApi = FakeCoderApi(); await desktop_entry.runDesktopApp( - bootstrap: FakeAppBootstrap(api: desktopApi), + services: fakeAppServices(desktopApi), ); await tester.pumpAndSettle(); - expect(find.text('등록된 workspace가 없습니다.'), findsOneWidget); + expect(find.text('Test daemon'), findsOneWidget); final mobileApi = FakeCoderApi(); await mobile_entry.runMobileApp( - bootstrap: FakeAppBootstrap(api: mobileApi), + services: fakeAppServices(mobileApi), ); await tester.pump(); expect(find.byType(CoderApp), findsOneWidget); diff --git a/apps/coder_app/test/golden/core_golden_test.dart b/apps/coder_app/test/golden/core_golden_test.dart index c7d0c4e..0b9752e 100644 --- a/apps/coder_app/test/golden/core_golden_test.dart +++ b/apps/coder_app/test/golden/core_golden_test.dart @@ -2,8 +2,11 @@ import 'dart:async'; import 'package:alchemist/alchemist.dart'; import 'package:coder_app/src/app.dart'; +import 'package:coder_app/src/app_services.dart'; import 'package:coder_app/src/controller.dart'; -import 'package:coder_app/src/settings_page.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_client/coder_client.dart'; import 'package:coder_protocol/coder_protocol.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -52,7 +55,9 @@ void main() { height: 400, child: _material( ThemeMode.light, - ProviderScope(child: ApprovalCard(approval: approval)), + ProviderScope( + child: ApprovalCard(hostId: 'server', approval: approval), + ), ), ), ), @@ -63,7 +68,9 @@ void main() { height: 400, child: _material( ThemeMode.dark, - ProviderScope(child: ApprovalCard(approval: approval)), + ProviderScope( + child: ApprovalCard(hostId: 'server', approval: approval), + ), ), ), ), @@ -122,6 +129,35 @@ void main() { ), ), ); + + unawaited( + goldenTest( + 'daemon-independent shell renders offline and global settings states', + fileName: 'daemon_hosts', + constraints: const BoxConstraints.tightFor(width: 1500, height: 900), + builder: () => GoldenTestGroup( + columns: 2, + children: [ + GoldenTestScenario( + name: 'offline dashboard desktop', + child: SizedBox( + width: 800, + height: 700, + child: _offlineDashboard(ThemeMode.light), + ), + ), + GoldenTestScenario( + name: 'remote settings mobile', + child: SizedBox( + width: 390, + height: 700, + child: _globalSettings(ThemeMode.dark), + ), + ), + ], + ), + ), + ); } Widget _settings(ThemeMode mode) { @@ -145,9 +181,15 @@ Widget _settings(ThemeMode mode) { ); return ProviderScope( overrides: [ - bootstrapProvider.overrideWithValue(FakeAppBootstrap(api: api)), + appServicesProvider.overrideWithValue(fakeAppServices(api)), ], - child: _material(mode, const SettingsPage(hostId: 'server')), + child: _material( + mode, + const UnifiedSettingsPage( + category: SettingsCategory.provider, + hostId: 'server', + ), + ), ); } @@ -160,3 +202,65 @@ Widget _material(ThemeMode mode, Widget child) => MaterialApp( themeMode: mode, home: Scaffold(body: child), ); + +Widget _offlineDashboard(ThemeMode mode) { + final api = FakeCoderApi(); + return ProviderScope( + overrides: [ + appServicesProvider.overrideWithValue( + fakeAppServices(api, connected: false), + ), + ], + child: _material( + mode, + const WorkspacePage(), + ), + ); +} + +Widget _globalSettings(ThemeMode mode) { + final now = DateTime.utc(2026, 8, 3); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [ + RemoteDaemonProfile( + id: 'production', + label: 'Production daemon', + websocketUri: Uri.parse('wss://coder.example.com/ws'), + autoConnect: false, + createdAt: now, + updatedAt: now, + ), + ], + tokens: const {'production': 'secret'}, + ); + return ProviderScope( + overrides: [ + appServicesProvider.overrideWithValue( + AppServices( + settings: store, + profiles: store, + credentials: store, + clients: const _UnusedClients(), + clientKind: 'golden', + ), + ), + ], + child: _material( + mode, + const UnifiedSettingsPage(category: SettingsCategory.daemon), + ), + ); +} + +final class _UnusedClients implements HostClientFactory { + const _UnusedClients(); + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) => throw StateError('Golden profiles do not auto-connect.'); +} diff --git a/apps/coder_app/test/golden/goldens/linux/daemon_hosts.png b/apps/coder_app/test/golden/goldens/linux/daemon_hosts.png new file mode 100644 index 0000000000000000000000000000000000000000..c64437b042ba3a3919d4bd96c5837cfa855c0f9c GIT binary patch literal 51684 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 literal 93005 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[first, second], + tokens: const {'first': 'one', 'second': 'two'}, + ); + final firstConnect = Completer(); + final secondApi = FakeCoderApi( + serverInfo: _serverInfo('server-two'), + ); + final factory = _ClientFactory(>{ + 'first.test': firstConnect.future, + 'second.test': Future.value(secondApi), + }); + final registry = HostRegistry( + store: store, + clientFactory: factory, + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + + final loaded = await registry.load(); + expect(loaded.runtimes.keys, containsAll(['first', 'second'])); + expect(loaded.runtimes['first']!.status, HostRuntimeStatus.connecting); + await _flush(); + expect( + registry.value.runtimes['second']!.status, + HostRuntimeStatus.online, + ); + expect( + registry.value.runtimes['first']!.status, + HostRuntimeStatus.connecting, + ); + + firstConnect.complete( + FakeCoderApi(serverInfo: _serverInfo('server-one')), + ); + await _flush(); + expect( + registry.value.runtimes['first']!.status, + HostRuntimeStatus.online, + ); + expect( + factory.connectedHosts, + containsAll(['first.test', 'second.test']), + ); + }, + ); + + test( + 'offline profiles save before connection and disabled hosts remain idle', + () async { + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + ); + final factory = _ClientFactory(const >{}); + final registry = HostRegistry( + store: store, + clientFactory: factory, + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + await registry.load(); + + final profile = await registry.addRemote( + label: 'Offline', + address: 'wss://offline.test/ws', + bearerToken: 'secret', + autoConnect: false, + ); + + expect(profile.id, 'generated-id'); + expect(store.profiles.single, profile); + expect(store.tokens[profile.id], 'secret'); + expect( + registry.value.runtimes[profile.id]!.status, + HostRuntimeStatus.idle, + ); + expect(factory.connectedHosts, isEmpty); + }, + ); + + test( + 'embedded daemon is optional and remote selection never stops it', + () async { + final store = MemoryAppStore(); + final embeddedApi = FakeCoderApi( + serverInfo: _serverInfo('embedded-server'), + ); + final launcher = _EmbeddedLauncher(); + final registry = HostRegistry( + store: store, + clientFactory: _ClientFactory(>{ + 'embedded.test': Future.value(embeddedApi), + }), + embeddedLauncher: launcher, + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'desktop', + ); + addTearDown(registry.close); + + await registry.load(); + await _flush(); + expect(launcher.starts, 1); + expect( + registry.value.runtimes[embeddedHostId]!.status, + HostRuntimeStatus.online, + ); + + await registry.selectHost('missing-remote'); + expect(launcher.session.stops, 0); + await registry.setEmbeddedDaemonEnabled(enabled: false); + expect(launcher.session.stops, 1); + expect(registry.value.runtimes, isNot(contains(embeddedHostId))); + }, + ); + + test('duplicate server identity becomes an explicit conflict', () async { + final profiles = [ + for (final id in ['first', 'second']) + RemoteDaemonProfile( + id: id, + label: id, + websocketUri: Uri.parse('ws://$id.test/ws'), + autoConnect: true, + createdAt: now, + updatedAt: now, + ), + ]; + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: profiles, + tokens: const {'first': 'one', 'second': 'two'}, + ); + final registry = HostRegistry( + store: store, + clientFactory: _ClientFactory(>{ + 'first.test': Future.value( + FakeCoderApi(serverInfo: _serverInfo('same')), + ), + 'second.test': Future.value( + FakeCoderApi(serverInfo: _serverInfo('same')), + ), + }), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + + await registry.load(); + await _flush(); + expect( + registry.value.runtimes.values.where( + (runtime) => runtime.status == HostRuntimeStatus.conflict, + ), + hasLength(1), + ); + }); + + test( + 'transient failures back off while permanent failures wait for retry', + () async { + final profile = RemoteDaemonProfile( + id: 'remote', + label: 'Remote', + websocketUri: Uri.parse('wss://remote.test/ws'), + autoConnect: true, + createdAt: now, + updatedAt: now, + ); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [profile], + tokens: const {'remote': 'token'}, + ); + final api = FakeCoderApi(serverInfo: _serverInfo('remote-server')); + final delay = _RecordingDelay(); + final factory = _SequenceClientFactory( Function()>[ + () => Future.error( + const HostConnectionFailure.network('temporary outage'), + ), + () => Future.error( + const HostConnectionFailure.authentication('bad token'), + ), + () => Future.value(api), + ]); + final registry = HostRegistry( + store: store, + clientFactory: factory, + ids: const _Ids(), + clock: _Clock(now), + delay: delay, + clientKind: 'test', + ); + addTearDown(registry.close); + + await registry.load(); + await _flush(); + expect(factory.attempts, 2); + expect(delay.durations, [const Duration(seconds: 1)]); + expect( + registry.value.runtimes['remote']!.status, + HostRuntimeStatus.error, + ); + + await registry.reconnect('remote'); + expect(factory.attempts, 3); + expect( + registry.value.runtimes['remote']!.status, + HostRuntimeStatus.online, + ); + }, + ); + + test( + 'editing and deleting one host cleans only its runtime and secret', + () async { + final profile = RemoteDaemonProfile( + id: 'remote', + label: 'Before', + websocketUri: Uri.parse('wss://before.test/ws'), + autoConnect: true, + createdAt: now, + updatedAt: now, + ); + final store = MemoryAppStore( + settings: const AppSettings( + embeddedDaemonEnabled: false, + lastActiveHostId: 'remote', + ), + profiles: [profile], + tokens: const {'remote': 'old-token'}, + ); + final beforeApi = FakeCoderApi(serverInfo: _serverInfo('before')); + final afterApi = FakeCoderApi(serverInfo: _serverInfo('after')); + final registry = HostRegistry( + store: store, + clientFactory: _ClientFactory(>{ + 'before.test': Future.value(beforeApi), + 'after.test': Future.value(afterApi), + }), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + await registry.load(); + await _flush(); + + await registry.updateRemote( + profileId: 'remote', + label: 'After', + address: 'wss://after.test/ws', + autoConnect: true, + replacementBearerToken: 'new-token', + ); + await _flush(); + expect(beforeApi.isClosed, isTrue); + expect(store.tokens['remote'], 'new-token'); + expect(registry.value.profiles.single.label, 'After'); + expect(registry.value.runtimes['remote']!.api, same(afterApi)); + + await registry.removeRemote('remote'); + expect(afterApi.isClosed, isTrue); + expect(store.profiles, isEmpty); + expect(store.tokens, isEmpty); + expect(store.settings.lastActiveHostId, isNull); + expect(registry.value.runtimes, isNot(contains('remote'))); + }, + ); + + test('rejects non-WebSocket endpoints before persisting a profile', () async { + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + ); + final registry = HostRegistry( + store: store, + clientFactory: _ClientFactory(const >{}), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + await registry.load(); + + expect( + () => registry.addRemote( + label: 'Invalid', + address: 'https://daemon.example/ws', + bearerToken: 'token', + autoConnect: false, + ), + throwsA( + isA().having( + (failure) => failure.kind, + 'kind', + HostConnectionFailureKind.invalidEndpoint, + ), + ), + ); + expect(store.profiles, isEmpty); + }); + + test( + 'validates credentials and rolls secrets back on profile failure', + () async { + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + ); + final registry = HostRegistry( + store: store, + profiles: const _FailingProfiles(), + credentials: store, + clientFactory: _ClientFactory(const >{}), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + await registry.load(); + expect(await registry.load(), same(registry.value)); + + await expectLater( + registry.addRemote( + label: 'Missing token', + address: 'wss://daemon.test/ws', + bearerToken: ' ', + autoConnect: false, + ), + throwsA( + isA().having( + (failure) => failure.kind, + 'kind', + HostConnectionFailureKind.authentication, + ), + ), + ); + await expectLater( + registry.addRemote( + label: 'Failure', + address: 'wss://daemon.test/ws', + bearerToken: 'secret', + autoConnect: false, + ), + throwsA(isA<_ProfileWriteFailure>()), + ); + expect(store.tokens, isEmpty); + }, + ); + + test( + 'auto-connect toggles and missing secrets affect only that host', + () async { + final profile = RemoteDaemonProfile( + id: 'remote', + label: 'Remote', + websocketUri: Uri.parse('wss://remote.test/ws'), + autoConnect: false, + createdAt: now, + updatedAt: now, + ); + final missing = RemoteDaemonProfile( + id: 'missing', + label: 'Missing', + websocketUri: Uri.parse('wss://missing.test/ws'), + autoConnect: true, + createdAt: now, + updatedAt: now, + ); + final api = FakeCoderApi(serverInfo: _serverInfo('remote-server')); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [profile, missing], + tokens: const {'remote': 'token'}, + ); + final registry = HostRegistry( + store: store, + clientFactory: _ClientFactory(>{ + 'remote.test': Future.value(api), + }), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + await registry.load(); + await _flush(); + expect( + registry.value.runtimes['missing']!.status, + HostRuntimeStatus.error, + ); + + await registry.setAutoConnect('remote', enabled: true); + await _flush(); + expect(registry.value.runtimes['remote']!.connected, isTrue); + api.emitState(ClientConnectionState.disconnected); + expect( + registry.value.runtimes['remote']!.status, + HostRuntimeStatus.offline, + ); + api.emitState(ClientConnectionState.connecting); + expect( + registry.value.runtimes['remote']!.status, + HostRuntimeStatus.reconnecting, + ); + api.emitState(ClientConnectionState.connected); + expect( + registry.value.runtimes['remote']!.status, + HostRuntimeStatus.online, + ); + + await registry.setAutoConnect('remote', enabled: false); + expect(registry.value.runtimes['remote']!.status, HostRuntimeStatus.idle); + expect(api.isClosed, isTrue); + }, + ); + + test( + 'embedded reconnect, enable, failure, and unsupported paths are typed', + () async { + final store = MemoryAppStore(); + final launcher = _EmbeddedLauncher(); + final registry = HostRegistry( + store: store, + clientFactory: _ClientFactory(>{ + 'embedded.test': Future.value( + FakeCoderApi(serverInfo: _serverInfo('embedded-server')), + ), + }), + embeddedLauncher: launcher, + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(registry.close); + await registry.load(); + await _flush(); + + await registry.reconnect(embeddedHostId); + expect(launcher.starts, 2); + await registry.setEmbeddedDaemonEnabled(enabled: false); + await registry.setEmbeddedDaemonEnabled(enabled: true); + expect(launcher.starts, 3); + + final remoteOnly = HostRegistry( + store: MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + ), + clientFactory: _ClientFactory(const >{}), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + await remoteOnly.load(); + await remoteOnly.setEmbeddedDaemonEnabled(enabled: true); + expect(remoteOnly.value.runtimes, isEmpty); + await remoteOnly.close(); + await remoteOnly.close(); + + final failingStore = MemoryAppStore(); + final failing = HostRegistry( + store: failingStore, + clientFactory: _ClientFactory(const >{}), + embeddedLauncher: const _FailingEmbeddedLauncher(), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + addTearDown(failing.close); + await failing.load(); + await _flush(); + expect( + failing.value.runtimes[embeddedHostId]!.status, + HostRuntimeStatus.error, + ); + }, + ); + + test( + 'protocol mismatch stops retry and closing drops a late connection', + () async { + final profile = RemoteDaemonProfile( + id: 'remote', + label: 'Remote', + websocketUri: Uri.parse('wss://remote.test/ws'), + autoConnect: true, + createdAt: now, + updatedAt: now, + ); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [profile], + tokens: const {'remote': 'token'}, + ); + final mismatch = HostRegistry( + store: store, + clientFactory: _SequenceClientFactory( Function()>[ + () => Future.error( + const CoderClientException( + 'wrong version', + code: 'protocol_mismatch', + ), + ), + ]), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + await mismatch.load(); + await _flush(); + expect(mismatch.value.runtimes['remote']!.error, 'wrong version'); + await mismatch.close(); + + final lateApi = FakeCoderApi(serverInfo: _serverInfo('late')); + final pending = Completer(); + final late = HostRegistry( + store: store, + clientFactory: _ClientFactory(>{ + 'remote.test': pending.future, + }), + ids: const _Ids(), + clock: _Clock(now), + delay: const _NoDelay(), + clientKind: 'test', + ); + await late.load(); + await _flush(); + final closing = late.close(); + pending.complete(lateApi); + await closing; + await _flush(); + expect(lateApi.isClosed, isTrue); + }, + ); +} + +ServerInfoDto _serverInfo(String id) => ServerInfoDto( + serverId: id, + version: 'test', + protocolVersion: coderProtocolVersion, + features: const {}, +); + +Future _flush() async { + for (var index = 0; index < 5; index += 1) { + await Future.delayed(Duration.zero); + } +} + +final class _ClientFactory implements HostClientFactory { + _ClientFactory(this.clients); + + final Map> clients; + final List connectedHosts = []; + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) { + connectedHosts.add(endpoint.websocketUri.host); + return clients[endpoint.websocketUri.host] ?? + Future.error(const HostConnectionFailure.network('offline')); + } +} + +final class _SequenceClientFactory implements HostClientFactory { + _SequenceClientFactory(this.results); + + final List Function()> results; + int attempts = 0; + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) { + final result = results[attempts](); + attempts += 1; + return result; + } +} + +final class _EmbeddedLauncher implements EmbeddedDaemonLauncher { + final _EmbeddedSession session = _EmbeddedSession(); + int starts = 0; + + @override + Future start() async { + starts += 1; + return session; + } +} + +final class _EmbeddedSession implements EmbeddedDaemonSession { + @override + HostEndpoint get endpoint => HostEndpoint.parse('ws://embedded.test/ws'); + + @override + DaemonCredentials get credentials => const DaemonCredentials( + bearerToken: 'embedded-bearer', + adminToken: 'embedded-admin', + ); + + @override + String get serverId => 'embedded-server'; + int stops = 0; + + @override + Future stop() async { + stops += 1; + } +} + +final class _Ids implements AppIdGenerator { + const _Ids(); + + @override + String generate() => 'generated-id'; +} + +final class _Clock implements AppClock { + const _Clock(this.value); + + final DateTime value; + + @override + DateTime nowUtc() => value; +} + +final class _NoDelay implements AppDelay { + const _NoDelay(); + + @override + Future wait(Duration duration) async {} +} + +final class _RecordingDelay implements AppDelay { + final List durations = []; + + @override + Future wait(Duration duration) async { + durations.add(duration); + } +} + +final class _FailingProfiles implements RemoteHostRepository { + const _FailingProfiles(); + + @override + Future deleteProfile(String profileId) async {} + + @override + Future> listProfiles() async => + const []; + + @override + Future upsertProfile(RemoteDaemonProfile profile) => + Future.error(const _ProfileWriteFailure()); +} + +final class _ProfileWriteFailure implements Exception { + const _ProfileWriteFailure(); +} + +final class _FailingEmbeddedLauncher implements EmbeddedDaemonLauncher { + const _FailingEmbeddedLauncher(); + + @override + Future start() => Future.error( + const HostConnectionFailure.network('startup failed'), + ); +} diff --git a/apps/coder_app/test/host_settings_flow_test.dart b/apps/coder_app/test/host_settings_flow_test.dart new file mode 100644 index 0000000..6d54b04 --- /dev/null +++ b/apps/coder_app/test/host_settings_flow_test.dart @@ -0,0 +1,311 @@ +import 'dart:async'; + +import 'package:coder_app/src/app.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_client/coder_client.dart'; +import 'package:coder_protocol/coder_protocol.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'support/fake_coder_api.dart'; + +void main() { + testWidgets('restores the last selected host even while it is offline', ( + tester, + ) async { + final now = DateTime.utc(2026, 8, 3); + final store = MemoryAppStore( + settings: const AppSettings( + embeddedDaemonEnabled: false, + lastActiveHostId: 'offline', + ), + profiles: [ + RemoteDaemonProfile( + id: 'offline', + label: 'Offline daemon', + websocketUri: Uri.parse('wss://offline.example/ws'), + autoConnect: false, + createdAt: now, + updatedAt: now, + ), + ], + tokens: const {'offline': 'token'}, + ); + await tester.pumpWidget( + CoderApp( + services: AppServices( + settings: store, + profiles: store, + credentials: store, + clients: const _OfflineClients(), + clientKind: 'mobile', + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('Offline daemon'), findsWidgets); + expect(find.text('자동 연결 꺼짐'), findsOneWidget); + }); + + testWidgets('remote-only app renders and opens settings without a daemon', ( + tester, + ) async { + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + ); + await tester.pumpWidget( + CoderApp( + services: AppServices( + settings: store, + profiles: store, + credentials: store, + clients: const _OfflineClients(), + clientKind: 'mobile', + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('설정된 daemon이 없습니다.'), findsOneWidget); + await tester.tap(find.byTooltip('설정')); + await tester.pumpAndSettle(); + expect(find.text('설정'), findsOneWidget); + expect(find.text('내장 daemon'), findsNothing); + expect(find.text('원격 daemon 추가'), findsOneWidget); + }); + + testWidgets('remote profiles can be saved offline, edited, and deleted', ( + tester, + ) async { + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + ); + await tester.pumpWidget( + CoderApp( + services: AppServices( + settings: store, + profiles: store, + credentials: store, + clients: const _OfflineClients(), + clientKind: 'mobile', + ), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.byTooltip('설정')); + await tester.pumpAndSettle(); + await tester.tap(find.widgetWithText(FilledButton, '원격 daemon 추가')); + await tester.pumpAndSettle(); + + await tester.enterText(_field('이름'), 'Production'); + await tester.enterText( + _field('WebSocket 주소'), + 'ws://daemon.example/ws', + ); + await tester.enterText(_field('Bearer token'), 'secret-token'); + await tester.pump(); + expect( + tester.widget(_field('WebSocket 주소')).controller?.text, + 'ws://daemon.example/ws', + ); + expect( + find.textContaining('암호화되지 않습니다'), + findsOneWidget, + reason: tester + .widgetList(find.byType(Text)) + .map((widget) => widget.data) + .whereType() + .join(' | '), + ); + await tester.tap(find.byType(Switch)); + await tester.tap(find.widgetWithText(FilledButton, '저장')); + await tester.pumpAndSettle(); + + expect(store.profiles.single.label, 'Production'); + expect(store.profiles.single.autoConnect, isFalse); + expect(store.tokens[store.profiles.single.id], 'secret-token'); + await tester.tap(find.byTooltip('연결 편집')); + await tester.pumpAndSettle(); + await tester.enterText(_field('이름'), 'Renamed'); + await tester.tap(find.widgetWithText(FilledButton, '저장')); + await tester.pumpAndSettle(); + expect(store.profiles.single.label, 'Renamed'); + + await tester.tap(find.byTooltip('연결 편집')); + await tester.pumpAndSettle(); + await tester.tap(find.widgetWithText(TextButton, '삭제')); + await tester.pumpAndSettle(); + await tester.tap(find.widgetWithText(FilledButton, '삭제')); + await tester.pumpAndSettle(); + expect(store.profiles, isEmpty); + expect(store.tokens, isEmpty); + expect(find.text('설정된 daemon이 없습니다.'), findsOneWidget); + }); + + testWidgets('desktop settings toggles the embedded daemon independently', ( + tester, + ) async { + final store = MemoryAppStore(); + final launcher = _FailingLauncher(); + await tester.pumpWidget( + CoderApp( + services: AppServices( + settings: store, + profiles: store, + credentials: store, + clients: const _OfflineClients(), + clientKind: 'desktop', + embeddedLauncher: launcher, + ), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.byTooltip('설정')); + await tester.pumpAndSettle(); + + expect(find.text('내장 daemon'), findsOneWidget); + final toggle = tester.widget(find.byType(Switch)); + expect(toggle.value, isTrue); + await tester.tap(find.byType(Switch)); + await tester.pumpAndSettle(); + await tester.tap(find.text('취소')); + await tester.pumpAndSettle(); + expect(store.settings.embeddedDaemonEnabled, isTrue); + await tester.tap(find.byType(Switch)); + await tester.pumpAndSettle(); + await tester.tap(find.text('중지')); + await tester.pumpAndSettle(); + expect(store.settings.embeddedDaemonEnabled, isFalse); + await tester.tap(find.byType(Switch)); + await tester.pumpAndSettle(); + expect(store.settings.embeddedDaemonEnabled, isTrue); + }); + + testWidgets('host home and settings render independent runtime statuses', ( + tester, + ) async { + final now = DateTime.utc(2026, 8, 3); + RemoteDaemonProfile profile(String id, {bool autoConnect = true}) => + RemoteDaemonProfile( + id: id, + label: '$id daemon', + websocketUri: Uri.parse('wss://$id.test/ws'), + autoConnect: autoConnect, + createdAt: now, + updatedAt: now, + ); + final onlineApi = FakeCoderApi( + serverInfo: _serverInfo('shared-server'), + ); + final duplicateApi = FakeCoderApi( + serverInfo: _serverInfo('shared-server'), + ); + final pending = Completer(); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [ + profile('online'), + profile('duplicate'), + profile('error'), + profile('pending'), + profile('idle', autoConnect: false), + ], + tokens: const { + 'online': 'token', + 'duplicate': 'token', + 'error': 'token', + 'pending': 'token', + 'idle': 'token', + }, + ); + await tester.pumpWidget( + CoderApp( + services: AppServices( + settings: store, + profiles: store, + credentials: store, + clients: _ProfileClients( Function()>{ + 'online.test': () async => onlineApi, + 'duplicate.test': () async => duplicateApi, + 'error.test': () => Future.error( + const HostConnectionFailure.authentication('bad token'), + ), + 'pending.test': () => pending.future, + }), + clientKind: 'test', + ), + ), + ); + await tester.pump(); + await tester.pump(); + expect(find.textContaining('온라인'), findsOneWidget); + expect(find.textContaining('중복 daemon'), findsOneWidget); + expect(find.textContaining('오류'), findsOneWidget); + expect(find.textContaining('연결 중'), findsOneWidget); + expect(find.textContaining('자동 연결 꺼짐'), findsOneWidget); + + onlineApi.emitState(ClientConnectionState.reconnecting); + await tester.pump(); + expect(find.textContaining('재연결 중'), findsOneWidget); + await tester.tap(find.byTooltip('설정')); + await tester.pumpAndSettle(); + expect(find.textContaining('재연결 중'), findsOneWidget); + expect(find.textContaining('bad token'), findsOneWidget); + await tester.drag(find.byType(ListView).last, const Offset(0, -1000)); + await tester.pumpAndSettle(); + expect(find.textContaining('자동 연결 꺼짐'), findsWidgets); + + pending.complete( + FakeCoderApi(serverInfo: _serverInfo('pending-server')), + ); + }); +} + +ServerInfoDto _serverInfo(String id) => ServerInfoDto( + serverId: id, + version: 'test', + protocolVersion: coderProtocolVersion, + features: const {}, +); + +final class _OfflineClients implements HostClientFactory { + const _OfflineClients(); + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) => Future.error( + const HostConnectionFailure.network('offline'), + ); +} + +final class _FailingLauncher implements EmbeddedDaemonLauncher { + @override + Future start() => Future.error( + const HostConnectionFailure.network('not running'), + ); +} + +final class _ProfileClients implements HostClientFactory { + const _ProfileClients(this.connections); + + final Map Function()> connections; + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) => connections[endpoint.websocketUri.host]!(); +} + +Finder _field(String label) => find.byWidgetPredicate( + (widget) => widget is TextField && widget.decoration?.labelText == label, +); diff --git a/apps/coder_app/test/platform_services_test.dart b/apps/coder_app/test/platform_services_test.dart new file mode 100644 index 0000000..3d3c5bd --- /dev/null +++ b/apps/coder_app/test/platform_services_test.dart @@ -0,0 +1,198 @@ +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/desktop_bootstrap.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; +import 'package:coder_app/src/remote_bootstrap.dart'; +import 'package:coder_client/coder_client.dart'; +import 'package:coder_daemon/coder_daemon.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'support/fake_coder_api.dart'; + +void main() { + setUp(() { + SharedPreferences.setMockInitialValues({}); + FlutterSecureStorage.setMockInitialValues({}); + }); + + test( + 'desktop and mobile service factories expose platform capabilities', + () async { + const clients = _UnusedClients(); + const launcher = _UnusedLauncher(); + final desktop = await createDesktopServices( + embeddedLauncher: launcher, + clients: clients, + ); + final mobile = await createRemoteServices(clients: clients); + + expect(desktop.supportsEmbeddedDaemon, isTrue); + expect(desktop.embeddedLauncher, same(launcher)); + expect(desktop.clients, same(clients)); + expect(desktop.clientKind, 'desktop'); + expect(mobile.supportsEmbeddedDaemon, isFalse); + expect(mobile.embeddedLauncher, isNull); + expect(mobile.clients, same(clients)); + expect(mobile.clientKind, 'mobile'); + }, + ); + + test( + 'isolate launcher returns endpoint, identity, credentials, and stop', + () async { + final handle = _DaemonHandle(); + DaemonConfig? startedConfig; + const config = DaemonConfig( + homeDirectory: '/test-home', + port: 0, + bearerToken: 'launcher-token-0123456789abcdef012345', + adminToken: 'launcher-admin-0123456789abcdef012345', + useEnvironmentCredentials: false, + ); + final launcher = IsolateEmbeddedDaemonLauncher( + config: config, + startDaemon: (value) async { + startedConfig = value; + return handle; + }, + ); + + final session = await launcher.start(); + expect(startedConfig, same(config)); + expect(session.endpoint.websocketUri.scheme, 'ws'); + expect(session.serverId, isNotEmpty); + expect( + session.credentials.bearerToken, + 'launcher-token-0123456789abcdef012345', + ); + expect( + session.credentials.adminToken, + 'launcher-admin-0123456789abcdef012345', + ); + await session.stop(); + expect(handle.stops, 1); + }, + ); + + test('WebSocket factory classifies typed connection failures', () async { + final api = FakeCoderApi(); + final endpoint = HostEndpoint.parse('wss://daemon.example/ws'); + const credentials = DaemonCredentials(bearerToken: 'token'); + final success = WebSocketHostClientFactory( + openClient: + ({ + required endpoint, + required credentials, + required clientId, + required clientKind, + }) async => api, + ); + expect( + await success.connect( + endpoint: endpoint, + credentials: credentials, + clientId: 'client', + clientKind: 'test', + ), + same(api), + ); + + Future connectWith(Object error) => + WebSocketHostClientFactory( + openClient: + ({ + required endpoint, + required credentials, + required clientId, + required clientKind, + }) => Future.error(error), + ).connect( + endpoint: endpoint, + credentials: credentials, + clientId: 'client', + clientKind: 'test', + ); + + await expectLater( + connectWith( + const CoderClientException( + 'wrong protocol', + code: 'protocol_mismatch', + ), + ), + throwsA( + isA().having( + (failure) => failure.kind, + 'kind', + HostConnectionFailureKind.protocolMismatch, + ), + ), + ); + await expectLater( + connectWith(Exception('HTTP 401')), + throwsA( + isA().having( + (failure) => failure.kind, + 'kind', + HostConnectionFailureKind.authentication, + ), + ), + ); + await expectLater( + connectWith(Exception('offline')), + throwsA( + isA().having( + (failure) => failure.kind, + 'kind', + HostConnectionFailureKind.network, + ), + ), + ); + }); +} + +final class _UnusedClients implements HostClientFactory { + const _UnusedClients(); + + @override + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) => throw StateError('No connection is expected in a factory test.'); +} + +final class _UnusedLauncher implements EmbeddedDaemonLauncher { + const _UnusedLauncher(); + + @override + Future start() => + throw StateError('No daemon is expected in a factory test.'); +} + +final class _DaemonHandle implements DaemonHandle { + int stops = 0; + + @override + String get adminToken => 'launcher-admin-0123456789abcdef012345'; + + @override + String get bearerToken => 'launcher-token-0123456789abcdef012345'; + + @override + Uri get boundEndpoint => Uri.parse('ws://127.0.0.1:4321/ws'); + + @override + Future get ready async {} + + @override + String get serverId => 'launcher-server'; + + @override + Future stop() async { + stops += 1; + } +} diff --git a/apps/coder_app/test/settings_flow_test.dart b/apps/coder_app/test/settings_flow_test.dart index 3b3160f..715cb4e 100644 --- a/apps/coder_app/test/settings_flow_test.dart +++ b/apps/coder_app/test/settings_flow_test.dart @@ -1,7 +1,8 @@ -import 'package:coder_app/src/bootstrap.dart'; +import 'package:coder_app/src/app_services.dart'; import 'package:coder_app/src/controller.dart'; import 'package:coder_app/src/external_url_opener.dart'; -import 'package:coder_app/src/ports.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; import 'package:coder_app/src/settings_page.dart'; import 'package:coder_client/coder_client.dart'; import 'package:coder_protocol/coder_protocol.dart'; @@ -319,7 +320,15 @@ void main() { await tester.pumpWidget( ProviderScope( overrides: [ - bootstrapProvider.overrideWithValue(const _FailingBootstrap()), + appServicesProvider.overrideWithValue( + const AppServices( + settings: _FailingStore(), + profiles: _FailingStore(), + credentials: _FailingStore(), + clients: _FailingStore(), + clientKind: 'test', + ), + ), ], child: const MaterialApp(home: SettingsPage(hostId: 'server')), ), @@ -380,11 +389,8 @@ Future _pumpSettings( await tester.pumpWidget( ProviderScope( overrides: [ - bootstrapProvider.overrideWithValue( - FakeAppBootstrap( - api: api, - autoConnectEnabled: autoConnectEnabled, - ), + appServicesProvider.overrideWithValue( + fakeAppServices(api, connected: autoConnectEnabled), ), appIdGeneratorProvider.overrideWithValue(const _Ids()), externalUrlOpenerProvider.overrideWithValue( @@ -414,20 +420,45 @@ final class _Ids implements AppIdGenerator { String generate() => 'new-provider'; } -final class _FailingBootstrap implements AppBootstrap { - const _FailingBootstrap(); +final class _FailingStore + implements + AppSettingsRepository, + RemoteHostRepository, + RemoteHostCredentialStore, + HostClientFactory { + const _FailingStore(); + + @override + Future loadSettings() => + Future.error(StateError('connection failed')); + + @override + Future> listProfiles() async => + const []; + + @override + Future saveSettings(AppSettings settings) async {} + + @override + Future upsertProfile(RemoteDaemonProfile profile) async {} + + @override + Future deleteProfile(String profileId) async {} @override - bool get canRegisterLocalWorkspace => false; + Future readBearerToken(String profileId) async => null; @override - Future autoConnect() => - Future.error(StateError('connection failed')); + Future writeBearerToken(String profileId, String token) async {} @override - Future close() async {} + Future deleteBearerToken(String profileId) async {} @override - Future connectRemote(HostEndpoint endpoint) => - Future.error(StateError('connection failed')); + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) => Future.error(StateError('connection failed')); } diff --git a/apps/coder_app/test/support/fake_coder_api.dart b/apps/coder_app/test/support/fake_coder_api.dart index 855df3c..70b3670 100644 --- a/apps/coder_app/test/support/fake_coder_api.dart +++ b/apps/coder_app/test/support/fake_coder_api.dart @@ -1,6 +1,8 @@ import 'dart:async'; -import 'package:coder_app/src/bootstrap.dart'; +import 'package:coder_app/src/app_services.dart'; +import 'package:coder_app/src/host_models.dart'; +import 'package:coder_app/src/host_ports.dart'; import 'package:coder_client/coder_client.dart'; import 'package:coder_protocol/coder_protocol.dart'; @@ -12,6 +14,7 @@ final class FakeCoderApi implements CoderApi { ProviderCatalogDto? catalog, List? connections, List? workspaces, + List? worktrees, List? agents, Map>? timelines, Map>? models, @@ -19,6 +22,7 @@ final class FakeCoderApi implements CoderApi { _catalog = catalog ?? _defaultCatalog, _connections = connections ?? [_openAIConnection], _workspaces = workspaces ?? [], + _worktrees = worktrees ?? [], _agents = agents ?? [], _timelines = >{ for (final entry @@ -108,6 +112,7 @@ final class FakeCoderApi implements CoderApi { ProviderCatalogDto _catalog; final List _connections; final List _workspaces; + final List _worktrees; final List _agents; final Map> _timelines; final Map> _models; @@ -117,6 +122,9 @@ final class FakeCoderApi implements CoderApi { StreamController.broadcast(sync: true); bool _closed = false; + /// Whether [close] released this fake client. + bool get isClosed => _closed; + /// Turn prompts received by the fake. final List startedPrompts = []; @@ -152,34 +160,123 @@ final class FakeCoderApi implements CoderApi { ServerInfoDto get serverInfo => _serverInfo; @override - Future> listWorkspaces() async => - List.unmodifiable(_workspaces); + Future getWorkspaceCatalog() async => + WorkspaceCatalogDto( + workspaces: List.unmodifiable(_workspaces), + worktrees: List.unmodifiable(_worktrees), + ); @override - Future registerWorkspace({ - required String id, + Future registerWorkspace({ + required String workspaceId, + required String checkoutId, required String rootPath, required String name, }) async { final workspace = WorkspaceDto( - id: id, + id: workspaceId, name: name, rootPath: rootPath, + kind: WorkspaceKind.directory, + createdAt: _now, + ); + final worktree = WorktreeDto( + id: checkoutId, + workspaceId: workspace.id, + name: name, + path: rootPath, + kind: WorktreeKind.directory, + isCoderOwned: false, createdAt: _now, ); _workspaces.add(workspace); - return workspace; + _worktrees.add(worktree); + return WorkspaceRegisterResultDto( + workspace: workspace, + worktrees: [worktree], + ); + } + + @override + Future refreshWorkspace(String workspaceId) => + getWorkspaceCatalog(); + + @override + Future unregisterWorkspace(String workspaceId) async { + _workspaces.removeWhere((item) => item.id == workspaceId); + _worktrees.removeWhere((item) => item.workspaceId == workspaceId); + } + + @override + Future> suggestDirectories( + String query, { + int limit = 30, + }) async => [ + DirectorySuggestionDto(path: query, name: query.split('/').last), + ]; + + @override + Future> listGitBranches(String workspaceId) async => + const [ + GitBranchDto(name: 'main', current: true, checkedOut: true), + ]; + + @override + Future createWorktree({ + required String id, + required String workspaceId, + required WorktreeCreateMode mode, + required String branchName, + String? baseBranch, + }) async { + final worktree = WorktreeDto( + id: id, + workspaceId: workspaceId, + name: branchName, + path: '/worktrees/$branchName', + branch: branchName, + kind: WorktreeKind.managed, + isCoderOwned: true, + createdAt: _now, + ); + _worktrees.add(worktree); + return worktree; } @override - Future> listAgents({String? workspaceId}) async => _agents - .where((agent) => workspaceId == null || agent.workspaceId == workspaceId) + Future previewWorktreeArchive( + String worktreeId, + ) async => WorktreeArchivePreviewDto( + worktreeId: worktreeId, + dirty: false, + unpushedCommitCount: 0, + runningSessionCount: 0, + removesDirectory: _worktrees + .where((item) => item.id == worktreeId) + .first + .isCoderOwned, + ); + + @override + Future archiveWorktree( + String worktreeId, { + bool force = false, + }) async { + final index = _worktrees.indexWhere((item) => item.id == worktreeId); + final archived = _worktrees[index].copyWith(archivedAt: _now); + _worktrees.removeAt(index); + return archived; + } + + @override + Future> listAgents({String? worktreeId}) async => _agents + .where((agent) => worktreeId == null || agent.worktreeId == worktreeId) .toList(growable: false); @override Future createAgent({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, @@ -188,7 +285,7 @@ final class FakeCoderApi implements CoderApi { }) async { final agent = AgentDto( id: id, - workspaceId: workspaceId, + worktreeId: worktreeId, title: title, providerConnectionId: providerConnectionId, model: model, @@ -498,48 +595,46 @@ final class FakeCoderApi implements CoderApi { } } -/// An [AppBootstrap] that always returns an in-memory API connection. -final class FakeAppBootstrap implements AppBootstrap { - /// Creates a [FakeAppBootstrap]. - FakeAppBootstrap({ - required this.api, - this.canRegisterLocalWorkspace = true, - this.autoConnectEnabled = true, - this.connectFailures = 0, - }); - - /// The API returned by [autoConnect] and [connectRemote]. - final FakeCoderApi api; - - @override - final bool canRegisterLocalWorkspace; - - /// Whether [autoConnect] returns the fake API connection. - final bool autoConnectEnabled; - - /// Number of explicit remote connections that fail before succeeding. - int connectFailures; +/// Creates app services with one deterministic remote daemon profile. +AppServices fakeAppServices( + FakeCoderApi api, { + bool connected = true, + String hostId = 'server', +}) { + final now = DateTime.utc(2026, 8, 2); + final store = MemoryAppStore( + settings: const AppSettings(embeddedDaemonEnabled: false), + profiles: [ + RemoteDaemonProfile( + id: hostId, + label: 'Test daemon', + websocketUri: Uri.parse('ws://127.0.0.1:7337/ws'), + autoConnect: connected, + createdAt: now, + updatedAt: now, + ), + ], + tokens: {hostId: 'test-token'}, + ); + return AppServices( + settings: store, + profiles: store, + credentials: store, + clients: _FakeHostClientFactory(api), + clientKind: 'test', + ); +} - @override - Future autoConnect() async => autoConnectEnabled - ? BootstrapConnection( - client: api, - endpoint: HostEndpoint.parse( - 'ws://127.0.0.1:7337/ws', - token: 'test-token', - ), - ) - : null; +final class _FakeHostClientFactory implements HostClientFactory { + const _FakeHostClientFactory(this.api); - @override - Future connectRemote(HostEndpoint endpoint) async { - if (connectFailures > 0) { - connectFailures -= 1; - throw const FormatException('Invalid test endpoint.'); - } - return BootstrapConnection(client: api, endpoint: endpoint); - } + final CoderApi api; @override - Future close() async {} + Future connect({ + required HostEndpoint endpoint, + required DaemonCredentials credentials, + required String clientId, + required String clientKind, + }) async => api; } diff --git a/docs/remote-daemon.md b/docs/remote-daemon.md new file mode 100644 index 0000000..118567c --- /dev/null +++ b/docs/remote-daemon.md @@ -0,0 +1,87 @@ +# Remote daemon and TLS proxy + +Tinyrack Coder's daemon serves plain HTTP/WebSocket only. For a remote host, +keep the daemon on loopback and let an operator-managed reverse proxy provide +DNS, certificates, TLS policy, firewalling, and public reachability. The app +uses the operating system trust store for `wss://`; it does not offer a +self-signed-certificate or certificate-validation bypass. + +Start a standalone daemon with explicit 256-bit secrets when reproducible +deployment credentials are required: + +```sh +TINYRACK_CODER_LISTEN=127.0.0.1:7337 \ +TINYRACK_CODER_TOKEN='' \ +TINYRACK_CODER_ADMIN_TOKEN='' \ +dart run packages/coder_daemon/bin/coder_daemon.dart +``` + +The bearer token grants ordinary coding access. The independent admin token +grants provider and credential mutation. Remote GUI profiles accept and store +only the bearer token, so a reverse proxy that makes its upstream peer appear as +loopback cannot accidentally elevate the client. The daemon ignores +`X-Forwarded-For` and other proxy identity headers. + +## Caddy + +Caddy forwards WebSocket upgrades and `Authorization` by default. This public +configuration explicitly removes any client-supplied admin header: + +```caddyfile +coder.example.com { + reverse_proxy 127.0.0.1:7337 { + header_up -X-Tinyrack-Coder-Admin + } +} +``` + +Register `wss://coder.example.com/ws` in the app and enter the bearer token. +Only a separately protected administrative proxy should preserve +`X-Tinyrack-Coder-Admin` when a trusted CLI workflow specifically needs it. + +## Nginx + +```nginx +map $http_upgrade $tinyrack_connection_upgrade { + default upgrade; + '' close; +} + +server { + listen 443 ssl; + server_name coder.example.com; + + # ssl_certificate and TLS policy are operator-managed. + + location /ws { + proxy_pass http://127.0.0.1:7337; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $tinyrack_connection_upgrade; + proxy_set_header Authorization $http_authorization; + proxy_set_header X-Tinyrack-Coder-Admin ""; + } +} +``` + +For a separately secured administrative endpoint, replace the empty admin +header with `$http_x_tinyrack_coder_admin`. Never synthesize an admin token from +an IP address or an identity header. + +## Local files and development resets + +Daemon bearer, admin, API-key, and OAuth credentials are stored atomically in +`credentials.json`. On POSIX systems the configuration directory is mode +`0700` and the file is mode `0600`; secrets are not stored in SQLite, protocol +payloads, or logs. + +This repository is in active development and does not migrate older internal +credential formats. An existing version-2 `credentials.json` produces an +`incompatible_credentials` error with its exact path. Stop the daemon and +explicitly remove that file to reset it. An old `auth.json` is ignored and is +not deleted automatically; remove it manually after confirming it is no longer +needed. + +Non-loopback `ws://` profiles remain available for trusted-network development, +but the UI warns that traffic and bearer credentials are unencrypted. Prefer +`wss://` for every remote connection. diff --git a/docs/testing.md b/docs/testing.md index 7e4ccef..88f9ea6 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -37,6 +37,11 @@ architecture rules, generated-code drift, and unit/widget/contract tests. `verif adds goldens and per-package coverage. `verify:debug` is deliberately separate because it compiles and launches the Linux desktop runner. +Golden tests run in their own canonical Linux process. Coverage excludes the +`golden` tag so font/rendering configuration from unrelated test isolates cannot +make pixel comparisons nondeterministic; the same UI behavior remains covered by +widget tests and `test:golden` is still a required gate. + ## Coverage `tool/verify_coverage.dart` enforces 90% line and 80% branch coverage separately diff --git a/packages/coder_client/lib/src/api.dart b/packages/coder_client/lib/src/api.dart index 7fb378c..48bcca4 100644 --- a/packages/coder_client/lib/src/api.dart +++ b/packages/coder_client/lib/src/api.dart @@ -67,23 +67,54 @@ abstract interface class CoderApi { /// The serverInfo public API member. ServerInfoDto get serverInfo; - /// The listWorkspaces public API member. - Future> listWorkspaces(); + /// Returns the daemon's repositories and active checkouts atomically. + Future getWorkspaceCatalog(); /// The registerWorkspace public API member. - Future registerWorkspace({ - required String id, + Future registerWorkspace({ + required String workspaceId, + required String checkoutId, required String rootPath, required String name, }); + /// Refreshes Git metadata and checkout registrations. + Future refreshWorkspace(String workspaceId); + + /// Removes one repository registration. + Future unregisterWorkspace(String workspaceId); + + /// Searches directories on the daemon machine. + Future> suggestDirectories( + String query, { + int limit = 30, + }); + + /// Lists local branches in one Git repository. + Future> listGitBranches(String workspaceId); + + /// Creates a managed Git worktree. + Future createWorktree({ + required String id, + required String workspaceId, + required WorktreeCreateMode mode, + required String branchName, + String? baseBranch, + }); + + /// Previews archive safety conditions. + Future previewWorktreeArchive(String worktreeId); + + /// Archives a worktree registration and optionally its managed checkout. + Future archiveWorktree(String worktreeId, {bool force = false}); + /// The listAgents public API member. - Future> listAgents({String? workspaceId}); + Future> listAgents({String? worktreeId}); /// The createAgent public API member. Future createAgent({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, diff --git a/packages/coder_client/lib/src/client.dart b/packages/coder_client/lib/src/client.dart index f12f13a..e82547a 100644 --- a/packages/coder_client/lib/src/client.dart +++ b/packages/coder_client/lib/src/client.dart @@ -29,6 +29,7 @@ class CoderClientException implements Exception { class CoderClient implements CoderApi { CoderClient._({ required this._endpoint, + required this._credentials, required this._clientId, required this._clientKind, required this._connector, @@ -39,6 +40,7 @@ class CoderClient implements CoderApi { /// The connect public API member. static Future connect({ required HostEndpoint endpoint, + required DaemonCredentials credentials, required String clientId, required String clientKind, WebSocketConnector connector = const IoWebSocketConnector(), @@ -47,6 +49,7 @@ class CoderClient implements CoderApi { }) async { final client = CoderClient._( endpoint: endpoint, + credentials: credentials, clientId: clientId, clientKind: clientKind, connector: connector, @@ -60,6 +63,7 @@ class CoderClient implements CoderApi { } final HostEndpoint _endpoint; + final DaemonCredentials _credentials; final String _clientId; final String _clientKind; final WebSocketConnector _connector; @@ -96,7 +100,10 @@ class CoderClient implements CoderApi { try { final channel = await _connector.connect( _endpoint.websocketUri, - headers: {'Authorization': 'Bearer ${_endpoint.token}'}, + headers: { + 'Authorization': 'Bearer ${_credentials.bearerToken}', + 'X-Tinyrack-Coder-Admin': ?_credentials.adminToken, + }, ); final peer = json_rpc.Peer(channel.cast()); _peer = peer; @@ -221,36 +228,120 @@ class CoderClient implements CoderApi { } @override - Future> listWorkspaces() async { + Future getWorkspaceCatalog() async { final response = await _request( - RpcMethod.workspaceList, + RpcMethod.workspaceCatalog, const {}, ); - return WorkspaceListResultDto.fromJson(response).workspaces; + return WorkspaceCatalogResultDto.fromJson(response).catalog; } @override - Future registerWorkspace({ - required String id, + Future registerWorkspace({ + required String workspaceId, + required String checkoutId, required String rootPath, required String name, }) async { final response = await _request( RpcMethod.workspaceRegister, WorkspaceRegisterParamsDto( - id: id, + workspaceId: workspaceId, + checkoutId: checkoutId, rootPath: rootPath, name: name, ).toJson(), ); - return WorkspaceResultDto.fromJson(response).workspace; + return WorkspaceRegisterResultDto.fromJson(response); + } + + @override + Future refreshWorkspace(String workspaceId) async { + final response = await _request( + RpcMethod.workspaceRefresh, + WorkspaceIdParamsDto(workspaceId: workspaceId).toJson(), + ); + return WorkspaceCatalogResultDto.fromJson(response).catalog; + } + + @override + Future unregisterWorkspace(String workspaceId) async { + await _request( + RpcMethod.workspaceUnregister, + WorkspaceIdParamsDto(workspaceId: workspaceId).toJson(), + ); + } + + @override + Future> suggestDirectories( + String query, { + int limit = 30, + }) async { + final response = await _request( + RpcMethod.directorySuggest, + DirectorySuggestParamsDto(query: query, limit: limit).toJson(), + ); + return DirectorySuggestResultDto.fromJson(response).suggestions; + } + + @override + Future> listGitBranches(String workspaceId) async { + final response = await _request( + RpcMethod.gitBranchesList, + GitBranchesListParamsDto(workspaceId: workspaceId).toJson(), + ); + return GitBranchesListResultDto.fromJson(response).branches; + } + + @override + Future createWorktree({ + required String id, + required String workspaceId, + required WorktreeCreateMode mode, + required String branchName, + String? baseBranch, + }) async { + final response = await _request( + RpcMethod.worktreeCreate, + WorktreeCreateParamsDto( + id: id, + workspaceId: workspaceId, + mode: mode, + branchName: branchName, + baseBranch: baseBranch, + ).toJson(), + ); + return WorktreeResultDto.fromJson(response).worktree; } @override - Future> listAgents({String? workspaceId}) async { + Future previewWorktreeArchive( + String worktreeId, + ) async { + final response = await _request( + RpcMethod.worktreeArchivePreview, + WorktreeIdParamsDto(worktreeId: worktreeId).toJson(), + ); + return WorktreeArchivePreviewResultDto.fromJson(response).preview; + } + + @override + Future archiveWorktree( + String worktreeId, { + bool force = false, + }) async { + final response = await _request( + RpcMethod.worktreeArchive, + WorktreeArchiveParamsDto(worktreeId: worktreeId, force: force).toJson(), + ); + return WorktreeResultDto.fromJson(response).worktree; + } + + @override + Future> listAgents({String? worktreeId}) async { final response = await _request( RpcMethod.agentList, - AgentListParamsDto(workspaceId: workspaceId).toJson(), + AgentListParamsDto(worktreeId: worktreeId).toJson(), ); return AgentListResultDto.fromJson(response).agents; } @@ -258,7 +349,7 @@ class CoderClient implements CoderApi { @override Future createAgent({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, @@ -269,7 +360,7 @@ class CoderClient implements CoderApi { RpcMethod.agentCreate, AgentCreateParamsDto( id: id, - workspaceId: workspaceId, + worktreeId: worktreeId, title: title, providerConnectionId: providerConnectionId, model: model, diff --git a/packages/coder_client/lib/src/endpoint.dart b/packages/coder_client/lib/src/endpoint.dart index 8c3daf9..08faa08 100644 --- a/packages/coder_client/lib/src/endpoint.dart +++ b/packages/coder_client/lib/src/endpoint.dart @@ -1,25 +1,57 @@ /// HostEndpoint defines a public contract. class HostEndpoint { /// Creates a [HostEndpoint]. - const HostEndpoint({required this.websocketUri, required this.token}); + const HostEndpoint({required this.websocketUri}); /// Creates a [HostEndpoint]. - factory HostEndpoint.parse(String address, {required String token}) { + factory HostEndpoint.parse(String address) { + final normalized = address.trim(); + if (normalized.isEmpty) { + throw const FormatException('Endpoint address must not be empty.'); + } + final entered = Uri.tryParse(normalized); + if (entered?.hasScheme == true && + entered!.scheme != 'ws' && + entered.scheme != 'wss') { + throw FormatException('Endpoint must use ws:// or wss://.', address); + } final hasWebSocketScheme = RegExp( '^wss?://', caseSensitive: false, - ).hasMatch(address); - var uri = Uri.parse(hasWebSocketScheme ? address : 'ws://$address'); + ).hasMatch(normalized); + var uri = Uri.parse( + hasWebSocketScheme ? normalized : 'ws://$normalized', + ); if (uri.path.isEmpty || uri.path == '/') uri = uri.replace(path: '/ws'); if (uri.scheme != 'ws' && uri.scheme != 'wss') { throw FormatException('Endpoint must use ws:// or wss://.', address); } - return HostEndpoint(websocketUri: uri, token: token); + if (uri.host.isEmpty || + uri.userInfo.isNotEmpty || + uri.fragment.isNotEmpty) { + throw FormatException( + 'Endpoint must contain a host and no credentials or fragment.', + address, + ); + } + return HostEndpoint(websocketUri: uri); } /// The websocketUri public API member. final Uri websocketUri; +} + +/// Secret credentials sent while opening a daemon transport. +final class DaemonCredentials { + /// Creates daemon connection credentials. + const DaemonCredentials({required this.bearerToken, this.adminToken}); + + /// Token authenticating ordinary daemon API access. + final String bearerToken; + + /// Optional local administration token, never used by remote profiles. + final String? adminToken; - /// The token public API member. - final String token; + @override + String toString() => 'DaemonCredentials()'; } diff --git a/packages/coder_client/test/client_test.dart b/packages/coder_client/test/client_test.dart index 360c9cf..4653d51 100644 --- a/packages/coder_client/test/client_test.dart +++ b/packages/coder_client/test/client_test.dart @@ -14,11 +14,21 @@ void main() { id: 'workspace', name: 'Workspace', rootPath: '/workspace', + kind: WorkspaceKind.directory, + createdAt: now, + ); + final worktree = WorktreeDto( + id: 'worktree', + workspaceId: workspace.id, + name: workspace.name, + path: workspace.rootPath, + kind: WorktreeKind.directory, + isCoderOwned: false, createdAt: now, ); final agent = AgentDto( id: 'agent', - workspaceId: workspace.id, + worktreeId: worktree.id, title: 'Agent', providerConnectionId: 'provider', model: 'model', @@ -91,6 +101,7 @@ void main() { peer, requests, workspace: workspace, + worktree: worktree, agent: agent, definition: definition, connection: connection, @@ -102,9 +113,10 @@ void main() { ); final states = []; final clientFuture = CoderClient.connect( - endpoint: HostEndpoint.parse( - '127.0.0.1:7337', - token: 'secret-token', + endpoint: HostEndpoint.parse('127.0.0.1:7337'), + credentials: const DaemonCredentials( + bearerToken: 'secret-token', + adminToken: 'admin-token', ), clientId: 'client', clientKind: 'test', @@ -120,24 +132,58 @@ void main() { expect(connector.lastUri, Uri.parse('ws://127.0.0.1:7337/ws')); expect( connector.lastHeaders, - const {'Authorization': 'Bearer secret-token'}, + const { + 'Authorization': 'Bearer secret-token', + 'X-Tinyrack-Coder-Admin': 'admin-token', + }, + ); + expect( + await client.getWorkspaceCatalog(), + WorkspaceCatalogDto( + workspaces: [workspace], + worktrees: [worktree], + ), ); - expect(await client.listWorkspaces(), [workspace]); expect( await client.registerWorkspace( - id: workspace.id, + workspaceId: workspace.id, + checkoutId: worktree.id, rootPath: workspace.rootPath, name: workspace.name, ), - workspace, + WorkspaceRegisterResultDto( + workspace: workspace, + worktrees: [worktree], + ), + ); + expect( + await client.refreshWorkspace(workspace.id), + isA(), + ); + await client.unregisterWorkspace(workspace.id); + expect(await client.suggestDirectories('work'), isNotEmpty); + expect(await client.listGitBranches(workspace.id), isNotEmpty); + expect( + await client.createWorktree( + id: worktree.id, + workspaceId: workspace.id, + mode: WorktreeCreateMode.newBranch, + branchName: 'topic', + ), + worktree, ); - expect(await client.listAgents(workspaceId: workspace.id), [ + expect( + await client.previewWorktreeArchive(worktree.id), + isA(), + ); + expect(await client.archiveWorktree(worktree.id), worktree); + expect(await client.listAgents(worktreeId: worktree.id), [ agent, ]); expect( await client.createAgent( id: agent.id, - workspaceId: workspace.id, + worktreeId: worktree.id, title: agent.title, providerConnectionId: agent.providerConnectionId, model: agent.model, @@ -246,8 +292,15 @@ void main() { expect( connector.requests.map((request) => request.method), containsAll([ - RpcMethod.workspaceList, + RpcMethod.workspaceCatalog, RpcMethod.workspaceRegister, + RpcMethod.workspaceRefresh, + RpcMethod.workspaceUnregister, + RpcMethod.directorySuggest, + RpcMethod.gitBranchesList, + RpcMethod.worktreeCreate, + RpcMethod.worktreeArchivePreview, + RpcMethod.worktreeArchive, RpcMethod.agentList, RpcMethod.agentCreate, RpcMethod.agentConfigurationUpdate, @@ -280,7 +333,7 @@ void main() { final connector = _TestConnector( onConfigure: (peer, requests) { _registerHello(peer, requests); - peer.registerMethod(RpcMethod.workspaceList, (_) { + peer.registerMethod(RpcMethod.workspaceCatalog, (_) { throw json_rpc.RpcException( -32000, 'Workspace unavailable', @@ -293,7 +346,8 @@ void main() { }, ); final client = await CoderClient.connect( - endpoint: HostEndpoint.parse('ws://localhost/ws', token: 'token'), + endpoint: HostEndpoint.parse('ws://localhost/ws'), + credentials: const DaemonCredentials(bearerToken: 'token'), clientId: 'client', clientKind: 'test', connector: connector, @@ -301,7 +355,7 @@ void main() { addTearDown(client.close); await expectLater( - client.listWorkspaces(), + client.getWorkspaceCatalog(), throwsA( isA() .having((error) => error.code, 'code', 'workspace_unavailable') @@ -320,20 +374,21 @@ void main() { onConfigure: (peer, requests) { _registerHello(peer, requests); peer.registerMethod( - RpcMethod.workspaceList, + RpcMethod.workspaceCatalog, (_) => Completer>().future, ); }, ); final client = await CoderClient.connect( - endpoint: HostEndpoint.parse('ws://localhost/ws', token: 'token'), + endpoint: HostEndpoint.parse('ws://localhost/ws'), + credentials: const DaemonCredentials(bearerToken: 'token'), clientId: 'client', clientKind: 'test', connector: connector, requestTimeout: const Duration(milliseconds: 10), ); await expectLater( - client.listWorkspaces(), + client.getWorkspaceCatalog(), throwsA(isA()), ); await client.close(); @@ -363,7 +418,8 @@ void main() { }, ); final client = await CoderClient.connect( - endpoint: HostEndpoint.parse('ws://localhost/ws', token: 'token'), + endpoint: HostEndpoint.parse('ws://localhost/ws'), + credentials: const DaemonCredentials(bearerToken: 'token'), clientId: 'client', clientKind: 'test', connector: connector, @@ -474,6 +530,7 @@ void _registerFixtureMethods( json_rpc.Peer peer, List<_Request> requests, { required WorkspaceDto workspace, + required WorktreeDto worktree, required AgentDto agent, required ProviderDefinitionDto definition, required ProviderConnectionDto connection, @@ -489,13 +546,46 @@ void _registerFixtureMethods( status: ProviderAuthAttemptStatus.awaitingUser, userCode: 'CODE-1234', ); + final workspaceCatalog = WorkspaceCatalogDto( + workspaces: [workspace], + worktrees: [worktree], + ); + const archivePreview = WorktreeArchivePreviewDto( + worktreeId: 'worktree', + dirty: false, + unpushedCommitCount: 0, + runningSessionCount: 0, + removesDirectory: false, + ); final responses = >{ - RpcMethod.workspaceList: WorkspaceListResultDto( - workspaces: [workspace], + RpcMethod.workspaceCatalog: WorkspaceCatalogResultDto( + catalog: workspaceCatalog, ).toJson(), - RpcMethod.workspaceRegister: WorkspaceResultDto( + RpcMethod.workspaceRegister: WorkspaceRegisterResultDto( workspace: workspace, + worktrees: [worktree], + ).toJson(), + RpcMethod.workspaceRefresh: WorkspaceCatalogResultDto( + catalog: workspaceCatalog, + ).toJson(), + RpcMethod.workspaceUnregister: const WorkspaceUnregisterResultDto( + unregistered: true, + ).toJson(), + RpcMethod.directorySuggest: const DirectorySuggestResultDto( + suggestions: [ + DirectorySuggestionDto(path: '/workspace', name: 'Workspace'), + ], + ).toJson(), + RpcMethod.gitBranchesList: const GitBranchesListResultDto( + branches: [ + GitBranchDto(name: 'main', current: true, checkedOut: true), + ], + ).toJson(), + RpcMethod.worktreeCreate: WorktreeResultDto(worktree: worktree).toJson(), + RpcMethod.worktreeArchivePreview: const WorktreeArchivePreviewResultDto( + preview: archivePreview, ).toJson(), + RpcMethod.worktreeArchive: WorktreeResultDto(worktree: worktree).toJson(), RpcMethod.agentList: AgentListResultDto(agents: [agent]).toJson(), RpcMethod.agentCreate: AgentResultDto(agent: agent).toJson(), RpcMethod.agentConfigurationUpdate: AgentResultDto(agent: agent).toJson(), diff --git a/packages/coder_client/test/endpoint_test.dart b/packages/coder_client/test/endpoint_test.dart index 7199132..6be62b0 100644 --- a/packages/coder_client/test/endpoint_test.dart +++ b/packages/coder_client/test/endpoint_test.dart @@ -3,7 +3,19 @@ import 'package:test/test.dart'; void main() { test('endpoint adds ws scheme and protocol path', () { - final endpoint = HostEndpoint.parse('127.0.0.1:7337', token: 'secret'); + final endpoint = HostEndpoint.parse('127.0.0.1:7337'); expect(endpoint.websocketUri.toString(), 'ws://127.0.0.1:7337/ws'); }); + + test('credentials keep transport location and secrets separate', () { + const credentials = DaemonCredentials( + bearerToken: 'bearer', + adminToken: 'admin', + ); + + expect(credentials.bearerToken, 'bearer'); + expect(credentials.adminToken, 'admin'); + expect(credentials.toString(), isNot(contains('bearer'))); + expect(credentials.toString(), isNot(contains('admin'))); + }); } diff --git a/packages/coder_daemon/bin/coder_daemon.dart b/packages/coder_daemon/bin/coder_daemon.dart index 10687ce..7b2241d 100644 --- a/packages/coder_daemon/bin/coder_daemon.dart +++ b/packages/coder_daemon/bin/coder_daemon.dart @@ -61,7 +61,8 @@ Future _runProvider( final credentials = CredentialStore(config.configDirectory); await credentials.load(); final token = config.bearerToken ?? credentials.bearerToken; - if (token == null) { + final adminToken = config.adminToken ?? credentials.adminToken; + if (token == null || adminToken == null) { throw StateError( 'No daemon connection token found. Start coder_daemon first.', ); @@ -74,7 +75,10 @@ Future _runProvider( port: config.port, path: '/ws', ), - token: token, + ), + credentials: DaemonCredentials( + bearerToken: token, + adminToken: adminToken, ), clientId: 'coder-daemon-cli', clientKind: 'standalone-cli', diff --git a/packages/coder_daemon/lib/coder_daemon.dart b/packages/coder_daemon/lib/coder_daemon.dart index 2df0094..33341a6 100644 --- a/packages/coder_daemon/lib/coder_daemon.dart +++ b/packages/coder_daemon/lib/coder_daemon.dart @@ -1,6 +1,8 @@ export 'src/application.dart'; export 'src/config.dart'; export 'src/embedded.dart'; +export 'src/git_workspace.dart'; export 'src/ports.dart'; export 'src/provider_adapters.dart'; export 'src/repositories.dart'; +export 'src/workspace_service.dart'; diff --git a/packages/coder_daemon/lib/src/agent_service.dart b/packages/coder_daemon/lib/src/agent_service.dart index efc239b..3ab889f 100644 --- a/packages/coder_daemon/lib/src/agent_service.dart +++ b/packages/coder_daemon/lib/src/agent_service.dart @@ -17,7 +17,7 @@ class AgentService { /// Creates a [AgentService]. AgentService({ required this._agents, - required this._workspaces, + required this._worktrees, required this._timeline, required this._providers, required this._events, @@ -28,7 +28,7 @@ class AgentService { }); final AgentRepository _agents; - final WorkspaceRepository _workspaces; + final WorktreeRepository _worktrees; final TimelineRepository _timeline; final ProviderService _providers; final DaemonEventSink _events; @@ -56,9 +56,9 @@ class AgentService { if (_activeTurns.containsKey(agentId)) { throw StateError('Agent already has a running turn.'); } - final workspace = await _workspaces.getById(agent.workspaceId); - if (workspace == null) { - throw StateError('Workspace not found: ${agent.workspaceId}'); + final worktree = await _worktrees.getById(agent.worktreeId); + if (worktree == null || worktree.archivedAt != null) { + throw StateError('Worktree not found: ${agent.worktreeId}'); } final provider = await _providers.resolve( agent.providerConnectionId, @@ -127,7 +127,7 @@ class AgentService { AgentRunRequest( agentId: agentId, turnId: turnId, - workspaceRoot: workspace.rootPath, + workspaceRoot: worktree.path, prompt: prompt, model: agent.model, reasoningEffort: agent.reasoningEffort, diff --git a/packages/coder_daemon/lib/src/application.dart b/packages/coder_daemon/lib/src/application.dart index 751ee3d..d2f7a02 100644 --- a/packages/coder_daemon/lib/src/application.dart +++ b/packages/coder_daemon/lib/src/application.dart @@ -7,6 +7,7 @@ import 'package:coder_daemon/src/agent_service.dart'; import 'package:coder_daemon/src/config.dart'; import 'package:coder_daemon/src/credential_store.dart'; import 'package:coder_daemon/src/database.dart'; +import 'package:coder_daemon/src/git_workspace.dart'; import 'package:coder_daemon/src/openai_oauth_gateway.dart'; import 'package:coder_daemon/src/ports.dart'; import 'package:coder_daemon/src/provider_adapters.dart'; @@ -14,6 +15,7 @@ import 'package:coder_daemon/src/provider_auth.dart'; import 'package:coder_daemon/src/provider_catalog.dart'; import 'package:coder_daemon/src/provider_service.dart'; import 'package:coder_daemon/src/server.dart'; +import 'package:coder_daemon/src/workspace_service.dart'; import 'package:coder_protocol/coder_protocol.dart'; import 'package:crypto/crypto.dart'; import 'package:path/path.dart' as p; @@ -33,6 +35,9 @@ abstract interface class DaemonHandle { /// The bearerToken public API member. String get bearerToken; + /// Secret granting provider administration to trusted local clients. + String get adminToken; + /// The stop public API member. Future stop(); } @@ -49,8 +54,8 @@ abstract final class DaemonApplication { ModelProviderFactory providerFactory = const OpenAICompatibleProviderFactory(), ProviderOAuthGateway? oauthGateway, - WorkspaceCanonicalizer workspaceCanonicalizer = - const IoWorkspaceCanonicalizer(), + WorkspacePathGateway workspacePaths = const IoWorkspacePathGateway(), + GitWorkspaceGateway? git, }) async { final home = Directory(config.homeDirectory); await home.create(recursive: true); @@ -80,17 +85,28 @@ abstract final class DaemonApplication { config.bearerToken ?? credentials.bearerToken ?? generateBearerToken(); + final adminToken = + config.adminToken ?? credentials.adminToken ?? generateBearerToken(); if (utf8.encode(token).length < 32) { throw ArgumentError( 'Bearer token must contain at least 256 bits (32 bytes).', ); } + if (utf8.encode(adminToken).length < 32) { + throw ArgumentError( + 'Admin token must contain at least 256 bits (32 bytes).', + ); + } await database.settingsDao.setValue( 'auth.tokenHash', sha256.convert(utf8.encode(token)).toString(), ); - if (credentials.bearerToken != token) { - await credentials.setBearerToken(token); + if (credentials.bearerToken != token || + credentials.adminToken != adminToken) { + await credentials.setDaemonTokens( + bearerToken: token, + adminToken: adminToken, + ); } final events = StreamController.broadcast(sync: true); final effectiveOAuthGateway = @@ -122,7 +138,7 @@ abstract final class DaemonApplication { ); final service = AgentService( agents: database.agentDao, - workspaces: database.workspaceDao, + worktrees: database.worktreeDao, timeline: database.timelineDao, providers: providers, events: events.add, @@ -137,6 +153,15 @@ abstract final class DaemonApplication { RunCommandTool(), ], ); + final workspaceService = WorkspaceService( + database.workspaceDao, + database.worktreeDao, + database.agentDao, + workspacePaths, + git ?? const ProcessGitWorkspaceGateway(IoCommandRunner()), + clock, + p.join(home.path, 'worktrees'), + ); final info = ServerInfoDto( serverId: serverId, version: config.version, @@ -149,16 +174,16 @@ abstract final class DaemonApplication { }, ); final rpc = DaemonRpcServer( - workspaces: database.workspaceDao, + workspaces: workspaceService, agentRepository: database.agentDao, timeline: database.timelineDao, agents: service, providers: providers, providerAuth: providerAuth, clock: clock, - workspaceCanonicalizer: workspaceCanonicalizer, serverInfo: info, token: token, + adminToken: adminToken, events: events.stream, ); final http = await shelf_io.serve( @@ -178,6 +203,7 @@ abstract final class DaemonApplication { ), serverIdValue: serverId, token: token, + adminTokenValue: adminToken, http: http, rpc: rpc, database: database, @@ -198,16 +224,19 @@ class _LocalDaemonHandle implements DaemonHandle { required this._endpoint, required String serverIdValue, required this._token, + required String adminTokenValue, required this._http, required this._rpc, required this._database, required this._events, required this._lock, - }) : _serverId = serverIdValue; + }) : _serverId = serverIdValue, + _adminToken = adminTokenValue; final Uri _endpoint; final String _serverId; final String _token; + final String _adminToken; final HttpServer _http; final DaemonRpcServer _rpc; final CoderDatabase _database; @@ -222,6 +251,8 @@ class _LocalDaemonHandle implements DaemonHandle { @override String get bearerToken => _token; @override + String get adminToken => _adminToken; + @override Future get ready => Future.value(); @override diff --git a/packages/coder_daemon/lib/src/config.dart b/packages/coder_daemon/lib/src/config.dart index 5d9595b..2badaba 100644 --- a/packages/coder_daemon/lib/src/config.dart +++ b/packages/coder_daemon/lib/src/config.dart @@ -47,6 +47,7 @@ class DaemonConfig { this.port = 7337, this.apiKey, this.bearerToken, + this.adminToken, this.version = '0.1.0', this.useEnvironmentCredentials = true, }) : configDirectory = configDirectory ?? homeDirectory; @@ -60,6 +61,7 @@ class DaemonConfig { port: value['port']! as int, apiKey: value['apiKey'] as String?, bearerToken: value['bearerToken'] as String?, + adminToken: value['adminToken'] as String?, version: value['version']! as String, useEnvironmentCredentials: value['useEnvironmentCredentials']! as bool, ); @@ -86,6 +88,7 @@ class DaemonConfig { port: int.parse(listen.substring(separator + 1)), apiKey: apiKey, bearerToken: values['TINYRACK_CODER_TOKEN'], + adminToken: values['TINYRACK_CODER_ADMIN_TOKEN'], ); } @@ -107,6 +110,9 @@ class DaemonConfig { /// The bearerToken public API member. final String? bearerToken; + /// Optional local-administration secret supplied by the composition root. + final String? adminToken; + /// The version public API member. final String version; @@ -121,6 +127,7 @@ class DaemonConfig { int? port, String? apiKey, String? bearerToken, + String? adminToken, bool? useEnvironmentCredentials, }) => DaemonConfig( homeDirectory: homeDirectory ?? this.homeDirectory, @@ -129,6 +136,7 @@ class DaemonConfig { port: port ?? this.port, apiKey: apiKey ?? this.apiKey, bearerToken: bearerToken ?? this.bearerToken, + adminToken: adminToken ?? this.adminToken, version: version, useEnvironmentCredentials: useEnvironmentCredentials ?? this.useEnvironmentCredentials, @@ -142,6 +150,7 @@ class DaemonConfig { 'port': port, 'apiKey': apiKey, 'bearerToken': bearerToken, + 'adminToken': adminToken, 'version': version, 'useEnvironmentCredentials': useEnvironmentCredentials, }; diff --git a/packages/coder_daemon/lib/src/credential_store.dart b/packages/coder_daemon/lib/src/credential_store.dart index 1839c35..a98daaf 100644 --- a/packages/coder_daemon/lib/src/credential_store.dart +++ b/packages/coder_daemon/lib/src/credential_store.dart @@ -14,11 +14,11 @@ class CredentialStore implements CredentialRepository { final Map _providerCredentials = {}; String? _bearerToken; + String? _adminToken; bool _loaded = false; File get _credentialsFile => File(p.join(configDirectory, 'credentials.json')); - File get _authFile => File(p.join(configDirectory, 'auth.json')); @override Future load() async { @@ -27,7 +27,7 @@ class CredentialStore implements CredentialRepository { await _ensureDirectory(); if (_credentialsFile.existsSync()) { final decoded = jsonDecode(await _credentialsFile.readAsString()); - if (decoded is! Map || decoded['version'] != 2) { + if (decoded is! Map || decoded['version'] != 3) { throw FormatException( 'incompatible_credentials: explicitly remove ' '${_credentialsFile.path} to reset development credentials.', @@ -41,11 +41,18 @@ class CredentialStore implements CredentialRepository { } } } - } - if (_authFile.existsSync()) { - final decoded = jsonDecode(await _authFile.readAsString()); - if (decoded is Map && decoded['bearerToken'] is String) { - _bearerToken = decoded['bearerToken'] as String; + final daemon = decoded['daemon']; + if (daemon != null) { + if (daemon is! Map || + daemon['bearerToken'] is! String || + daemon['adminToken'] is! String) { + throw const FormatException('Invalid daemon credential data.'); + } + _bearerToken = daemon['bearerToken'] as String; + _adminToken = daemon['adminToken'] as String; + } + if ((_bearerToken == null) != (_adminToken == null)) { + throw const FormatException('Invalid daemon credential data.'); } } } @@ -53,18 +60,22 @@ class CredentialStore implements CredentialRepository { @override String? get bearerToken => _bearerToken; + @override + String? get adminToken => _adminToken; + @override ProviderCredential? credential(String connectionId) => _providerCredentials[connectionId]; @override - Future setBearerToken(String token) async { + Future setDaemonTokens({ + required String bearerToken, + required String adminToken, + }) async { await load(); - _bearerToken = token; - await _writeJson(_authFile, { - 'version': 1, - 'bearerToken': token, - }); + _bearerToken = bearerToken; + _adminToken = adminToken; + await _writeCredentials(); } @override @@ -86,7 +97,12 @@ class CredentialStore implements CredentialRepository { Future _writeCredentials() => _writeJson(_credentialsFile, { - 'version': 2, + 'version': 3, + if (_bearerToken case final bearerToken?) + 'daemon': { + 'bearerToken': bearerToken, + 'adminToken': _adminToken, + }, 'providerCredentials': { for (final entry in _providerCredentials.entries) entry.key: _credentialToJson(entry.value), diff --git a/packages/coder_daemon/lib/src/daos.dart b/packages/coder_daemon/lib/src/daos.dart index ebcd2c3..84e7ae3 100644 --- a/packages/coder_daemon/lib/src/daos.dart +++ b/packages/coder_daemon/lib/src/daos.dart @@ -27,7 +27,7 @@ class SettingsDao extends DatabaseAccessor ).insertOnConflictUpdate(SettingsCompanion.insert(key: key, value: value)); } -@DriftAccessor(tables: [Workspaces]) +@DriftAccessor(tables: [Workspaces, Worktrees]) /// WorkspaceDao defines a public contract. class WorkspaceDao extends DatabaseAccessor with _$WorkspaceDaoMixin @@ -53,6 +53,14 @@ class WorkspaceDao extends DatabaseAccessor return row == null ? null : _toDto(row); } + @override + Future getByRootPath(String rootPath) async { + final row = await (select( + workspaces, + )..where((table) => table.rootPath.equals(rootPath))).getSingleOrNull(); + return row == null ? null : _toDto(row); + } + @override Future register(WorkspaceDto workspace) async { final existing = await getById(workspace.id); @@ -62,16 +70,103 @@ class WorkspaceDao extends DatabaseAccessor id: workspace.id, name: workspace.name, rootPath: workspace.rootPath, + kind: workspace.kind.name, createdAt: workspace.createdAt, ), ); return workspace; } + @override + Future unregister(String id) => transaction(() async { + await (delete( + worktrees, + )..where((row) => row.workspaceId.equals(id))).go(); + await (delete(workspaces)..where((row) => row.id.equals(id))).go(); + }); + WorkspaceDto _toDto(Workspace row) => WorkspaceDto( id: row.id, name: row.name, rootPath: row.rootPath, + kind: WorkspaceKind.values.byName(row.kind), + createdAt: row.createdAt, + ); +} + +@DriftAccessor(tables: [Worktrees]) +/// Drift adapter for worktree persistence. +class WorktreeDao extends DatabaseAccessor + with _$WorktreeDaoMixin + implements WorktreeRepository { + /// Creates a [WorktreeDao]. + WorktreeDao(super.attachedDatabase); + + @override + Future> list({String? workspaceId}) async { + final query = select(worktrees)..where((row) => row.archivedAt.isNull()); + if (workspaceId != null) { + query.where((row) => row.workspaceId.equals(workspaceId)); + } + query.orderBy(>[ + (row) => OrderingTerm.asc(row.name), + ]); + return (await query.get()).map(_toDto).toList(growable: false); + } + + @override + Future getById(String id) async { + final row = await (select( + worktrees, + )..where((table) => table.id.equals(id))).getSingleOrNull(); + return row == null ? null : _toDto(row); + } + + @override + Future getByPath(String path) async { + final row = + await (select(worktrees)..where( + (table) => table.path.equals(path) & table.archivedAt.isNull(), + )) + .getSingleOrNull(); + return row == null ? null : _toDto(row); + } + + @override + Future upsert(WorktreeDto worktree) async { + await into(worktrees).insertOnConflictUpdate( + WorktreesCompanion.insert( + id: worktree.id, + workspaceId: worktree.workspaceId, + name: worktree.name, + path: worktree.path, + branch: Value(worktree.branch), + head: Value(worktree.head), + kind: worktree.kind.name, + isCoderOwned: worktree.isCoderOwned, + archivedAt: Value(worktree.archivedAt), + createdAt: worktree.createdAt, + ), + ); + return (await getById(worktree.id))!; + } + + @override + Future archive(String id, DateTime archivedAt) => + (update(worktrees)..where((row) => row.id.equals(id))).write( + WorktreesCompanion(archivedAt: Value(archivedAt)), + ); + + WorktreeDto _toDto(Worktree row) => WorktreeDto( + id: row.id, + workspaceId: row.workspaceId, + name: row.name, + path: row.path, + branch: row.branch, + head: row.head, + kind: WorktreeKind.values.byName(row.kind), + isCoderOwned: row.isCoderOwned, + archivedAt: row.archivedAt, createdAt: row.createdAt, ); } @@ -85,10 +180,10 @@ class AgentDao extends DatabaseAccessor AgentDao(super.attachedDatabase); @override - Future> list({String? workspaceId}) async { + Future> list({String? worktreeId}) async { final query = select(agents); - if (workspaceId != null) { - query.where((row) => row.workspaceId.equals(workspaceId)); + if (worktreeId != null) { + query.where((row) => row.worktreeId.equals(worktreeId)); } query.orderBy(>[ (row) => OrderingTerm.desc(row.updatedAt), @@ -104,6 +199,22 @@ class AgentDao extends DatabaseAccessor return row == null ? null : _toDto(row); } + @override + Future countActive(String worktreeId) async { + final count = agents.id.count(); + final query = selectOnly(agents) + ..addColumns(>[count]) + ..where( + agents.worktreeId.equals(worktreeId) & + agents.status.isIn([ + AgentStatus.running.name, + AgentStatus.waitingForApproval.name, + AgentStatus.initializing.name, + ]), + ); + return (await query.getSingle()).read(count) ?? 0; + } + @override Future create(AgentDto agent) async { final existing = await getById(agent.id); @@ -111,7 +222,7 @@ class AgentDao extends DatabaseAccessor await into(agents).insert( AgentsCompanion.insert( id: agent.id, - workspaceId: agent.workspaceId, + worktreeId: agent.worktreeId, title: agent.title, providerConnectionId: agent.providerConnectionId, model: agent.model, @@ -213,7 +324,7 @@ class AgentDao extends DatabaseAccessor AgentDto _toDto(Agent row) => AgentDto( id: row.id, - workspaceId: row.workspaceId, + worktreeId: row.worktreeId, title: row.title, providerConnectionId: row.providerConnectionId, model: row.model, diff --git a/packages/coder_daemon/lib/src/daos.g.dart b/packages/coder_daemon/lib/src/daos.g.dart index f780a42..1486b49 100644 --- a/packages/coder_daemon/lib/src/daos.g.dart +++ b/packages/coder_daemon/lib/src/daos.g.dart @@ -17,6 +17,7 @@ class SettingsDaoManager { mixin _$WorkspaceDaoMixin on DatabaseAccessor { $WorkspacesTable get workspaces => attachedDatabase.workspaces; + $WorktreesTable get worktrees => attachedDatabase.worktrees; WorkspaceDaoManager get managers => WorkspaceDaoManager(this); } @@ -25,10 +26,28 @@ class WorkspaceDaoManager { WorkspaceDaoManager(this._db); $$WorkspacesTableTableManager get workspaces => $$WorkspacesTableTableManager(_db.attachedDatabase, _db.workspaces); + $$WorktreesTableTableManager get worktrees => + $$WorktreesTableTableManager(_db.attachedDatabase, _db.worktrees); +} + +mixin _$WorktreeDaoMixin on DatabaseAccessor { + $WorkspacesTable get workspaces => attachedDatabase.workspaces; + $WorktreesTable get worktrees => attachedDatabase.worktrees; + WorktreeDaoManager get managers => WorktreeDaoManager(this); +} + +class WorktreeDaoManager { + final _$WorktreeDaoMixin _db; + WorktreeDaoManager(this._db); + $$WorkspacesTableTableManager get workspaces => + $$WorkspacesTableTableManager(_db.attachedDatabase, _db.workspaces); + $$WorktreesTableTableManager get worktrees => + $$WorktreesTableTableManager(_db.attachedDatabase, _db.worktrees); } mixin _$AgentDaoMixin on DatabaseAccessor { $WorkspacesTable get workspaces => attachedDatabase.workspaces; + $WorktreesTable get worktrees => attachedDatabase.worktrees; $AgentsTable get agents => attachedDatabase.agents; $TurnsTable get turns => attachedDatabase.turns; AgentDaoManager get managers => AgentDaoManager(this); @@ -39,6 +58,8 @@ class AgentDaoManager { AgentDaoManager(this._db); $$WorkspacesTableTableManager get workspaces => $$WorkspacesTableTableManager(_db.attachedDatabase, _db.workspaces); + $$WorktreesTableTableManager get worktrees => + $$WorktreesTableTableManager(_db.attachedDatabase, _db.worktrees); $$AgentsTableTableManager get agents => $$AgentsTableTableManager(_db.attachedDatabase, _db.agents); $$TurnsTableTableManager get turns => @@ -47,6 +68,7 @@ class AgentDaoManager { mixin _$TimelineDaoMixin on DatabaseAccessor { $WorkspacesTable get workspaces => attachedDatabase.workspaces; + $WorktreesTable get worktrees => attachedDatabase.worktrees; $AgentsTable get agents => attachedDatabase.agents; $TimelineEventsTable get timelineEvents => attachedDatabase.timelineEvents; $TurnsTable get turns => attachedDatabase.turns; @@ -61,6 +83,8 @@ class TimelineDaoManager { TimelineDaoManager(this._db); $$WorkspacesTableTableManager get workspaces => $$WorkspacesTableTableManager(_db.attachedDatabase, _db.workspaces); + $$WorktreesTableTableManager get worktrees => + $$WorktreesTableTableManager(_db.attachedDatabase, _db.worktrees); $$AgentsTableTableManager get agents => $$AgentsTableTableManager(_db.attachedDatabase, _db.agents); $$TimelineEventsTableTableManager get timelineEvents => @@ -87,6 +111,7 @@ mixin _$ProviderDaoMixin on DatabaseAccessor { attachedDatabase.providerConnections; $ProviderModelsTable get providerModels => attachedDatabase.providerModels; $WorkspacesTable get workspaces => attachedDatabase.workspaces; + $WorktreesTable get worktrees => attachedDatabase.worktrees; $AgentsTable get agents => attachedDatabase.agents; ProviderDaoManager get managers => ProviderDaoManager(this); } @@ -106,12 +131,15 @@ class ProviderDaoManager { ); $$WorkspacesTableTableManager get workspaces => $$WorkspacesTableTableManager(_db.attachedDatabase, _db.workspaces); + $$WorktreesTableTableManager get worktrees => + $$WorktreesTableTableManager(_db.attachedDatabase, _db.worktrees); $$AgentsTableTableManager get agents => $$AgentsTableTableManager(_db.attachedDatabase, _db.agents); } mixin _$RuntimeDaoMixin on DatabaseAccessor { $WorkspacesTable get workspaces => attachedDatabase.workspaces; + $WorktreesTable get worktrees => attachedDatabase.worktrees; $AgentsTable get agents => attachedDatabase.agents; $TurnsTable get turns => attachedDatabase.turns; $ApprovalRequestsTable get approvalRequests => @@ -124,6 +152,8 @@ class RuntimeDaoManager { RuntimeDaoManager(this._db); $$WorkspacesTableTableManager get workspaces => $$WorkspacesTableTableManager(_db.attachedDatabase, _db.workspaces); + $$WorktreesTableTableManager get worktrees => + $$WorktreesTableTableManager(_db.attachedDatabase, _db.worktrees); $$AgentsTableTableManager get agents => $$AgentsTableTableManager(_db.attachedDatabase, _db.agents); $$TurnsTableTableManager get turns => diff --git a/packages/coder_daemon/lib/src/database.dart b/packages/coder_daemon/lib/src/database.dart index 4641aeb..80bed4b 100644 --- a/packages/coder_daemon/lib/src/database.dart +++ b/packages/coder_daemon/lib/src/database.dart @@ -18,6 +18,9 @@ class Workspaces extends Table { /// The rootPath public API member. TextColumn get rootPath => text()(); + /// Whether this workspace represents a Git repository or a directory. + TextColumn get kind => text()(); + /// The createdAt public API member. DateTimeColumn get createdAt => dateTime()(); @@ -25,13 +28,49 @@ class Workspaces extends Table { Set> get primaryKey => >{id}; } +/// A concrete checkout belonging to a repository workspace. +class Worktrees extends Table { + /// Stable worktree identifier. + TextColumn get id => text()(); + + /// Owning workspace identifier. + TextColumn get workspaceId => text().references(Workspaces, #id)(); + + /// Human-readable checkout name. + TextColumn get name => text()(); + + /// Canonical checkout path. + TextColumn get path => text()(); + + /// Checked-out branch, when this is a Git worktree. + TextColumn get branch => text().nullable()(); + + /// Current commit, when this is a Git worktree. + TextColumn get head => text().nullable()(); + + /// Worktree ownership and lifecycle kind. + TextColumn get kind => text()(); + + /// Whether Coder created and may remove the checkout directory. + BoolColumn get isCoderOwned => boolean()(); + + /// Archive instant; null while visible in the workspace catalog. + DateTimeColumn get archivedAt => dateTime().nullable()(); + + /// Creation instant. + DateTimeColumn get createdAt => dateTime()(); + + @override + Set> get primaryKey => >{id}; +} + /// Agents defines a public contract. class Agents extends Table { /// The id public API member. TextColumn get id => text()(); - /// The workspaceId public API member. - TextColumn get workspaceId => text().references(Workspaces, #id)(); + /// The worktreeId public API member. + TextColumn get worktreeId => text().references(Worktrees, #id)(); /// The title public API member. TextColumn get title => text()(); @@ -267,6 +306,7 @@ class ProviderModels extends Table { @DriftDatabase( tables: [ Workspaces, + Worktrees, Agents, Turns, TimelineEvents, @@ -279,6 +319,7 @@ class ProviderModels extends Table { daos: [ SettingsDao, WorkspaceDao, + WorktreeDao, AgentDao, TimelineDao, ProviderDao, @@ -303,7 +344,7 @@ class CoderDatabase extends _$CoderDatabase { final String databasePath; @override - int get schemaVersion => 4; + int get schemaVersion => 5; @override MigrationStrategy get migration => MigrationStrategy( diff --git a/packages/coder_daemon/lib/src/database.g.dart b/packages/coder_daemon/lib/src/database.g.dart index 50b2861..12d9495 100644 --- a/packages/coder_daemon/lib/src/database.g.dart +++ b/packages/coder_daemon/lib/src/database.g.dart @@ -38,6 +38,15 @@ class $WorkspacesTable extends Workspaces type: DriftSqlType.string, requiredDuringInsert: true, ); + static const VerificationMeta _kindMeta = const VerificationMeta('kind'); + @override + late final GeneratedColumn kind = GeneratedColumn( + 'kind', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); static const VerificationMeta _createdAtMeta = const VerificationMeta( 'createdAt', ); @@ -50,7 +59,7 @@ class $WorkspacesTable extends Workspaces requiredDuringInsert: true, ); @override - List get $columns => [id, name, rootPath, createdAt]; + List get $columns => [id, name, rootPath, kind, createdAt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -84,6 +93,14 @@ class $WorkspacesTable extends Workspaces } else if (isInserting) { context.missing(_rootPathMeta); } + if (data.containsKey('kind')) { + context.handle( + _kindMeta, + kind.isAcceptableOrUnknown(data['kind']!, _kindMeta), + ); + } else if (isInserting) { + context.missing(_kindMeta); + } if (data.containsKey('created_at')) { context.handle( _createdAtMeta, @@ -113,6 +130,10 @@ class $WorkspacesTable extends Workspaces DriftSqlType.string, data['${effectivePrefix}root_path'], )!, + kind: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}kind'], + )!, createdAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}created_at'], @@ -136,12 +157,16 @@ class Workspace extends DataClass implements Insertable { /// The rootPath public API member. final String rootPath; + /// Whether this workspace represents a Git repository or a directory. + final String kind; + /// The createdAt public API member. final DateTime createdAt; const Workspace({ required this.id, required this.name, required this.rootPath, + required this.kind, required this.createdAt, }); @override @@ -150,6 +175,7 @@ class Workspace extends DataClass implements Insertable { map['id'] = Variable(id); map['name'] = Variable(name); map['root_path'] = Variable(rootPath); + map['kind'] = Variable(kind); map['created_at'] = Variable(createdAt); return map; } @@ -159,6 +185,7 @@ class Workspace extends DataClass implements Insertable { id: Value(id), name: Value(name), rootPath: Value(rootPath), + kind: Value(kind), createdAt: Value(createdAt), ); } @@ -172,6 +199,7 @@ class Workspace extends DataClass implements Insertable { id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), rootPath: serializer.fromJson(json['rootPath']), + kind: serializer.fromJson(json['kind']), createdAt: serializer.fromJson(json['createdAt']), ); } @@ -182,6 +210,7 @@ class Workspace extends DataClass implements Insertable { 'id': serializer.toJson(id), 'name': serializer.toJson(name), 'rootPath': serializer.toJson(rootPath), + 'kind': serializer.toJson(kind), 'createdAt': serializer.toJson(createdAt), }; } @@ -190,11 +219,13 @@ class Workspace extends DataClass implements Insertable { String? id, String? name, String? rootPath, + String? kind, DateTime? createdAt, }) => Workspace( id: id ?? this.id, name: name ?? this.name, rootPath: rootPath ?? this.rootPath, + kind: kind ?? this.kind, createdAt: createdAt ?? this.createdAt, ); Workspace copyWithCompanion(WorkspacesCompanion data) { @@ -202,6 +233,7 @@ class Workspace extends DataClass implements Insertable { id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, rootPath: data.rootPath.present ? data.rootPath.value : this.rootPath, + kind: data.kind.present ? data.kind.value : this.kind, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, ); } @@ -212,13 +244,14 @@ class Workspace extends DataClass implements Insertable { ..write('id: $id, ') ..write('name: $name, ') ..write('rootPath: $rootPath, ') + ..write('kind: $kind, ') ..write('createdAt: $createdAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name, rootPath, createdAt); + int get hashCode => Object.hash(id, name, rootPath, kind, createdAt); @override bool operator ==(Object other) => identical(this, other) || @@ -226,6 +259,7 @@ class Workspace extends DataClass implements Insertable { other.id == this.id && other.name == this.name && other.rootPath == this.rootPath && + other.kind == this.kind && other.createdAt == this.createdAt); } @@ -233,12 +267,14 @@ class WorkspacesCompanion extends UpdateCompanion { final Value id; final Value name; final Value rootPath; + final Value kind; final Value createdAt; final Value rowid; const WorkspacesCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), this.rootPath = const Value.absent(), + this.kind = const Value.absent(), this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); @@ -246,16 +282,19 @@ class WorkspacesCompanion extends UpdateCompanion { required String id, required String name, required String rootPath, + required String kind, required DateTime createdAt, this.rowid = const Value.absent(), }) : id = Value(id), name = Value(name), rootPath = Value(rootPath), + kind = Value(kind), createdAt = Value(createdAt); static Insertable custom({ Expression? id, Expression? name, Expression? rootPath, + Expression? kind, Expression? createdAt, Expression? rowid, }) { @@ -263,6 +302,7 @@ class WorkspacesCompanion extends UpdateCompanion { if (id != null) 'id': id, if (name != null) 'name': name, if (rootPath != null) 'root_path': rootPath, + if (kind != null) 'kind': kind, if (createdAt != null) 'created_at': createdAt, if (rowid != null) 'rowid': rowid, }); @@ -272,6 +312,7 @@ class WorkspacesCompanion extends UpdateCompanion { Value? id, Value? name, Value? rootPath, + Value? kind, Value? createdAt, Value? rowid, }) { @@ -279,6 +320,7 @@ class WorkspacesCompanion extends UpdateCompanion { id: id ?? this.id, name: name ?? this.name, rootPath: rootPath ?? this.rootPath, + kind: kind ?? this.kind, createdAt: createdAt ?? this.createdAt, rowid: rowid ?? this.rowid, ); @@ -296,6 +338,9 @@ class WorkspacesCompanion extends UpdateCompanion { if (rootPath.present) { map['root_path'] = Variable(rootPath.value); } + if (kind.present) { + map['kind'] = Variable(kind.value); + } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); } @@ -311,6 +356,7 @@ class WorkspacesCompanion extends UpdateCompanion { ..write('id: $id, ') ..write('name: $name, ') ..write('rootPath: $rootPath, ') + ..write('kind: $kind, ') ..write('createdAt: $createdAt, ') ..write('rowid: $rowid') ..write(')')) @@ -318,11 +364,12 @@ class WorkspacesCompanion extends UpdateCompanion { } } -class $AgentsTable extends Agents with TableInfo<$AgentsTable, Agent> { +class $WorktreesTable extends Worktrees + with TableInfo<$WorktreesTable, Worktree> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $AgentsTable(this.attachedDatabase, [this._alias]); + $WorktreesTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( @@ -346,6 +393,641 @@ class $AgentsTable extends Agents with TableInfo<$AgentsTable, Agent> { 'REFERENCES workspaces (id)', ), ); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _pathMeta = const VerificationMeta('path'); + @override + late final GeneratedColumn path = GeneratedColumn( + 'path', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _branchMeta = const VerificationMeta('branch'); + @override + late final GeneratedColumn branch = GeneratedColumn( + 'branch', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _headMeta = const VerificationMeta('head'); + @override + late final GeneratedColumn head = GeneratedColumn( + 'head', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + static const VerificationMeta _kindMeta = const VerificationMeta('kind'); + @override + late final GeneratedColumn kind = GeneratedColumn( + 'kind', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _isCoderOwnedMeta = const VerificationMeta( + 'isCoderOwned', + ); + @override + late final GeneratedColumn isCoderOwned = GeneratedColumn( + 'is_coder_owned', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_coder_owned" IN (0, 1))', + ), + ); + static const VerificationMeta _archivedAtMeta = const VerificationMeta( + 'archivedAt', + ); + @override + late final GeneratedColumn archivedAt = GeneratedColumn( + 'archived_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + static const VerificationMeta _createdAtMeta = const VerificationMeta( + 'createdAt', + ); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', + aliasedName, + false, + type: DriftSqlType.dateTime, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + id, + workspaceId, + name, + path, + branch, + head, + kind, + isCoderOwned, + archivedAt, + createdAt, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'worktrees'; + @override + VerificationContext validateIntegrity( + Insertable instance, { + bool isInserting = false, + }) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } else if (isInserting) { + context.missing(_idMeta); + } + if (data.containsKey('workspace_id')) { + context.handle( + _workspaceIdMeta, + workspaceId.isAcceptableOrUnknown( + data['workspace_id']!, + _workspaceIdMeta, + ), + ); + } else if (isInserting) { + context.missing(_workspaceIdMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, + name.isAcceptableOrUnknown(data['name']!, _nameMeta), + ); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('path')) { + context.handle( + _pathMeta, + path.isAcceptableOrUnknown(data['path']!, _pathMeta), + ); + } else if (isInserting) { + context.missing(_pathMeta); + } + if (data.containsKey('branch')) { + context.handle( + _branchMeta, + branch.isAcceptableOrUnknown(data['branch']!, _branchMeta), + ); + } + if (data.containsKey('head')) { + context.handle( + _headMeta, + head.isAcceptableOrUnknown(data['head']!, _headMeta), + ); + } + if (data.containsKey('kind')) { + context.handle( + _kindMeta, + kind.isAcceptableOrUnknown(data['kind']!, _kindMeta), + ); + } else if (isInserting) { + context.missing(_kindMeta); + } + if (data.containsKey('is_coder_owned')) { + context.handle( + _isCoderOwnedMeta, + isCoderOwned.isAcceptableOrUnknown( + data['is_coder_owned']!, + _isCoderOwnedMeta, + ), + ); + } else if (isInserting) { + context.missing(_isCoderOwnedMeta); + } + if (data.containsKey('archived_at')) { + context.handle( + _archivedAtMeta, + archivedAt.isAcceptableOrUnknown(data['archived_at']!, _archivedAtMeta), + ); + } + if (data.containsKey('created_at')) { + context.handle( + _createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta), + ); + } else if (isInserting) { + context.missing(_createdAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Worktree map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Worktree( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + workspaceId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}workspace_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + path: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}path'], + )!, + branch: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}branch'], + ), + head: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}head'], + ), + kind: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}kind'], + )!, + isCoderOwned: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}is_coder_owned'], + )!, + archivedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}archived_at'], + ), + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $WorktreesTable createAlias(String alias) { + return $WorktreesTable(attachedDatabase, alias); + } +} + +class Worktree extends DataClass implements Insertable { + /// Stable worktree identifier. + final String id; + + /// Owning workspace identifier. + final String workspaceId; + + /// Human-readable checkout name. + final String name; + + /// Canonical checkout path. + final String path; + + /// Checked-out branch, when this is a Git worktree. + final String? branch; + + /// Current commit, when this is a Git worktree. + final String? head; + + /// Worktree ownership and lifecycle kind. + final String kind; + + /// Whether Coder created and may remove the checkout directory. + final bool isCoderOwned; + + /// Archive instant; null while visible in the workspace catalog. + final DateTime? archivedAt; + + /// Creation instant. + final DateTime createdAt; + const Worktree({ + required this.id, + required this.workspaceId, + required this.name, + required this.path, + this.branch, + this.head, + required this.kind, + required this.isCoderOwned, + this.archivedAt, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['workspace_id'] = Variable(workspaceId); + map['name'] = Variable(name); + map['path'] = Variable(path); + if (!nullToAbsent || branch != null) { + map['branch'] = Variable(branch); + } + if (!nullToAbsent || head != null) { + map['head'] = Variable(head); + } + map['kind'] = Variable(kind); + map['is_coder_owned'] = Variable(isCoderOwned); + if (!nullToAbsent || archivedAt != null) { + map['archived_at'] = Variable(archivedAt); + } + map['created_at'] = Variable(createdAt); + return map; + } + + WorktreesCompanion toCompanion(bool nullToAbsent) { + return WorktreesCompanion( + id: Value(id), + workspaceId: Value(workspaceId), + name: Value(name), + path: Value(path), + branch: branch == null && nullToAbsent + ? const Value.absent() + : Value(branch), + head: head == null && nullToAbsent ? const Value.absent() : Value(head), + kind: Value(kind), + isCoderOwned: Value(isCoderOwned), + archivedAt: archivedAt == null && nullToAbsent + ? const Value.absent() + : Value(archivedAt), + createdAt: Value(createdAt), + ); + } + + factory Worktree.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Worktree( + id: serializer.fromJson(json['id']), + workspaceId: serializer.fromJson(json['workspaceId']), + name: serializer.fromJson(json['name']), + path: serializer.fromJson(json['path']), + branch: serializer.fromJson(json['branch']), + head: serializer.fromJson(json['head']), + kind: serializer.fromJson(json['kind']), + isCoderOwned: serializer.fromJson(json['isCoderOwned']), + archivedAt: serializer.fromJson(json['archivedAt']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'workspaceId': serializer.toJson(workspaceId), + 'name': serializer.toJson(name), + 'path': serializer.toJson(path), + 'branch': serializer.toJson(branch), + 'head': serializer.toJson(head), + 'kind': serializer.toJson(kind), + 'isCoderOwned': serializer.toJson(isCoderOwned), + 'archivedAt': serializer.toJson(archivedAt), + 'createdAt': serializer.toJson(createdAt), + }; + } + + Worktree copyWith({ + String? id, + String? workspaceId, + String? name, + String? path, + Value branch = const Value.absent(), + Value head = const Value.absent(), + String? kind, + bool? isCoderOwned, + Value archivedAt = const Value.absent(), + DateTime? createdAt, + }) => Worktree( + id: id ?? this.id, + workspaceId: workspaceId ?? this.workspaceId, + name: name ?? this.name, + path: path ?? this.path, + branch: branch.present ? branch.value : this.branch, + head: head.present ? head.value : this.head, + kind: kind ?? this.kind, + isCoderOwned: isCoderOwned ?? this.isCoderOwned, + archivedAt: archivedAt.present ? archivedAt.value : this.archivedAt, + createdAt: createdAt ?? this.createdAt, + ); + Worktree copyWithCompanion(WorktreesCompanion data) { + return Worktree( + id: data.id.present ? data.id.value : this.id, + workspaceId: data.workspaceId.present + ? data.workspaceId.value + : this.workspaceId, + name: data.name.present ? data.name.value : this.name, + path: data.path.present ? data.path.value : this.path, + branch: data.branch.present ? data.branch.value : this.branch, + head: data.head.present ? data.head.value : this.head, + kind: data.kind.present ? data.kind.value : this.kind, + isCoderOwned: data.isCoderOwned.present + ? data.isCoderOwned.value + : this.isCoderOwned, + archivedAt: data.archivedAt.present + ? data.archivedAt.value + : this.archivedAt, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('Worktree(') + ..write('id: $id, ') + ..write('workspaceId: $workspaceId, ') + ..write('name: $name, ') + ..write('path: $path, ') + ..write('branch: $branch, ') + ..write('head: $head, ') + ..write('kind: $kind, ') + ..write('isCoderOwned: $isCoderOwned, ') + ..write('archivedAt: $archivedAt, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + workspaceId, + name, + path, + branch, + head, + kind, + isCoderOwned, + archivedAt, + createdAt, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Worktree && + other.id == this.id && + other.workspaceId == this.workspaceId && + other.name == this.name && + other.path == this.path && + other.branch == this.branch && + other.head == this.head && + other.kind == this.kind && + other.isCoderOwned == this.isCoderOwned && + other.archivedAt == this.archivedAt && + other.createdAt == this.createdAt); +} + +class WorktreesCompanion extends UpdateCompanion { + final Value id; + final Value workspaceId; + final Value name; + final Value path; + final Value branch; + final Value head; + final Value kind; + final Value isCoderOwned; + final Value archivedAt; + final Value createdAt; + final Value rowid; + const WorktreesCompanion({ + this.id = const Value.absent(), + this.workspaceId = const Value.absent(), + this.name = const Value.absent(), + this.path = const Value.absent(), + this.branch = const Value.absent(), + this.head = const Value.absent(), + this.kind = const Value.absent(), + this.isCoderOwned = const Value.absent(), + this.archivedAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + WorktreesCompanion.insert({ + required String id, + required String workspaceId, + required String name, + required String path, + this.branch = const Value.absent(), + this.head = const Value.absent(), + required String kind, + required bool isCoderOwned, + this.archivedAt = const Value.absent(), + required DateTime createdAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + workspaceId = Value(workspaceId), + name = Value(name), + path = Value(path), + kind = Value(kind), + isCoderOwned = Value(isCoderOwned), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? workspaceId, + Expression? name, + Expression? path, + Expression? branch, + Expression? head, + Expression? kind, + Expression? isCoderOwned, + Expression? archivedAt, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (workspaceId != null) 'workspace_id': workspaceId, + if (name != null) 'name': name, + if (path != null) 'path': path, + if (branch != null) 'branch': branch, + if (head != null) 'head': head, + if (kind != null) 'kind': kind, + if (isCoderOwned != null) 'is_coder_owned': isCoderOwned, + if (archivedAt != null) 'archived_at': archivedAt, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + WorktreesCompanion copyWith({ + Value? id, + Value? workspaceId, + Value? name, + Value? path, + Value? branch, + Value? head, + Value? kind, + Value? isCoderOwned, + Value? archivedAt, + Value? createdAt, + Value? rowid, + }) { + return WorktreesCompanion( + id: id ?? this.id, + workspaceId: workspaceId ?? this.workspaceId, + name: name ?? this.name, + path: path ?? this.path, + branch: branch ?? this.branch, + head: head ?? this.head, + kind: kind ?? this.kind, + isCoderOwned: isCoderOwned ?? this.isCoderOwned, + archivedAt: archivedAt ?? this.archivedAt, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (workspaceId.present) { + map['workspace_id'] = Variable(workspaceId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (path.present) { + map['path'] = Variable(path.value); + } + if (branch.present) { + map['branch'] = Variable(branch.value); + } + if (head.present) { + map['head'] = Variable(head.value); + } + if (kind.present) { + map['kind'] = Variable(kind.value); + } + if (isCoderOwned.present) { + map['is_coder_owned'] = Variable(isCoderOwned.value); + } + if (archivedAt.present) { + map['archived_at'] = Variable(archivedAt.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('WorktreesCompanion(') + ..write('id: $id, ') + ..write('workspaceId: $workspaceId, ') + ..write('name: $name, ') + ..write('path: $path, ') + ..write('branch: $branch, ') + ..write('head: $head, ') + ..write('kind: $kind, ') + ..write('isCoderOwned: $isCoderOwned, ') + ..write('archivedAt: $archivedAt, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $AgentsTable extends Agents with TableInfo<$AgentsTable, Agent> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $AgentsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + static const VerificationMeta _worktreeIdMeta = const VerificationMeta( + 'worktreeId', + ); + @override + late final GeneratedColumn worktreeId = GeneratedColumn( + 'worktree_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES worktrees (id)', + ), + ); static const VerificationMeta _titleMeta = const VerificationMeta('title'); @override late final GeneratedColumn title = GeneratedColumn( @@ -454,7 +1136,7 @@ class $AgentsTable extends Agents with TableInfo<$AgentsTable, Agent> { @override List get $columns => [ id, - workspaceId, + worktreeId, title, providerConnectionId, model, @@ -483,16 +1165,13 @@ class $AgentsTable extends Agents with TableInfo<$AgentsTable, Agent> { } else if (isInserting) { context.missing(_idMeta); } - if (data.containsKey('workspace_id')) { + if (data.containsKey('worktree_id')) { context.handle( - _workspaceIdMeta, - workspaceId.isAcceptableOrUnknown( - data['workspace_id']!, - _workspaceIdMeta, - ), + _worktreeIdMeta, + worktreeId.isAcceptableOrUnknown(data['worktree_id']!, _worktreeIdMeta), ); } else if (isInserting) { - context.missing(_workspaceIdMeta); + context.missing(_worktreeIdMeta); } if (data.containsKey('title')) { context.handle( @@ -593,9 +1272,9 @@ class $AgentsTable extends Agents with TableInfo<$AgentsTable, Agent> { DriftSqlType.string, data['${effectivePrefix}id'], )!, - workspaceId: attachedDatabase.typeMapping.read( + worktreeId: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}workspace_id'], + data['${effectivePrefix}worktree_id'], )!, title: attachedDatabase.typeMapping.read( DriftSqlType.string, @@ -650,8 +1329,8 @@ class Agent extends DataClass implements Insertable { /// The id public API member. final String id; - /// The workspaceId public API member. - final String workspaceId; + /// The worktreeId public API member. + final String worktreeId; /// The title public API member. final String title; @@ -684,7 +1363,7 @@ class Agent extends DataClass implements Insertable { final DateTime updatedAt; const Agent({ required this.id, - required this.workspaceId, + required this.worktreeId, required this.title, required this.providerConnectionId, required this.model, @@ -700,7 +1379,7 @@ class Agent extends DataClass implements Insertable { Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); - map['workspace_id'] = Variable(workspaceId); + map['worktree_id'] = Variable(worktreeId); map['title'] = Variable(title); map['provider_connection_id'] = Variable(providerConnectionId); map['model'] = Variable(model); @@ -721,7 +1400,7 @@ class Agent extends DataClass implements Insertable { AgentsCompanion toCompanion(bool nullToAbsent) { return AgentsCompanion( id: Value(id), - workspaceId: Value(workspaceId), + worktreeId: Value(worktreeId), title: Value(title), providerConnectionId: Value(providerConnectionId), model: Value(model), @@ -746,7 +1425,7 @@ class Agent extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return Agent( id: serializer.fromJson(json['id']), - workspaceId: serializer.fromJson(json['workspaceId']), + worktreeId: serializer.fromJson(json['worktreeId']), title: serializer.fromJson(json['title']), providerConnectionId: serializer.fromJson( json['providerConnectionId'], @@ -766,7 +1445,7 @@ class Agent extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'workspaceId': serializer.toJson(workspaceId), + 'worktreeId': serializer.toJson(worktreeId), 'title': serializer.toJson(title), 'providerConnectionId': serializer.toJson(providerConnectionId), 'model': serializer.toJson(model), @@ -782,7 +1461,7 @@ class Agent extends DataClass implements Insertable { Agent copyWith({ String? id, - String? workspaceId, + String? worktreeId, String? title, String? providerConnectionId, String? model, @@ -795,7 +1474,7 @@ class Agent extends DataClass implements Insertable { DateTime? updatedAt, }) => Agent( id: id ?? this.id, - workspaceId: workspaceId ?? this.workspaceId, + worktreeId: worktreeId ?? this.worktreeId, title: title ?? this.title, providerConnectionId: providerConnectionId ?? this.providerConnectionId, model: model ?? this.model, @@ -810,9 +1489,9 @@ class Agent extends DataClass implements Insertable { Agent copyWithCompanion(AgentsCompanion data) { return Agent( id: data.id.present ? data.id.value : this.id, - workspaceId: data.workspaceId.present - ? data.workspaceId.value - : this.workspaceId, + worktreeId: data.worktreeId.present + ? data.worktreeId.value + : this.worktreeId, title: data.title.present ? data.title.value : this.title, providerConnectionId: data.providerConnectionId.present ? data.providerConnectionId.value @@ -838,7 +1517,7 @@ class Agent extends DataClass implements Insertable { String toString() { return (StringBuffer('Agent(') ..write('id: $id, ') - ..write('workspaceId: $workspaceId, ') + ..write('worktreeId: $worktreeId, ') ..write('title: $title, ') ..write('providerConnectionId: $providerConnectionId, ') ..write('model: $model, ') @@ -856,7 +1535,7 @@ class Agent extends DataClass implements Insertable { @override int get hashCode => Object.hash( id, - workspaceId, + worktreeId, title, providerConnectionId, model, @@ -873,7 +1552,7 @@ class Agent extends DataClass implements Insertable { identical(this, other) || (other is Agent && other.id == this.id && - other.workspaceId == this.workspaceId && + other.worktreeId == this.worktreeId && other.title == this.title && other.providerConnectionId == this.providerConnectionId && other.model == this.model && @@ -888,7 +1567,7 @@ class Agent extends DataClass implements Insertable { class AgentsCompanion extends UpdateCompanion { final Value id; - final Value workspaceId; + final Value worktreeId; final Value title; final Value providerConnectionId; final Value model; @@ -902,7 +1581,7 @@ class AgentsCompanion extends UpdateCompanion { final Value rowid; const AgentsCompanion({ this.id = const Value.absent(), - this.workspaceId = const Value.absent(), + this.worktreeId = const Value.absent(), this.title = const Value.absent(), this.providerConnectionId = const Value.absent(), this.model = const Value.absent(), @@ -917,7 +1596,7 @@ class AgentsCompanion extends UpdateCompanion { }); AgentsCompanion.insert({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, @@ -930,7 +1609,7 @@ class AgentsCompanion extends UpdateCompanion { required DateTime updatedAt, this.rowid = const Value.absent(), }) : id = Value(id), - workspaceId = Value(workspaceId), + worktreeId = Value(worktreeId), title = Value(title), providerConnectionId = Value(providerConnectionId), model = Value(model), @@ -940,7 +1619,7 @@ class AgentsCompanion extends UpdateCompanion { updatedAt = Value(updatedAt); static Insertable custom({ Expression? id, - Expression? workspaceId, + Expression? worktreeId, Expression? title, Expression? providerConnectionId, Expression? model, @@ -955,7 +1634,7 @@ class AgentsCompanion extends UpdateCompanion { }) { return RawValuesInsertable({ if (id != null) 'id': id, - if (workspaceId != null) 'workspace_id': workspaceId, + if (worktreeId != null) 'worktree_id': worktreeId, if (title != null) 'title': title, if (providerConnectionId != null) 'provider_connection_id': providerConnectionId, @@ -973,7 +1652,7 @@ class AgentsCompanion extends UpdateCompanion { AgentsCompanion copyWith({ Value? id, - Value? workspaceId, + Value? worktreeId, Value? title, Value? providerConnectionId, Value? model, @@ -988,7 +1667,7 @@ class AgentsCompanion extends UpdateCompanion { }) { return AgentsCompanion( id: id ?? this.id, - workspaceId: workspaceId ?? this.workspaceId, + worktreeId: worktreeId ?? this.worktreeId, title: title ?? this.title, providerConnectionId: providerConnectionId ?? this.providerConnectionId, model: model ?? this.model, @@ -1009,9 +1688,9 @@ class AgentsCompanion extends UpdateCompanion { if (id.present) { map['id'] = Variable(id.value); } - if (workspaceId.present) { - map['workspace_id'] = Variable(workspaceId.value); - } + if (worktreeId.present) { + map['worktree_id'] = Variable(worktreeId.value); + } if (title.present) { map['title'] = Variable(title.value); } @@ -1054,7 +1733,7 @@ class AgentsCompanion extends UpdateCompanion { String toString() { return (StringBuffer('AgentsCompanion(') ..write('id: $id, ') - ..write('workspaceId: $workspaceId, ') + ..write('worktreeId: $worktreeId, ') ..write('title: $title, ') ..write('providerConnectionId: $providerConnectionId, ') ..write('model: $model, ') @@ -4560,6 +5239,7 @@ abstract class _$CoderDatabase extends GeneratedDatabase { _$CoderDatabase(QueryExecutor e) : super(e); $CoderDatabaseManager get managers => $CoderDatabaseManager(this); late final $WorkspacesTable workspaces = $WorkspacesTable(this); + late final $WorktreesTable worktrees = $WorktreesTable(this); late final $AgentsTable agents = $AgentsTable(this); late final $TurnsTable turns = $TurnsTable(this); late final $TimelineEventsTable timelineEvents = $TimelineEventsTable(this); @@ -4573,6 +5253,7 @@ abstract class _$CoderDatabase extends GeneratedDatabase { late final $ProviderModelsTable providerModels = $ProviderModelsTable(this); late final SettingsDao settingsDao = SettingsDao(this as CoderDatabase); late final WorkspaceDao workspaceDao = WorkspaceDao(this as CoderDatabase); + late final WorktreeDao worktreeDao = WorktreeDao(this as CoderDatabase); late final AgentDao agentDao = AgentDao(this as CoderDatabase); late final TimelineDao timelineDao = TimelineDao(this as CoderDatabase); late final ProviderDao providerDao = ProviderDao(this as CoderDatabase); @@ -4583,6 +5264,7 @@ abstract class _$CoderDatabase extends GeneratedDatabase { @override List get allSchemaEntities => [ workspaces, + worktrees, agents, turns, timelineEvents, @@ -4598,35 +5280,367 @@ typedef $$WorkspacesTableCreateCompanionBuilder = WorkspacesCompanion Function({ required String id, required String name, - required String rootPath, + required String rootPath, + required String kind, + required DateTime createdAt, + Value rowid, + }); +typedef $$WorkspacesTableUpdateCompanionBuilder = + WorkspacesCompanion Function({ + Value id, + Value name, + Value rootPath, + Value kind, + Value createdAt, + Value rowid, + }); + +final class $$WorkspacesTableReferences + extends BaseReferences<_$CoderDatabase, $WorkspacesTable, Workspace> { + $$WorkspacesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$WorktreesTable, List> + _worktreesRefsTable(_$CoderDatabase db) => MultiTypedResultKey.fromTable( + db.worktrees, + aliasName: 'workspaces__id__worktrees__workspace_id', + ); + + $$WorktreesTableProcessedTableManager get worktreesRefs { + final manager = $$WorktreesTableTableManager( + $_db, + $_db.worktrees, + ).filter((f) => f.workspaceId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_worktreesRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$WorkspacesTableFilterComposer + extends Composer<_$CoderDatabase, $WorkspacesTable> { + $$WorkspacesTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get rootPath => $composableBuilder( + column: $table.rootPath, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get kind => $composableBuilder( + column: $table.kind, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + Expression worktreesRefs( + Expression Function($$WorktreesTableFilterComposer f) f, + ) { + final $$WorktreesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.worktrees, + getReferencedColumn: (t) => t.workspaceId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$WorktreesTableFilterComposer( + $db: $db, + $table: $db.worktrees, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$WorkspacesTableOrderingComposer + extends Composer<_$CoderDatabase, $WorkspacesTable> { + $$WorkspacesTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get rootPath => $composableBuilder( + column: $table.rootPath, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get kind => $composableBuilder( + column: $table.kind, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$WorkspacesTableAnnotationComposer + extends Composer<_$CoderDatabase, $WorkspacesTable> { + $$WorkspacesTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get rootPath => + $composableBuilder(column: $table.rootPath, builder: (column) => column); + + GeneratedColumn get kind => + $composableBuilder(column: $table.kind, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + Expression worktreesRefs( + Expression Function($$WorktreesTableAnnotationComposer a) f, + ) { + final $$WorktreesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.worktrees, + getReferencedColumn: (t) => t.workspaceId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$WorktreesTableAnnotationComposer( + $db: $db, + $table: $db.worktrees, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$WorkspacesTableTableManager + extends + RootTableManager< + _$CoderDatabase, + $WorkspacesTable, + Workspace, + $$WorkspacesTableFilterComposer, + $$WorkspacesTableOrderingComposer, + $$WorkspacesTableAnnotationComposer, + $$WorkspacesTableCreateCompanionBuilder, + $$WorkspacesTableUpdateCompanionBuilder, + (Workspace, $$WorkspacesTableReferences), + Workspace, + PrefetchHooks Function({bool worktreesRefs}) + > { + $$WorkspacesTableTableManager(_$CoderDatabase db, $WorkspacesTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$WorkspacesTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$WorkspacesTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$WorkspacesTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value rootPath = const Value.absent(), + Value kind = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => WorkspacesCompanion( + id: id, + name: name, + rootPath: rootPath, + kind: kind, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String rootPath, + required String kind, + required DateTime createdAt, + Value rowid = const Value.absent(), + }) => WorkspacesCompanion.insert( + id: id, + name: name, + rootPath: rootPath, + kind: kind, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$WorkspacesTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({worktreesRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (worktreesRefs) db.worktrees], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (worktreesRefs) + await $_getPrefetchedData< + Workspace, + $WorkspacesTable, + Worktree + >( + currentTable: table, + referencedTable: $$WorkspacesTableReferences + ._worktreesRefsTable(db), + managerFromTypedResult: (p0) => + $$WorkspacesTableReferences( + db, + table, + p0, + ).worktreesRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where( + (e) => e.workspaceId == item.id, + ), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$WorkspacesTableProcessedTableManager = + ProcessedTableManager< + _$CoderDatabase, + $WorkspacesTable, + Workspace, + $$WorkspacesTableFilterComposer, + $$WorkspacesTableOrderingComposer, + $$WorkspacesTableAnnotationComposer, + $$WorkspacesTableCreateCompanionBuilder, + $$WorkspacesTableUpdateCompanionBuilder, + (Workspace, $$WorkspacesTableReferences), + Workspace, + PrefetchHooks Function({bool worktreesRefs}) + >; +typedef $$WorktreesTableCreateCompanionBuilder = + WorktreesCompanion Function({ + required String id, + required String workspaceId, + required String name, + required String path, + Value branch, + Value head, + required String kind, + required bool isCoderOwned, + Value archivedAt, required DateTime createdAt, Value rowid, }); -typedef $$WorkspacesTableUpdateCompanionBuilder = - WorkspacesCompanion Function({ +typedef $$WorktreesTableUpdateCompanionBuilder = + WorktreesCompanion Function({ Value id, + Value workspaceId, Value name, - Value rootPath, + Value path, + Value branch, + Value head, + Value kind, + Value isCoderOwned, + Value archivedAt, Value createdAt, Value rowid, }); -final class $$WorkspacesTableReferences - extends BaseReferences<_$CoderDatabase, $WorkspacesTable, Workspace> { - $$WorkspacesTableReferences(super.$_db, super.$_table, super.$_typedResult); +final class $$WorktreesTableReferences + extends BaseReferences<_$CoderDatabase, $WorktreesTable, Worktree> { + $$WorktreesTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $WorkspacesTable _workspaceIdTable(_$CoderDatabase db) => + db.workspaces.createAlias('worktrees__workspace_id__workspaces__id'); + + $$WorkspacesTableProcessedTableManager get workspaceId { + final $_column = $_itemColumn('workspace_id')!; + + final manager = $$WorkspacesTableTableManager( + $_db, + $_db.workspaces, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_workspaceIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } static MultiTypedResultKey<$AgentsTable, List> _agentsRefsTable( _$CoderDatabase db, ) => MultiTypedResultKey.fromTable( db.agents, - aliasName: 'workspaces__id__agents__workspace_id', + aliasName: 'worktrees__id__agents__worktree_id', ); $$AgentsTableProcessedTableManager get agentsRefs { final manager = $$AgentsTableTableManager( $_db, $_db.agents, - ).filter((f) => f.workspaceId.id.sqlEquals($_itemColumn('id')!)); + ).filter((f) => f.worktreeId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull(_agentsRefsTable($_db)); return ProcessedTableManager( @@ -4635,9 +5649,9 @@ final class $$WorkspacesTableReferences } } -class $$WorkspacesTableFilterComposer - extends Composer<_$CoderDatabase, $WorkspacesTable> { - $$WorkspacesTableFilterComposer({ +class $$WorktreesTableFilterComposer + extends Composer<_$CoderDatabase, $WorktreesTable> { + $$WorktreesTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -4654,8 +5668,33 @@ class $$WorkspacesTableFilterComposer builder: (column) => ColumnFilters(column), ); - ColumnFilters get rootPath => $composableBuilder( - column: $table.rootPath, + ColumnFilters get path => $composableBuilder( + column: $table.path, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get branch => $composableBuilder( + column: $table.branch, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get head => $composableBuilder( + column: $table.head, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get kind => $composableBuilder( + column: $table.kind, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get isCoderOwned => $composableBuilder( + column: $table.isCoderOwned, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get archivedAt => $composableBuilder( + column: $table.archivedAt, builder: (column) => ColumnFilters(column), ); @@ -4664,6 +5703,29 @@ class $$WorkspacesTableFilterComposer builder: (column) => ColumnFilters(column), ); + $$WorkspacesTableFilterComposer get workspaceId { + final $$WorkspacesTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.workspaceId, + referencedTable: $db.workspaces, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$WorkspacesTableFilterComposer( + $db: $db, + $table: $db.workspaces, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + Expression agentsRefs( Expression Function($$AgentsTableFilterComposer f) f, ) { @@ -4671,7 +5733,7 @@ class $$WorkspacesTableFilterComposer composer: this, getCurrentColumn: (t) => t.id, referencedTable: $db.agents, - getReferencedColumn: (t) => t.workspaceId, + getReferencedColumn: (t) => t.worktreeId, builder: ( joinBuilder, { @@ -4690,9 +5752,9 @@ class $$WorkspacesTableFilterComposer } } -class $$WorkspacesTableOrderingComposer - extends Composer<_$CoderDatabase, $WorkspacesTable> { - $$WorkspacesTableOrderingComposer({ +class $$WorktreesTableOrderingComposer + extends Composer<_$CoderDatabase, $WorktreesTable> { + $$WorktreesTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -4709,8 +5771,33 @@ class $$WorkspacesTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - ColumnOrderings get rootPath => $composableBuilder( - column: $table.rootPath, + ColumnOrderings get path => $composableBuilder( + column: $table.path, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get branch => $composableBuilder( + column: $table.branch, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get head => $composableBuilder( + column: $table.head, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get kind => $composableBuilder( + column: $table.kind, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get isCoderOwned => $composableBuilder( + column: $table.isCoderOwned, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get archivedAt => $composableBuilder( + column: $table.archivedAt, builder: (column) => ColumnOrderings(column), ); @@ -4718,11 +5805,34 @@ class $$WorkspacesTableOrderingComposer column: $table.createdAt, builder: (column) => ColumnOrderings(column), ); + + $$WorkspacesTableOrderingComposer get workspaceId { + final $$WorkspacesTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.workspaceId, + referencedTable: $db.workspaces, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$WorkspacesTableOrderingComposer( + $db: $db, + $table: $db.workspaces, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } -class $$WorkspacesTableAnnotationComposer - extends Composer<_$CoderDatabase, $WorkspacesTable> { - $$WorkspacesTableAnnotationComposer({ +class $$WorktreesTableAnnotationComposer + extends Composer<_$CoderDatabase, $WorktreesTable> { + $$WorktreesTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -4735,12 +5845,54 @@ class $$WorkspacesTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - GeneratedColumn get rootPath => - $composableBuilder(column: $table.rootPath, builder: (column) => column); + GeneratedColumn get path => + $composableBuilder(column: $table.path, builder: (column) => column); + + GeneratedColumn get branch => + $composableBuilder(column: $table.branch, builder: (column) => column); + + GeneratedColumn get head => + $composableBuilder(column: $table.head, builder: (column) => column); + + GeneratedColumn get kind => + $composableBuilder(column: $table.kind, builder: (column) => column); + + GeneratedColumn get isCoderOwned => $composableBuilder( + column: $table.isCoderOwned, + builder: (column) => column, + ); + + GeneratedColumn get archivedAt => $composableBuilder( + column: $table.archivedAt, + builder: (column) => column, + ); GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); + $$WorkspacesTableAnnotationComposer get workspaceId { + final $$WorkspacesTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.workspaceId, + referencedTable: $db.workspaces, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$WorkspacesTableAnnotationComposer( + $db: $db, + $table: $db.workspaces, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + Expression agentsRefs( Expression Function($$AgentsTableAnnotationComposer a) f, ) { @@ -4748,7 +5900,7 @@ class $$WorkspacesTableAnnotationComposer composer: this, getCurrentColumn: (t) => t.id, referencedTable: $db.agents, - getReferencedColumn: (t) => t.workspaceId, + getReferencedColumn: (t) => t.worktreeId, builder: ( joinBuilder, { @@ -4767,57 +5919,81 @@ class $$WorkspacesTableAnnotationComposer } } -class $$WorkspacesTableTableManager +class $$WorktreesTableTableManager extends RootTableManager< _$CoderDatabase, - $WorkspacesTable, - Workspace, - $$WorkspacesTableFilterComposer, - $$WorkspacesTableOrderingComposer, - $$WorkspacesTableAnnotationComposer, - $$WorkspacesTableCreateCompanionBuilder, - $$WorkspacesTableUpdateCompanionBuilder, - (Workspace, $$WorkspacesTableReferences), - Workspace, - PrefetchHooks Function({bool agentsRefs}) + $WorktreesTable, + Worktree, + $$WorktreesTableFilterComposer, + $$WorktreesTableOrderingComposer, + $$WorktreesTableAnnotationComposer, + $$WorktreesTableCreateCompanionBuilder, + $$WorktreesTableUpdateCompanionBuilder, + (Worktree, $$WorktreesTableReferences), + Worktree, + PrefetchHooks Function({bool workspaceId, bool agentsRefs}) > { - $$WorkspacesTableTableManager(_$CoderDatabase db, $WorkspacesTable table) + $$WorktreesTableTableManager(_$CoderDatabase db, $WorktreesTable table) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$WorkspacesTableFilterComposer($db: db, $table: table), + $$WorktreesTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$WorkspacesTableOrderingComposer($db: db, $table: table), + $$WorktreesTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$WorkspacesTableAnnotationComposer($db: db, $table: table), + $$WorktreesTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), + Value workspaceId = const Value.absent(), Value name = const Value.absent(), - Value rootPath = const Value.absent(), + Value path = const Value.absent(), + Value branch = const Value.absent(), + Value head = const Value.absent(), + Value kind = const Value.absent(), + Value isCoderOwned = const Value.absent(), + Value archivedAt = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), - }) => WorkspacesCompanion( + }) => WorktreesCompanion( id: id, + workspaceId: workspaceId, name: name, - rootPath: rootPath, + path: path, + branch: branch, + head: head, + kind: kind, + isCoderOwned: isCoderOwned, + archivedAt: archivedAt, createdAt: createdAt, rowid: rowid, ), createCompanionCallback: ({ required String id, + required String workspaceId, required String name, - required String rootPath, + required String path, + Value branch = const Value.absent(), + Value head = const Value.absent(), + required String kind, + required bool isCoderOwned, + Value archivedAt = const Value.absent(), required DateTime createdAt, Value rowid = const Value.absent(), - }) => WorkspacesCompanion.insert( + }) => WorktreesCompanion.insert( id: id, + workspaceId: workspaceId, name: name, - rootPath: rootPath, + path: path, + branch: branch, + head: head, + kind: kind, + isCoderOwned: isCoderOwned, + archivedAt: archivedAt, createdAt: createdAt, rowid: rowid, ), @@ -4825,32 +6001,57 @@ class $$WorkspacesTableTableManager .map( (e) => ( e.readTable(table), - $$WorkspacesTableReferences(db, table, e), + $$WorktreesTableReferences(db, table, e), ), ) .toList(), - prefetchHooksCallback: ({agentsRefs = false}) { + prefetchHooksCallback: ({workspaceId = false, agentsRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [if (agentsRefs) db.agents], - addJoins: null, + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (workspaceId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.workspaceId, + referencedTable: $$WorktreesTableReferences + ._workspaceIdTable(db), + referencedColumn: $$WorktreesTableReferences + ._workspaceIdTable(db) + .id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return [ if (agentsRefs) - await $_getPrefetchedData< - Workspace, - $WorkspacesTable, - Agent - >( + await $_getPrefetchedData( currentTable: table, - referencedTable: $$WorkspacesTableReferences + referencedTable: $$WorktreesTableReferences ._agentsRefsTable(db), managerFromTypedResult: (p0) => - $$WorkspacesTableReferences(db, table, p0).agentsRefs, + $$WorktreesTableReferences(db, table, p0).agentsRefs, referencedItemsForCurrentItem: (item, referencedItems) => - referencedItems.where( - (e) => e.workspaceId == item.id, - ), + referencedItems.where((e) => e.worktreeId == item.id), typedResults: items, ), ]; @@ -4861,24 +6062,24 @@ class $$WorkspacesTableTableManager ); } -typedef $$WorkspacesTableProcessedTableManager = +typedef $$WorktreesTableProcessedTableManager = ProcessedTableManager< _$CoderDatabase, - $WorkspacesTable, - Workspace, - $$WorkspacesTableFilterComposer, - $$WorkspacesTableOrderingComposer, - $$WorkspacesTableAnnotationComposer, - $$WorkspacesTableCreateCompanionBuilder, - $$WorkspacesTableUpdateCompanionBuilder, - (Workspace, $$WorkspacesTableReferences), - Workspace, - PrefetchHooks Function({bool agentsRefs}) + $WorktreesTable, + Worktree, + $$WorktreesTableFilterComposer, + $$WorktreesTableOrderingComposer, + $$WorktreesTableAnnotationComposer, + $$WorktreesTableCreateCompanionBuilder, + $$WorktreesTableUpdateCompanionBuilder, + (Worktree, $$WorktreesTableReferences), + Worktree, + PrefetchHooks Function({bool workspaceId, bool agentsRefs}) >; typedef $$AgentsTableCreateCompanionBuilder = AgentsCompanion Function({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, @@ -4894,7 +6095,7 @@ typedef $$AgentsTableCreateCompanionBuilder = typedef $$AgentsTableUpdateCompanionBuilder = AgentsCompanion Function({ Value id, - Value workspaceId, + Value worktreeId, Value title, Value providerConnectionId, Value model, @@ -4912,17 +6113,17 @@ final class $$AgentsTableReferences extends BaseReferences<_$CoderDatabase, $AgentsTable, Agent> { $$AgentsTableReferences(super.$_db, super.$_table, super.$_typedResult); - static $WorkspacesTable _workspaceIdTable(_$CoderDatabase db) => - db.workspaces.createAlias('agents__workspace_id__workspaces__id'); + static $WorktreesTable _worktreeIdTable(_$CoderDatabase db) => + db.worktrees.createAlias('agents__worktree_id__worktrees__id'); - $$WorkspacesTableProcessedTableManager get workspaceId { - final $_column = $_itemColumn('workspace_id')!; + $$WorktreesTableProcessedTableManager get worktreeId { + final $_column = $_itemColumn('worktree_id')!; - final manager = $$WorkspacesTableTableManager( + final manager = $$WorktreesTableTableManager( $_db, - $_db.workspaces, + $_db.worktrees, ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_workspaceIdTable($_db)); + final item = $_typedResult.readTableOrNull(_worktreeIdTable($_db)); if (item == null) return manager; return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item]), @@ -5070,20 +6271,20 @@ class $$AgentsTableFilterComposer builder: (column) => ColumnFilters(column), ); - $$WorkspacesTableFilterComposer get workspaceId { - final $$WorkspacesTableFilterComposer composer = $composerBuilder( + $$WorktreesTableFilterComposer get worktreeId { + final $$WorktreesTableFilterComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.workspaceId, - referencedTable: $db.workspaces, + getCurrentColumn: (t) => t.worktreeId, + referencedTable: $db.worktrees, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$WorkspacesTableFilterComposer( + }) => $$WorktreesTableFilterComposer( $db: $db, - $table: $db.workspaces, + $table: $db.worktrees, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -5258,20 +6459,20 @@ class $$AgentsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - $$WorkspacesTableOrderingComposer get workspaceId { - final $$WorkspacesTableOrderingComposer composer = $composerBuilder( + $$WorktreesTableOrderingComposer get worktreeId { + final $$WorktreesTableOrderingComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.workspaceId, - referencedTable: $db.workspaces, + getCurrentColumn: (t) => t.worktreeId, + referencedTable: $db.worktrees, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$WorkspacesTableOrderingComposer( + }) => $$WorktreesTableOrderingComposer( $db: $db, - $table: $db.workspaces, + $table: $db.worktrees, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -5332,20 +6533,20 @@ class $$AgentsTableAnnotationComposer GeneratedColumn get updatedAt => $composableBuilder(column: $table.updatedAt, builder: (column) => column); - $$WorkspacesTableAnnotationComposer get workspaceId { - final $$WorkspacesTableAnnotationComposer composer = $composerBuilder( + $$WorktreesTableAnnotationComposer get worktreeId { + final $$WorktreesTableAnnotationComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.workspaceId, - referencedTable: $db.workspaces, + getCurrentColumn: (t) => t.worktreeId, + referencedTable: $db.worktrees, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$WorkspacesTableAnnotationComposer( + }) => $$WorktreesTableAnnotationComposer( $db: $db, - $table: $db.workspaces, + $table: $db.worktrees, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -5470,7 +6671,7 @@ class $$AgentsTableTableManager (Agent, $$AgentsTableReferences), Agent, PrefetchHooks Function({ - bool workspaceId, + bool worktreeId, bool turnsRefs, bool timelineEventsRefs, bool approvalRequestsRefs, @@ -5491,7 +6692,7 @@ class $$AgentsTableTableManager updateCompanionCallback: ({ Value id = const Value.absent(), - Value workspaceId = const Value.absent(), + Value worktreeId = const Value.absent(), Value title = const Value.absent(), Value providerConnectionId = const Value.absent(), Value model = const Value.absent(), @@ -5505,7 +6706,7 @@ class $$AgentsTableTableManager Value rowid = const Value.absent(), }) => AgentsCompanion( id: id, - workspaceId: workspaceId, + worktreeId: worktreeId, title: title, providerConnectionId: providerConnectionId, model: model, @@ -5521,7 +6722,7 @@ class $$AgentsTableTableManager createCompanionCallback: ({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, @@ -5535,7 +6736,7 @@ class $$AgentsTableTableManager Value rowid = const Value.absent(), }) => AgentsCompanion.insert( id: id, - workspaceId: workspaceId, + worktreeId: worktreeId, title: title, providerConnectionId: providerConnectionId, model: model, @@ -5556,7 +6757,7 @@ class $$AgentsTableTableManager .toList(), prefetchHooksCallback: ({ - workspaceId = false, + worktreeId = false, turnsRefs = false, timelineEventsRefs = false, approvalRequestsRefs = false, @@ -5586,15 +6787,15 @@ class $$AgentsTableTableManager dynamic > >(state) { - if (workspaceId) { + if (worktreeId) { state = state.withJoin( currentTable: table, - currentColumn: table.workspaceId, + currentColumn: table.worktreeId, referencedTable: $$AgentsTableReferences - ._workspaceIdTable(db), + ._worktreeIdTable(db), referencedColumn: $$AgentsTableReferences - ._workspaceIdTable(db) + ._worktreeIdTable(db) .id, ) as T; @@ -5701,7 +6902,7 @@ typedef $$AgentsTableProcessedTableManager = (Agent, $$AgentsTableReferences), Agent, PrefetchHooks Function({ - bool workspaceId, + bool worktreeId, bool turnsRefs, bool timelineEventsRefs, bool approvalRequestsRefs, @@ -8363,6 +9564,8 @@ class $CoderDatabaseManager { $CoderDatabaseManager(this._db); $$WorkspacesTableTableManager get workspaces => $$WorkspacesTableTableManager(_db, _db.workspaces); + $$WorktreesTableTableManager get worktrees => + $$WorktreesTableTableManager(_db, _db.worktrees); $$AgentsTableTableManager get agents => $$AgentsTableTableManager(_db, _db.agents); $$TurnsTableTableManager get turns => diff --git a/packages/coder_daemon/lib/src/embedded.dart b/packages/coder_daemon/lib/src/embedded.dart index 6c20113..bd8df34 100644 --- a/packages/coder_daemon/lib/src/embedded.dart +++ b/packages/coder_daemon/lib/src/embedded.dart @@ -5,12 +5,25 @@ import 'package:coder_agent/coder_agent.dart'; import 'package:coder_daemon/src/application.dart'; import 'package:coder_daemon/src/config.dart'; +/// Typed failure reported when an embedded daemon cannot complete startup. +final class EmbeddedDaemonStartupException implements Exception { + /// Creates an embedded startup failure with a safe diagnostic message. + const EmbeddedDaemonStartupException(this.message); + + /// Startup diagnostic without credential values. + final String message; + + @override + String toString() => message; +} + /// EmbeddedDaemonHandle defines a public contract. class EmbeddedDaemonHandle implements DaemonHandle { EmbeddedDaemonHandle._({ required this.boundEndpoint, required this.serverId, required this.bearerToken, + required this.adminToken, required this._isolate, required this._commands, }); @@ -30,17 +43,20 @@ class EmbeddedDaemonHandle implements DaemonHandle { receive.close(); if (message is! Map) { isolate.kill(priority: Isolate.immediate); - throw StateError('Embedded daemon returned an invalid ready message.'); + throw const EmbeddedDaemonStartupException( + 'Embedded daemon returned an invalid ready message.', + ); } final values = Map.from(message); if (values['error'] case final String error) { isolate.kill(priority: Isolate.immediate); - throw StateError(error); + throw EmbeddedDaemonStartupException(error); } return EmbeddedDaemonHandle._( boundEndpoint: Uri.parse(values['endpoint']! as String), serverId: values['serverId']! as String, bearerToken: values['token']! as String, + adminToken: values['adminToken']! as String, isolate: isolate, commands: values['commands']! as SendPort, ); @@ -52,6 +68,8 @@ class EmbeddedDaemonHandle implements DaemonHandle { final String serverId; @override final String bearerToken; + @override + final String adminToken; final Isolate _isolate; final SendPort _commands; bool _stopped = false; @@ -84,6 +102,7 @@ Future _embeddedDaemonMain(List message) async { 'endpoint': handle.boundEndpoint.toString(), 'serverId': handle.serverId, 'token': handle.bearerToken, + 'adminToken': handle.adminToken, 'commands': commands.sendPort, }); await for (final command in commands) { diff --git a/packages/coder_daemon/lib/src/git_workspace.dart b/packages/coder_daemon/lib/src/git_workspace.dart new file mode 100644 index 0000000..c056ad2 --- /dev/null +++ b/packages/coder_daemon/lib/src/git_workspace.dart @@ -0,0 +1,158 @@ +import 'package:coder_daemon/src/ports.dart'; +import 'package:coder_protocol/coder_protocol.dart'; + +/// Git CLI adapter that never invokes a shell. +final class ProcessGitWorkspaceGateway implements GitWorkspaceGateway { + /// Creates a Git adapter using the injected process boundary. + const ProcessGitWorkspaceGateway(this._commands); + + final CommandRunner _commands; + + @override + Future repositoryRoot(String path) async { + final result = await _commands.run( + 'git', + const ['rev-parse', '--show-toplevel'], + workingDirectory: path, + ); + return result.exitCode == 0 ? result.stdout.trim() : null; + } + + @override + Future> listWorktrees( + String repositoryRoot, + ) async { + final result = await _commands.run( + 'git', + const ['worktree', 'list', '--porcelain'], + workingDirectory: repositoryRoot, + ); + _requireSuccess(result, 'Unable to list Git worktrees.'); + return parseGitWorktreePorcelain(result.stdout); + } + + @override + Future> listBranches(String repositoryRoot) async { + final result = await _commands.run( + 'git', + const [ + 'for-each-ref', + '--format=%(refname:short)%00%(HEAD)', + 'refs/heads', + ], + workingDirectory: repositoryRoot, + ); + _requireSuccess(result, 'Unable to list local branches.'); + final checkedOut = (await listWorktrees( + repositoryRoot, + )).map((item) => item.branch).nonNulls.toSet(); + return result.stdout + .split('\n') + .where((line) => line.isNotEmpty) + .map((line) { + final fields = line.split('\u0000'); + final name = fields.first; + return GitBranchDto( + name: name, + current: fields.length > 1 && fields[1] == '*', + checkedOut: checkedOut.contains(name), + ); + }) + .toList(growable: false); + } + + @override + Future createWorktree(GitWorktreeCreateRequest request) async { + final arguments = ['worktree', 'add']; + if (request.mode == WorktreeCreateMode.newBranch) { + arguments + ..add('-b') + ..add(request.branchName) + ..add(request.path) + ..add(request.baseBranch ?? 'HEAD'); + } else { + arguments + ..add(request.path) + ..add(request.branchName); + } + final result = await _commands.run( + 'git', + arguments, + workingDirectory: request.repositoryRoot, + ); + _requireSuccess(result, 'Unable to create Git worktree.'); + } + + @override + Future inspectWorktree(String path) async { + final status = await _commands.run( + 'git', + const ['status', '--porcelain=v1'], + workingDirectory: path, + ); + _requireSuccess(status, 'Unable to inspect Git worktree.'); + final upstream = await _commands.run( + 'git', + const ['rev-parse', '--abbrev-ref', '@{upstream}'], + workingDirectory: path, + ); + var unpushed = 0; + if (upstream.exitCode == 0) { + final count = await _commands.run( + 'git', + const ['rev-list', '--count', '@{upstream}..HEAD'], + workingDirectory: path, + ); + _requireSuccess(count, 'Unable to inspect unpushed commits.'); + unpushed = int.tryParse(count.stdout.trim()) ?? 0; + } + return GitWorktreeState( + dirty: status.stdout.trim().isNotEmpty, + unpushedCommitCount: unpushed, + ); + } + + @override + Future removeWorktree(String repositoryRoot, String path) async { + final result = await _commands.run( + 'git', + ['worktree', 'remove', path], + workingDirectory: repositoryRoot, + ); + _requireSuccess(result, 'Unable to remove Git worktree.'); + } +} + +/// Parses the stable porcelain output of `git worktree list`. +List parseGitWorktreePorcelain(String output) { + final result = []; + String? path; + String? branch; + String? head; + void flush() { + if (path == null) return; + result.add(GitWorktreeSnapshot(path: path!, branch: branch, head: head)); + path = null; + branch = null; + head = null; + } + + for (final line in '${output.trimRight()}\n\n'.split('\n')) { + if (line.isEmpty) { + flush(); + } else if (line.startsWith('worktree ')) { + path = line.substring('worktree '.length); + } else if (line.startsWith('HEAD ')) { + head = line.substring('HEAD '.length); + } else if (line.startsWith('branch refs/heads/')) { + branch = line.substring('branch refs/heads/'.length); + } + } + return List.unmodifiable(result); +} + +void _requireSuccess(CommandResult result, String message) { + if (result.exitCode != 0) { + throw StateError('$message ${result.stderr.trim()}'); + } +} diff --git a/packages/coder_daemon/lib/src/ports.dart b/packages/coder_daemon/lib/src/ports.dart index 25c6be2..0533402 100644 --- a/packages/coder_daemon/lib/src/ports.dart +++ b/packages/coder_daemon/lib/src/ports.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:coder_protocol/coder_protocol.dart'; +import 'package:path/path.dart' as p; import 'package:uuid/uuid.dart'; /// Public API exposed by this library. @@ -52,3 +54,190 @@ final class IoWorkspaceCanonicalizer implements WorkspaceCanonicalizer { return directory.resolveSymbolicLinksSync(); } } + +/// Filesystem operations needed by the workspace application service. +abstract interface class WorkspacePathGateway { + /// Resolves an existing directory and rejects missing paths. + String canonicalizeExistingDirectory(String path); + + /// Creates a directory and missing parents. + Future createDirectory(String path); + + /// Returns matching directories on the daemon host. + Future> suggest(String query, int limit); +} + +/// Production workspace filesystem adapter. +final class IoWorkspacePathGateway implements WorkspacePathGateway { + /// Creates the production workspace filesystem adapter. + const IoWorkspacePathGateway(); + + @override + String canonicalizeExistingDirectory(String path) => + const IoWorkspaceCanonicalizer().canonicalizeExistingDirectory(path); + + @override + Future createDirectory(String path) => + Directory(path).create(recursive: true); + + @override + Future> suggest(String query, int limit) async { + if (limit <= 0) return const []; + final expanded = query.trim(); + if (expanded.isEmpty) return const []; + final candidate = Directory(expanded); + final parent = candidate.existsSync() ? candidate : candidate.parent; + if (!parent.existsSync()) return const []; + final needle = candidate.existsSync() + ? '' + : p.basename(expanded).toLowerCase(); + final suggestions = []; + try { + await for (final entity in parent.list(followLinks: false)) { + if (entity is! Directory) continue; + final name = p.basename(entity.path); + if (needle.isNotEmpty && !name.toLowerCase().contains(needle)) { + continue; + } + suggestions.add(DirectorySuggestionDto(path: entity.path, name: name)); + if (suggestions.length == limit) break; + } + } on FileSystemException { + return const []; + } + suggestions.sort((left, right) => left.name.compareTo(right.name)); + return suggestions; + } +} + +/// One checkout reported by `git worktree list --porcelain`. +final class GitWorktreeSnapshot { + /// Creates a Git worktree snapshot. + const GitWorktreeSnapshot({ + required this.path, + this.branch, + this.head, + }); + + /// Checkout path. + final String path; + + /// Short local branch name. + final String? branch; + + /// Checked-out commit. + final String? head; +} + +/// State that may make archiving destructive. +final class GitWorktreeState { + /// Creates worktree safety state. + const GitWorktreeState({this.dirty = false, this.unpushedCommitCount = 0}); + + /// Whether tracked or untracked files have changes. + final bool dirty; + + /// Number of commits not present on the configured upstream. + final int unpushedCommitCount; +} + +/// Typed request for `git worktree add`. +final class GitWorktreeCreateRequest { + /// Creates a managed-worktree request. + const GitWorktreeCreateRequest({ + required this.repositoryRoot, + required this.path, + required this.mode, + required this.branchName, + this.baseBranch, + }); + + /// Repository root used as the Git working directory. + final String repositoryRoot; + + /// New checkout path. + final String path; + + /// Whether a branch is created or an existing branch is checked out. + final WorktreeCreateMode mode; + + /// Normalized local branch name. + final String branchName; + + /// Base revision for a newly-created branch. + final String? baseBranch; +} + +/// Git operations used by workspace lifecycle logic. +abstract interface class GitWorkspaceGateway { + /// Resolves a repository root, or null for a non-Git directory. + Future repositoryRoot(String path); + + /// Lists active Git worktrees. + Future> listWorktrees(String repositoryRoot); + + /// Lists local branches and checkout state. + Future> listBranches(String repositoryRoot); + + /// Creates a managed checkout. + Future createWorktree(GitWorktreeCreateRequest request); + + /// Inspects dirty and unpushed state. + Future inspectWorktree(String path); + + /// Removes a managed checkout through Git. + Future removeWorktree(String repositoryRoot, String path); +} + +/// Result returned by a process invocation. +final class CommandResult { + /// Creates an immutable command result. + const CommandResult({ + required this.exitCode, + required this.stdout, + required this.stderr, + }); + + /// Process exit code. + final int exitCode; + + /// Standard output. + final String stdout; + + /// Standard error. + final String stderr; +} + +/// Process boundary used by the Git adapter. +abstract interface class CommandRunner { + /// Runs an executable with an argument list and no shell interpolation. + Future run( + String executable, + List arguments, { + required String workingDirectory, + }); +} + +/// Production process adapter. +final class IoCommandRunner implements CommandRunner { + /// Creates the production process adapter. + const IoCommandRunner(); + + @override + Future run( + String executable, + List arguments, { + required String workingDirectory, + }) async { + final result = await Process.run( + executable, + arguments, + workingDirectory: workingDirectory, + ); + return CommandResult( + exitCode: result.exitCode, + stdout: '${result.stdout}', + stderr: '${result.stderr}', + ); + } +} diff --git a/packages/coder_daemon/lib/src/repositories.dart b/packages/coder_daemon/lib/src/repositories.dart index 0bde599..ec3c2c7 100644 --- a/packages/coder_daemon/lib/src/repositories.dart +++ b/packages/coder_daemon/lib/src/repositories.dart @@ -18,18 +18,45 @@ abstract interface class WorkspaceRepository { /// The getById public API member. Future getById(String id); + /// Finds a workspace by its canonical repository root. + Future getByRootPath(String rootPath); + /// The register public API member. Future register(WorkspaceDto workspace); + + /// Removes a workspace registration and its archived worktrees. + Future unregister(String id); +} + +/// Persistence port for concrete checkouts. +abstract interface class WorktreeRepository { + /// Lists active worktrees, optionally for one workspace. + Future> list({String? workspaceId}); + + /// Returns one worktree, including archived records. + Future getById(String id); + + /// Finds an active worktree by canonical checkout path. + Future getByPath(String path); + + /// Creates or updates a worktree registration. + Future upsert(WorktreeDto worktree); + + /// Marks a worktree as archived without deleting session history. + Future archive(String id, DateTime archivedAt); } /// Public API exposed by this library. abstract interface class AgentRepository { /// The list public API member. - Future> list({String? workspaceId}); + Future> list({String? worktreeId}); /// The getById public API member. Future getById(String id); + /// Counts sessions with a turn currently running or awaiting approval. + Future countActive(String worktreeId); + /// The create public API member. Future create(AgentDto agent); @@ -141,8 +168,14 @@ abstract interface class CredentialRepository { /// The bearerToken public API member. String? get bearerToken; - /// The setBearerToken public API member. - Future setBearerToken(String token); + /// Secret authorizing daemon-local provider administration. + String? get adminToken; + + /// Atomically persists daemon access and local-administration tokens. + Future setDaemonTokens({ + required String bearerToken, + required String adminToken, + }); /// Returns the secret credential for one provider connection. ProviderCredential? credential(String connectionId); diff --git a/packages/coder_daemon/lib/src/server.dart b/packages/coder_daemon/lib/src/server.dart index 7fe72ae..c1055ae 100644 --- a/packages/coder_daemon/lib/src/server.dart +++ b/packages/coder_daemon/lib/src/server.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:io' show HttpConnectionInfo; import 'package:coder_daemon/src/agent_service.dart'; import 'package:coder_daemon/src/ports.dart'; import 'package:coder_daemon/src/provider_auth.dart'; import 'package:coder_daemon/src/provider_service.dart'; import 'package:coder_daemon/src/repositories.dart'; +import 'package:coder_daemon/src/workspace_service.dart'; import 'package:coder_protocol/coder_protocol.dart'; import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; -import 'package:path/path.dart' as p; import 'package:shelf/shelf.dart'; import 'package:shelf_web_socket/shelf_web_socket.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; @@ -25,9 +24,9 @@ class DaemonRpcServer { required this.providers, required this.providerAuth, required this.clock, - required this.workspaceCanonicalizer, required this.serverInfo, required this.token, + required this.adminToken, required Stream events, }) { _eventSubscription = events.listen(_broadcast); @@ -42,7 +41,7 @@ class DaemonRpcServer { } /// The workspaces public API member. - final WorkspaceRepository workspaces; + final WorkspaceService workspaces; /// The agentRepository public API member. final AgentRepository agentRepository; @@ -62,14 +61,14 @@ class DaemonRpcServer { /// The clock public API member. final Clock clock; - /// The workspaceCanonicalizer public API member. - final WorkspaceCanonicalizer workspaceCanonicalizer; - /// The serverInfo public API member. final ServerInfoDto serverInfo; /// The token public API member. final String token; + + /// Secret required for local provider-administration capabilities. + final String adminToken; final Set<_ClientSession> _sessions = <_ClientSession>{}; late final StreamSubscription _eventSubscription; late final StreamSubscription _authSubscription; @@ -90,10 +89,10 @@ class DaemonRpcServer { if (request.headers['authorization'] != 'Bearer $token') { return Response.unauthorized('A valid bearer token is required.'); } - final connectionInfo = request.context['shelf.io.connection_info']; - final localAdmin = - connectionInfo is HttpConnectionInfo && - connectionInfo.remoteAddress.isLoopback; + final localAdmin = _constantTimeEquals( + request.headers['x-tinyrack-coder-admin'], + adminToken, + ); return webSocketHandler( (channel, protocol) => _openSession(channel, protocol, localAdmin: localAdmin), @@ -114,7 +113,6 @@ class DaemonRpcServer { providers: providers, providerAuth: providerAuth, clock: clock, - workspaceCanonicalizer: workspaceCanonicalizer, serverInfo: serverInfo, localAdmin: localAdmin, onClosed: () {}, @@ -148,6 +146,22 @@ class DaemonRpcServer { } } +bool _constantTimeEquals(String? candidate, String expected) { + if (candidate == null) return false; + final candidateBytes = utf8.encode(candidate); + final expectedBytes = utf8.encode(expected); + var difference = candidateBytes.length ^ expectedBytes.length; + final length = candidateBytes.length > expectedBytes.length + ? candidateBytes.length + : expectedBytes.length; + for (var index = 0; index < length; index += 1) { + final left = index < candidateBytes.length ? candidateBytes[index] : 0; + final right = index < expectedBytes.length ? expectedBytes[index] : 0; + difference |= left ^ right; + } + return difference == 0; +} + class _ClientSession { _ClientSession({ required this.channel, @@ -158,21 +172,19 @@ class _ClientSession { required this.providers, required this.providerAuth, required this.clock, - required this.workspaceCanonicalizer, required this.serverInfo, required this.localAdmin, required this.onClosed, }); final WebSocketChannel channel; - final WorkspaceRepository workspaces; + final WorkspaceService workspaces; final AgentRepository agentRepository; final TimelineRepository timeline; final AgentService agents; final ProviderService providers; final ProviderAuthCoordinator providerAuth; final Clock clock; - final WorkspaceCanonicalizer workspaceCanonicalizer; final ServerInfoDto serverInfo; final bool localAdmin; void Function() onClosed; @@ -184,8 +196,15 @@ class _ClientSession { _peer = json_rpc.Peer(channel.cast()); _peer.registerMethod(RpcMethod.hello, _hello); for (final method in [ - RpcMethod.workspaceList, + RpcMethod.workspaceCatalog, RpcMethod.workspaceRegister, + RpcMethod.workspaceRefresh, + RpcMethod.workspaceUnregister, + RpcMethod.directorySuggest, + RpcMethod.gitBranchesList, + RpcMethod.worktreeCreate, + RpcMethod.worktreeArchivePreview, + RpcMethod.worktreeArchive, RpcMethod.agentList, RpcMethod.agentCreate, RpcMethod.agentConfigurationUpdate, @@ -284,30 +303,59 @@ class _ClientSession { Map payload, ) async { switch (method) { - case RpcMethod.workspaceList: - final items = await workspaces.list(); - return WorkspaceListResultDto(workspaces: items).toJson(); + case RpcMethod.workspaceCatalog: + return WorkspaceCatalogResultDto( + catalog: await workspaces.catalog(), + ).toJson(); case RpcMethod.workspaceRegister: final request = WorkspaceRegisterParamsDto.fromJson(payload); - final rootPath = request.rootPath; - final canonical = workspaceCanonicalizer.canonicalizeExistingDirectory( - rootPath, - ); - final workspace = await workspaces.register( - WorkspaceDto( - id: request.id, - name: request.name.trim().isNotEmpty - ? request.name.trim() - : p.basename(canonical), - rootPath: canonical, - createdAt: clock.nowUtc(), + return (await workspaces.register(request)).toJson(); + case RpcMethod.workspaceRefresh: + final request = WorkspaceIdParamsDto.fromJson(payload); + return WorkspaceCatalogResultDto( + catalog: await workspaces.refresh(request.workspaceId), + ).toJson(); + case RpcMethod.workspaceUnregister: + final request = WorkspaceIdParamsDto.fromJson(payload); + await workspaces.unregister(request.workspaceId); + return const WorkspaceUnregisterResultDto( + unregistered: true, + ).toJson(); + case RpcMethod.directorySuggest: + final request = DirectorySuggestParamsDto.fromJson(payload); + return DirectorySuggestResultDto( + suggestions: await workspaces.suggestDirectories( + request.query, + request.limit, ), - ); - return WorkspaceResultDto(workspace: workspace).toJson(); + ).toJson(); + case RpcMethod.gitBranchesList: + final request = GitBranchesListParamsDto.fromJson(payload); + return GitBranchesListResultDto( + branches: await workspaces.listBranches(request.workspaceId), + ).toJson(); + case RpcMethod.worktreeCreate: + final request = WorktreeCreateParamsDto.fromJson(payload); + return WorktreeResultDto( + worktree: await workspaces.createWorktree(request), + ).toJson(); + case RpcMethod.worktreeArchivePreview: + final request = WorktreeIdParamsDto.fromJson(payload); + return WorktreeArchivePreviewResultDto( + preview: await workspaces.previewArchive(request.worktreeId), + ).toJson(); + case RpcMethod.worktreeArchive: + final request = WorktreeArchiveParamsDto.fromJson(payload); + return WorktreeResultDto( + worktree: await workspaces.archive( + request.worktreeId, + force: request.force, + ), + ).toJson(); case RpcMethod.agentList: final request = AgentListParamsDto.fromJson(payload); final items = await agentRepository.list( - workspaceId: request.workspaceId, + worktreeId: request.worktreeId, ); return AgentListResultDto(agents: items).toJson(); case RpcMethod.agentCreate: @@ -332,7 +380,7 @@ class _ClientSession { final agent = await agentRepository.create( AgentDto( id: request.id, - workspaceId: request.workspaceId, + worktreeId: request.worktreeId, title: request.title, providerConnectionId: providerConnectionId, model: model, diff --git a/packages/coder_daemon/lib/src/workspace_service.dart b/packages/coder_daemon/lib/src/workspace_service.dart new file mode 100644 index 0000000..4ad5e0b --- /dev/null +++ b/packages/coder_daemon/lib/src/workspace_service.dart @@ -0,0 +1,280 @@ +import 'dart:convert'; + +import 'package:coder_daemon/src/ports.dart'; +import 'package:coder_daemon/src/repositories.dart'; +import 'package:coder_protocol/coder_protocol.dart'; +import 'package:crypto/crypto.dart'; +import 'package:path/path.dart' as p; + +/// Repository and checkout lifecycle application service. +final class WorkspaceService { + /// Creates a workspace service from typed persistence and host ports. + const WorkspaceService( + this._workspaces, + this._worktrees, + this._agents, + this._paths, + this._git, + this._clock, + this._managedWorktreeRoot, + ); + + final WorkspaceRepository _workspaces; + final WorktreeRepository _worktrees; + final AgentRepository _agents; + final WorkspacePathGateway _paths; + final GitWorkspaceGateway _git; + final Clock _clock; + final String _managedWorktreeRoot; + + /// Returns repositories and active worktrees as one catalog snapshot. + Future catalog() async => WorkspaceCatalogDto( + workspaces: await _workspaces.list(), + worktrees: await _worktrees.list(), + ); + + /// Registers a directory or Git repository and discovers its checkouts. + Future register( + WorkspaceRegisterParamsDto request, + ) async { + final selectedPath = _paths.canonicalizeExistingDirectory( + request.rootPath, + ); + final discoveredRoot = await _git.repositoryRoot(selectedPath); + if (discoveredRoot == null) { + final existing = await _workspaces.getByRootPath(selectedPath); + final workspace = + existing ?? + await _workspaces.register( + WorkspaceDto( + id: request.workspaceId, + name: request.name, + rootPath: selectedPath, + kind: WorkspaceKind.directory, + createdAt: _clock.nowUtc(), + ), + ); + final checkout = await _worktrees.upsert( + WorktreeDto( + id: request.checkoutId, + workspaceId: workspace.id, + name: request.name, + path: selectedPath, + kind: WorktreeKind.directory, + isCoderOwned: false, + createdAt: _clock.nowUtc(), + ), + ); + return WorkspaceRegisterResultDto( + workspace: workspace, + worktrees: [checkout], + ); + } + + final snapshots = await _git.listWorktrees(discoveredRoot); + final repositoryRoot = snapshots.isEmpty + ? discoveredRoot + : snapshots.first.path; + final canonicalRoot = _paths.canonicalizeExistingDirectory(repositoryRoot); + final existing = await _workspaces.getByRootPath(canonicalRoot); + final workspace = + existing ?? + await _workspaces.register( + WorkspaceDto( + id: request.workspaceId, + name: request.name, + rootPath: canonicalRoot, + kind: WorkspaceKind.git, + createdAt: _clock.nowUtc(), + ), + ); + final discovered = await _upsertGitSnapshots( + workspace, + snapshots, + checkoutId: request.checkoutId, + ); + return WorkspaceRegisterResultDto( + workspace: workspace, + worktrees: discovered, + ); + } + + /// Refreshes worktree metadata for one repository. + Future refresh(String workspaceId) async { + final workspace = await _requireWorkspace(workspaceId); + if (workspace.kind == WorkspaceKind.git) { + await _upsertGitSnapshots( + workspace, + await _git.listWorktrees(workspace.rootPath), + ); + } + return catalog(); + } + + /// Removes a workspace registration when no session history references it. + Future unregister(String workspaceId) => + _workspaces.unregister(workspaceId); + + /// Searches directories on the daemon host. + Future> suggestDirectories( + String query, + int limit, + ) => _paths.suggest(query, limit); + + /// Lists local branches in one Git workspace. + Future> listBranches(String workspaceId) async { + final workspace = await _requireWorkspace(workspaceId); + if (workspace.kind != WorkspaceKind.git) { + throw StateError('Workspace is not a Git repository.'); + } + return _git.listBranches(workspace.rootPath); + } + + /// Creates a managed checkout from a new or existing local branch. + Future createWorktree(WorktreeCreateParamsDto request) async { + final workspace = await _requireWorkspace(request.workspaceId); + if (workspace.kind != WorkspaceKind.git) { + throw StateError('Managed worktrees require a Git repository.'); + } + if (await _worktrees.getById(request.id) case final existing?) { + return existing; + } + final branch = _normalizeBranch(request.branchName); + final repositoryHash = sha256 + .convert(utf8.encode(workspace.rootPath)) + .toString() + .substring(0, 12); + final checkoutPath = p.join( + _managedWorktreeRoot, + repositoryHash, + branch, + ); + if (await _worktrees.getByPath(checkoutPath) != null) { + throw StateError('A worktree already uses the generated path.'); + } + await _paths.createDirectory(p.dirname(checkoutPath)); + await _git.createWorktree( + GitWorktreeCreateRequest( + repositoryRoot: workspace.rootPath, + path: checkoutPath, + mode: request.mode, + branchName: branch, + baseBranch: request.baseBranch, + ), + ); + final snapshots = await _git.listWorktrees(workspace.rootPath); + final snapshot = snapshots + .where((item) => item.path == checkoutPath) + .firstOrNull; + return _worktrees.upsert( + WorktreeDto( + id: request.id, + workspaceId: workspace.id, + name: branch, + path: checkoutPath, + branch: snapshot?.branch ?? branch, + head: snapshot?.head, + kind: WorktreeKind.managed, + isCoderOwned: true, + createdAt: _clock.nowUtc(), + ), + ); + } + + /// Returns current archive safety conditions. + Future previewArchive(String worktreeId) async { + final worktree = await _requireWorktree(worktreeId); + final state = worktree.kind == WorktreeKind.directory + ? const GitWorktreeState() + : await _git.inspectWorktree(worktree.path); + return WorktreeArchivePreviewDto( + worktreeId: worktree.id, + dirty: state.dirty, + unpushedCommitCount: state.unpushedCommitCount, + runningSessionCount: await _agents.countActive(worktree.id), + removesDirectory: worktree.isCoderOwned, + ); + } + + /// Archives one worktree and removes only Coder-owned managed checkouts. + Future archive( + String worktreeId, { + required bool force, + }) async { + final worktree = await _requireWorktree(worktreeId); + final preview = await previewArchive(worktreeId); + if (preview.runningSessionCount > 0) { + throw StateError('A session is still running in this worktree.'); + } + if (!force && (preview.dirty || preview.unpushedCommitCount > 0)) { + throw StateError('Archive confirmation is required for local changes.'); + } + if (worktree.isCoderOwned) { + final workspace = await _requireWorkspace(worktree.workspaceId); + await _git.removeWorktree(workspace.rootPath, worktree.path); + } + final archivedAt = _clock.nowUtc(); + await _worktrees.archive(worktree.id, archivedAt); + return (await _worktrees.getById(worktree.id))!; + } + + Future> _upsertGitSnapshots( + WorkspaceDto workspace, + List snapshots, { + String? checkoutId, + }) async { + final result = []; + for (var index = 0; index < snapshots.length; index += 1) { + final snapshot = snapshots[index]; + final existing = await _worktrees.getByPath(snapshot.path); + final isCheckout = index == 0; + final worktree = await _worktrees.upsert( + WorktreeDto( + id: + existing?.id ?? + (isCheckout && checkoutId != null + ? checkoutId + : _stableWorktreeId(workspace.id, snapshot.path)), + workspaceId: workspace.id, + name: snapshot.branch ?? p.basename(snapshot.path), + path: snapshot.path, + branch: snapshot.branch, + head: snapshot.head, + kind: + existing?.kind ?? + (isCheckout ? WorktreeKind.checkout : WorktreeKind.external), + isCoderOwned: existing?.isCoderOwned ?? false, + createdAt: existing?.createdAt ?? _clock.nowUtc(), + ), + ); + result.add(worktree); + } + return result; + } + + Future _requireWorkspace(String id) async => + await _workspaces.getById(id) ?? + (throw StateError('Workspace not found: $id')); + + Future _requireWorktree(String id) async => + await _worktrees.getById(id) ?? + (throw StateError('Worktree not found: $id')); +} + +String _normalizeBranch(String input) { + final slug = input + .trim() + .toLowerCase() + .replaceAll(RegExp('[^a-z0-9._-]+'), '-') + .replaceAll(RegExp('-+'), '-') + .replaceAll(RegExp(r'^[-/.]+|[-/.]+$'), ''); + if (slug.isEmpty || slug.contains('..') || slug.endsWith('.lock')) { + throw const FormatException('Invalid branch name.'); + } + return slug; +} + +String _stableWorktreeId(String workspaceId, String path) => sha256 + .convert(utf8.encode('$workspaceId\u0000$path')) + .toString() + .substring(0, 24); diff --git a/packages/coder_daemon/test/credential_store_test.dart b/packages/coder_daemon/test/credential_store_test.dart index 4f2b4d6..28bfdc3 100644 --- a/packages/coder_daemon/test/credential_store_test.dart +++ b/packages/coder_daemon/test/credential_store_test.dart @@ -6,7 +6,7 @@ import 'package:test/test.dart'; void main() { test( - 'stores typed provider credentials separately from daemon auth', + 'stores provider and daemon credentials atomically in one protected file', () async { final directory = await Directory.systemTemp.createTemp( 'coder-credential-test-', @@ -28,7 +28,10 @@ void main() { accountId: 'account-id', ), ); - await store.setBearerToken('daemon-secret'); + await store.setDaemonTokens( + bearerToken: 'daemon-secret', + adminToken: 'admin-secret', + ); final reloaded = CredentialStore(directory.path); await reloaded.load(); @@ -60,24 +63,22 @@ void main() { ), ); expect(reloaded.bearerToken, 'daemon-secret'); + expect(reloaded.adminToken, 'admin-secret'); final credentialsJson = await File( '${directory.path}/credentials.json', ).readAsString(); - final authJson = await File('${directory.path}/auth.json').readAsString(); - expect(credentialsJson, isNot(contains('daemon-secret'))); - expect(authJson, isNot(contains('api-secret'))); - expect(authJson, isNot(contains('access-secret'))); + expect(credentialsJson, contains('daemon-secret')); + expect(credentialsJson, contains('admin-secret')); + expect(credentialsJson, contains('api-secret')); + expect(credentialsJson, contains('access-secret')); + expect(File('${directory.path}/auth.json').existsSync(), isFalse); if (!Platform.isWindows) { expect( File('${directory.path}/credentials.json').statSync().mode & 0x1ff, 0x180, ); - expect( - File('${directory.path}/auth.json').statSync().mode & 0x1ff, - 0x180, - ); } }, ); diff --git a/packages/coder_daemon/test/daemon_integration_test.dart b/packages/coder_daemon/test/daemon_integration_test.dart index 644ce07..e789e39 100644 --- a/packages/coder_daemon/test/daemon_integration_test.dart +++ b/packages/coder_daemon/test/daemon_integration_test.dart @@ -49,9 +49,10 @@ void main() { }); final client = await CoderClient.connect( - endpoint: HostEndpoint( - websocketUri: handle.boundEndpoint, - token: 'test-token-0123456789abcdef0123456789', + endpoint: HostEndpoint(websocketUri: handle.boundEndpoint), + credentials: DaemonCredentials( + bearerToken: 'test-token-0123456789abcdef0123456789', + adminToken: handle.adminToken, ), clientId: 'integration-test', clientKind: 'test', @@ -129,16 +130,21 @@ void main() { isNot(contains('temporary')), ); final registered = await client.registerWorkspace( - id: 'workspace-1', + workspaceId: 'workspace-1', + checkoutId: 'checkout-1', rootPath: workspace.path, name: 'Workspace', ); - expect(registered.rootPath, workspace.resolveSymbolicLinksSync()); - expect(await client.listWorkspaces(), hasLength(1)); + expect( + registered.workspace.rootPath, + workspace.resolveSymbolicLinksSync(), + ); + expect((await client.getWorkspaceCatalog()).workspaces, hasLength(1)); + final checkout = registered.worktrees.single; final agent = await client.createAgent( id: 'agent-1', - workspaceId: registered.id, + worktreeId: checkout.id, title: 'Session', providerConnectionId: 'local-test', model: 'test-model', @@ -152,7 +158,7 @@ void main() { reasoningEffort: 'high', ); expect(configuredAgent.reasoningEffort, 'high'); - expect(await client.listAgents(workspaceId: registered.id), hasLength(1)); + expect(await client.listAgents(worktreeId: checkout.id), hasLength(1)); expect(await client.subscribeTimeline(agent.id), isEmpty); final approvalFuture = client.events @@ -215,65 +221,59 @@ void main() { ), ); expect( - (await client.listAgents(workspaceId: registered.id)).single.status, + (await client.listAgents(worktreeId: checkout.id)).single.status, AgentStatus.idle, ); }, ); - test('non-loopback clients cannot mutate provider settings', () async { - final interfaces = await NetworkInterface.list( - type: InternetAddressType.IPv4, - ); - final address = interfaces - .expand((item) => item.addresses) - .where((item) => !item.isLoopback) - .firstOrNull; - if (address == null) return; - final home = await Directory.systemTemp.createTemp('coder-remote-home-'); - final handle = await DaemonApplication.start( - DaemonConfig( - homeDirectory: home.path, - host: '0.0.0.0', - port: 0, - bearerToken: 'remote-token-0123456789abcdef0123456789', - useEnvironmentCredentials: false, - ), - provider: _PatchProvider(), - ); - addTearDown(() async { - await handle.stop(); - await home.delete(recursive: true); - }); - final client = await CoderClient.connect( - endpoint: HostEndpoint( - websocketUri: handle.boundEndpoint.replace(host: address.address), - token: 'remote-token-0123456789abcdef0123456789', - ), - clientId: 'remote-test', - clientKind: 'mobile', - ); - addTearDown(client.close); - expect(client.serverInfo.features['providerAdmin'], isFalse); - expect( - client.createCustomProvider( - 'denied', - const CustomProviderConfigDto( - name: 'Denied', - baseUrl: 'http://127.0.0.1:9999/v1', - apiFormat: ProviderApiFormat.chatCompletions, - authenticationRequired: false, + test( + 'bearer-only loopback clients cannot mutate provider settings', + () async { + final home = await Directory.systemTemp.createTemp('coder-remote-home-'); + final handle = await DaemonApplication.start( + DaemonConfig( + homeDirectory: home.path, + port: 0, + bearerToken: 'remote-token-0123456789abcdef0123456789', + useEnvironmentCredentials: false, ), - ), - throwsA( - isA().having( - (error) => error.code, - 'code', - 'local_admin_required', + provider: _PatchProvider(), + ); + addTearDown(() async { + await handle.stop(); + await home.delete(recursive: true); + }); + final client = await CoderClient.connect( + endpoint: HostEndpoint(websocketUri: handle.boundEndpoint), + credentials: const DaemonCredentials( + bearerToken: 'remote-token-0123456789abcdef0123456789', ), - ), - ); - }); + clientId: 'remote-test', + clientKind: 'mobile', + ); + addTearDown(client.close); + expect(client.serverInfo.features['providerAdmin'], isFalse); + expect( + client.createCustomProvider( + 'denied', + const CustomProviderConfigDto( + name: 'Denied', + baseUrl: 'http://127.0.0.1:9999/v1', + apiFormat: ProviderApiFormat.chatCompletions, + authenticationRequired: false, + ), + ), + throwsA( + isA().having( + (error) => error.code, + 'code', + 'local_admin_required', + ), + ), + ); + }, + ); test('secrets are not persisted in daemon files', () async { final home = await Directory.systemTemp.createTemp('coder-secret-home-'); @@ -302,19 +302,14 @@ void main() { } expect(persisted.toString(), isNot(contains(token))); expect(persisted.toString(), isNot(contains(apiKey))); - expect( - await File('${config.path}/auth.json').readAsString(), - contains(token), - ); - expect( - await File('${config.path}/credentials.json').readAsString(), - contains(apiKey), - ); + final credentials = await File( + '${config.path}/credentials.json', + ).readAsString(); + expect(credentials, contains(token)); + expect(credentials, contains(apiKey)); + expect(credentials, contains(handle.adminToken)); + expect(File('${config.path}/auth.json').existsSync(), isFalse); if (!Platform.isWindows) { - expect( - File('${config.path}/auth.json').statSync().mode & 0x1ff, - 0x180, - ); expect( File('${config.path}/credentials.json').statSync().mode & 0x1ff, 0x180, diff --git a/packages/coder_daemon/test/provider_service_test.dart b/packages/coder_daemon/test/provider_service_test.dart index b4976d5..cfc1784 100644 --- a/packages/coder_daemon/test/provider_service_test.dart +++ b/packages/coder_daemon/test/provider_service_test.dart @@ -602,10 +602,14 @@ final class _ProviderRepository implements ProviderRepository { final class _Credentials implements CredentialRepository { final Map values = {}; String? token; + String? localAdminToken; @override String? get bearerToken => token; + @override + String? get adminToken => localAdminToken; + @override ProviderCredential? credential(String connectionId) => values[connectionId]; @@ -618,8 +622,12 @@ final class _Credentials implements CredentialRepository { } @override - Future setBearerToken(String token) async { - this.token = token; + Future setDaemonTokens({ + required String bearerToken, + required String adminToken, + }) async { + token = bearerToken; + localAdminToken = adminToken; } @override diff --git a/packages/coder_daemon/test/recovery_test.dart b/packages/coder_daemon/test/recovery_test.dart index a164c64..070133c 100644 --- a/packages/coder_daemon/test/recovery_test.dart +++ b/packages/coder_daemon/test/recovery_test.dart @@ -17,13 +17,25 @@ void main() { id: 'workspace', name: 'Workspace', rootPath: home.path, + kind: WorkspaceKind.directory, + createdAt: now, + ), + ); + await database.worktreeDao.upsert( + WorktreeDto( + id: 'worktree', + workspaceId: 'workspace', + name: 'Workspace', + path: home.path, + kind: WorktreeKind.directory, + isCoderOwned: false, createdAt: now, ), ); await database.agentDao.create( AgentDto( id: 'agent', - workspaceId: 'workspace', + worktreeId: 'worktree', title: 'Agent', providerConnectionId: 'openai', model: 'gpt-5.6-sol', diff --git a/packages/coder_daemon/test/schema_test.dart b/packages/coder_daemon/test/schema_test.dart index b603834..e61aebf 100644 --- a/packages/coder_daemon/test/schema_test.dart +++ b/packages/coder_daemon/test/schema_test.dart @@ -9,14 +9,30 @@ void main() { () => workspaces.id, () => workspaces.name, () => workspaces.rootPath, + () => workspaces.kind, () => workspaces.createdAt, () => workspaces.primaryKey, ]); + final worktrees = Worktrees(); + _expectGeneratedDsl([ + () => worktrees.id, + () => worktrees.workspaceId, + () => worktrees.name, + () => worktrees.path, + () => worktrees.branch, + () => worktrees.head, + () => worktrees.kind, + () => worktrees.isCoderOwned, + () => worktrees.archivedAt, + () => worktrees.createdAt, + () => worktrees.primaryKey, + ]); + final agents = Agents(); _expectGeneratedDsl([ () => agents.id, - () => agents.workspaceId, + () => agents.worktreeId, () => agents.title, () => agents.providerConnectionId, () => agents.model, @@ -121,13 +137,14 @@ void main() { final database = CoderDatabase.forTesting(NativeDatabase.memory()); addTearDown(database.close); - expect(database.schemaVersion, 4); + expect(database.schemaVersion, 5); expect(database.migration, isNotNull); expect( await database.customSelect('PRAGMA foreign_keys').getSingle(), isNotNull, ); expect(await database.workspaceDao.list(), isEmpty); + expect(await database.worktreeDao.list(), isEmpty); }, ); } diff --git a/packages/coder_daemon/test/workspace_path_gateway_test.dart b/packages/coder_daemon/test/workspace_path_gateway_test.dart new file mode 100644 index 0000000..0b7bcf9 --- /dev/null +++ b/packages/coder_daemon/test/workspace_path_gateway_test.dart @@ -0,0 +1,51 @@ +import 'dart:io'; + +import 'package:coder_daemon/src/ports.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +void main() { + test( + 'filesystem gateway canonicalizes, creates, filters, and limits', + () async { + final root = await Directory.systemTemp.createTemp('coder-path-port-'); + addTearDown(() => root.delete(recursive: true)); + const gateway = IoWorkspacePathGateway(); + final alpha = await Directory(p.join(root.path, 'alpha')).create(); + await Directory(p.join(root.path, 'beta')).create(); + await File(p.join(root.path, 'alphabet.txt')).writeAsString('ignored'); + + expect(gateway.canonicalizeExistingDirectory(alpha.path), alpha.path); + final nested = p.join(root.path, 'nested', 'child'); + await gateway.createDirectory(nested); + expect(Directory(nested).existsSync(), isTrue); + expect(await gateway.suggest('', 10), isEmpty); + expect(await gateway.suggest(root.path, 0), isEmpty); + expect( + await gateway.suggest(p.join(root.path, 'missing', 'child'), 10), + isEmpty, + ); + final filtered = await gateway.suggest(p.join(root.path, 'al'), 10); + expect(filtered.map((item) => item.name), ['alpha']); + final limited = await gateway.suggest(root.path, 1); + expect(limited, hasLength(1)); + }, + ); + + test('filesystem gateway rejects a missing workspace', () { + expect( + () => const IoWorkspaceCanonicalizer().canonicalizeExistingDirectory( + p.join(Directory.systemTemp.path, 'coder-path-that-does-not-exist'), + ), + throwsA(isA()), + ); + }); + + test('system adapters produce UTC time and unique UUID identifiers', () { + expect(const SystemClock().nowUtc().isUtc, isTrue); + final first = const UuidIdGenerator().generate(); + final second = const UuidIdGenerator().generate(); + expect(first, isNot(second)); + expect(first, matches(RegExp(r'^[0-9a-f-]{36}$'))); + }); +} diff --git a/packages/coder_daemon/test/workspace_service_test.dart b/packages/coder_daemon/test/workspace_service_test.dart new file mode 100644 index 0000000..e65460b --- /dev/null +++ b/packages/coder_daemon/test/workspace_service_test.dart @@ -0,0 +1,574 @@ +import 'package:coder_daemon/src/database.dart'; +import 'package:coder_daemon/src/git_workspace.dart'; +import 'package:coder_daemon/src/ports.dart'; +import 'package:coder_daemon/src/workspace_service.dart'; +import 'package:coder_protocol/coder_protocol.dart'; +import 'package:drift/native.dart'; +import 'package:test/test.dart'; + +void main() { + test('parses git worktree porcelain without shell-dependent output', () { + final worktrees = parseGitWorktreePorcelain(''' +worktree /repo +HEAD abc123 +branch refs/heads/main + +worktree /repo-feature +HEAD def456 +branch refs/heads/feature/settings + +'''); + + expect(worktrees, hasLength(2)); + expect(worktrees.first.path, '/repo'); + expect(worktrees.first.branch, 'main'); + expect(worktrees.last.branch, 'feature/settings'); + }); + + test( + 'registers repository checkouts and creates a managed worktree', + () async { + final database = CoderDatabase.forTesting( + NativeDatabase.memory(), + clock: _FixedClock(), + ); + addTearDown(database.close); + final git = _FakeGitGateway(); + final service = WorkspaceService( + database.workspaceDao, + database.worktreeDao, + database.agentDao, + _FakeWorkspacePaths(), + git, + _FixedClock(), + '/state/worktrees', + ); + + final registered = await service.register( + const WorkspaceRegisterParamsDto( + workspaceId: 'repo-1', + checkoutId: 'checkout-1', + rootPath: '/repo', + name: 'Repository', + ), + ); + expect(registered.workspace.kind, WorkspaceKind.git); + expect( + registered.worktrees.map((item) => item.kind), + [WorktreeKind.checkout, WorktreeKind.external], + ); + + final managed = await service.createWorktree( + const WorktreeCreateParamsDto( + id: 'managed-1', + workspaceId: 'repo-1', + mode: WorktreeCreateMode.newBranch, + branchName: 'Feature/User Settings', + baseBranch: 'main', + ), + ); + expect(managed.kind, WorktreeKind.managed); + expect(managed.isCoderOwned, isTrue); + expect(managed.path, contains('/state/worktrees/')); + expect(git.created.single.branchName, 'feature-user-settings'); + }, + ); + + test( + 'archive requires confirmation and only removes managed paths', + () async { + final database = CoderDatabase.forTesting( + NativeDatabase.memory(), + clock: _FixedClock(), + ); + addTearDown(database.close); + final git = _FakeGitGateway() + ..state = const GitWorktreeState(dirty: true); + final service = WorkspaceService( + database.workspaceDao, + database.worktreeDao, + database.agentDao, + _FakeWorkspacePaths(), + git, + _FixedClock(), + '/state/worktrees', + ); + await service.register( + const WorkspaceRegisterParamsDto( + workspaceId: 'repo-1', + checkoutId: 'checkout-1', + rootPath: '/repo', + name: 'Repository', + ), + ); + await service.createWorktree( + const WorktreeCreateParamsDto( + id: 'managed-1', + workspaceId: 'repo-1', + mode: WorktreeCreateMode.existingBranch, + branchName: 'topic', + ), + ); + + final preview = await service.previewArchive('managed-1'); + expect(preview.dirty, isTrue); + expect(preview.removesDirectory, isTrue); + await expectLater( + service.archive('managed-1', force: false), + throwsA(isA()), + ); + final archived = await service.archive('managed-1', force: true); + expect(archived.archivedAt?.toUtc(), _FixedClock.now); + expect(git.removed, hasLength(1)); + }, + ); + + test( + 'supports directory lifecycle and rejects Git-only operations', + () async { + final database = CoderDatabase.forTesting( + NativeDatabase.memory(), + clock: _FixedClock(), + ); + addTearDown(database.close); + final git = _FakeGitGateway()..root = null; + final paths = _FakeWorkspacePaths(); + final service = WorkspaceService( + database.workspaceDao, + database.worktreeDao, + database.agentDao, + paths, + git, + _FixedClock(), + '/state/worktrees', + ); + + final registered = await service.register( + const WorkspaceRegisterParamsDto( + workspaceId: 'directory-1', + checkoutId: 'directory-checkout', + rootPath: '/plain', + name: 'Plain folder', + ), + ); + expect(registered.workspace.kind, WorkspaceKind.directory); + expect(registered.worktrees.single.kind, WorktreeKind.directory); + expect((await service.catalog()).workspaces, hasLength(1)); + expect((await service.refresh('directory-1')).worktrees, hasLength(1)); + expect(await service.suggestDirectories('/pl', 4), hasLength(1)); + expect(paths.lastSuggestion, (query: '/pl', limit: 4)); + await expectLater( + service.listBranches('directory-1'), + throwsA(isA()), + ); + await expectLater( + service.createWorktree( + const WorktreeCreateParamsDto( + id: 'not-allowed', + workspaceId: 'directory-1', + mode: WorktreeCreateMode.newBranch, + branchName: 'topic', + ), + ), + throwsA(isA()), + ); + final preview = await service.previewArchive('directory-checkout'); + expect(preview.dirty, isFalse); + expect(preview.removesDirectory, isFalse); + await service.archive('directory-checkout', force: false); + expect(git.removed, isEmpty); + await service.unregister('directory-1'); + expect((await service.catalog()).workspaces, isEmpty); + }, + ); + + test( + 'worktree creation is idempotent and validates branch collisions', + () async { + final database = CoderDatabase.forTesting( + NativeDatabase.memory(), + clock: _FixedClock(), + ); + addTearDown(database.close); + final git = _FakeGitGateway(); + final service = WorkspaceService( + database.workspaceDao, + database.worktreeDao, + database.agentDao, + _FakeWorkspacePaths(), + git, + _FixedClock(), + '/state/worktrees', + ); + await service.register( + const WorkspaceRegisterParamsDto( + workspaceId: 'repo-1', + checkoutId: 'checkout-1', + rootPath: '/repo', + name: 'Repository', + ), + ); + expect(await service.listBranches('repo-1'), hasLength(2)); + + const request = WorktreeCreateParamsDto( + id: 'managed-1', + workspaceId: 'repo-1', + mode: WorktreeCreateMode.newBranch, + branchName: 'Topic Branch', + ); + final created = await service.createWorktree(request); + expect(created.head, 'created-head'); + expect(await service.createWorktree(request), created); + await expectLater( + service.createWorktree( + const WorktreeCreateParamsDto( + id: 'managed-2', + workspaceId: 'repo-1', + mode: WorktreeCreateMode.newBranch, + branchName: 'Topic Branch', + ), + ), + throwsA(isA()), + ); + await expectLater( + service.createWorktree( + const WorktreeCreateParamsDto( + id: 'managed-invalid', + workspaceId: 'repo-1', + mode: WorktreeCreateMode.newBranch, + branchName: '../', + ), + ), + throwsA(isA()), + ); + await expectLater( + service.refresh('missing'), + throwsA(isA()), + ); + await expectLater( + service.previewArchive('missing'), + throwsA(isA()), + ); + }, + ); + + test('archive refuses a worktree with a running session', () async { + final database = CoderDatabase.forTesting( + NativeDatabase.memory(), + clock: _FixedClock(), + ); + addTearDown(database.close); + final service = WorkspaceService( + database.workspaceDao, + database.worktreeDao, + database.agentDao, + _FakeWorkspacePaths(), + _FakeGitGateway(), + _FixedClock(), + '/state/worktrees', + ); + await service.register( + const WorkspaceRegisterParamsDto( + workspaceId: 'repo-1', + checkoutId: 'checkout-1', + rootPath: '/repo', + name: 'Repository', + ), + ); + await database.agentDao.create( + AgentDto( + id: 'running-session', + worktreeId: 'checkout-1', + title: 'Running', + providerConnectionId: 'openai', + model: 'model', + status: AgentStatus.running, + permissionMode: PermissionMode.ask, + createdAt: _FixedClock.now, + updatedAt: _FixedClock.now, + ), + ); + + expect((await service.previewArchive('checkout-1')).runningSessionCount, 1); + await expectLater( + service.archive('checkout-1', force: true), + throwsA(isA()), + ); + }); + + group('process Git workspace gateway', () { + test( + 'discovers repositories, worktrees, and checkout branch state', + () async { + final commands = _FakeCommandRunner([ + _result(stdout: '/repo\n'), + _result(stdout: _worktreePorcelain), + _result(stdout: 'main\u0000*\ntopic\u0000\n'), + _result(stdout: _worktreePorcelain), + ]); + final gateway = ProcessGitWorkspaceGateway(commands); + + expect(await gateway.repositoryRoot('/repo/child'), '/repo'); + final worktrees = await gateway.listWorktrees('/repo'); + expect(worktrees, hasLength(2)); + final branches = await gateway.listBranches('/repo'); + expect( + branches, + [ + const GitBranchDto( + name: 'main', + current: true, + checkedOut: true, + ), + const GitBranchDto( + name: 'topic', + current: false, + checkedOut: false, + ), + ], + ); + expect(commands.invocations.first.executable, 'git'); + expect(commands.invocations.first.workingDirectory, '/repo/child'); + }, + ); + + test('returns null outside a repository and surfaces Git errors', () async { + final commands = _FakeCommandRunner([ + _result(exitCode: 128, stderr: 'not a repository'), + _result(exitCode: 1, stderr: 'broken repository'), + ]); + final gateway = ProcessGitWorkspaceGateway(commands); + + expect(await gateway.repositoryRoot('/plain'), isNull); + await expectLater( + gateway.listWorktrees('/repo'), + throwsA( + isA().having( + (error) => error.message, + 'message', + contains('broken repository'), + ), + ), + ); + }); + + test('creates new and existing branch worktrees without a shell', () async { + final commands = _FakeCommandRunner([ + _result(), + _result(), + ]); + final gateway = ProcessGitWorkspaceGateway(commands); + + await gateway.createWorktree( + const GitWorktreeCreateRequest( + repositoryRoot: '/repo', + path: '/managed/new', + mode: WorktreeCreateMode.newBranch, + branchName: 'feature', + baseBranch: 'develop', + ), + ); + await gateway.createWorktree( + const GitWorktreeCreateRequest( + repositoryRoot: '/repo', + path: '/managed/existing', + mode: WorktreeCreateMode.existingBranch, + branchName: 'topic', + ), + ); + + expect( + commands.invocations.first.arguments, + [ + 'worktree', + 'add', + '-b', + 'feature', + '/managed/new', + 'develop', + ], + ); + expect( + commands.invocations.last.arguments, + ['worktree', 'add', '/managed/existing', 'topic'], + ); + }); + + test( + 'inspects dirty and unpushed state with and without upstream', + () async { + final withUpstream = ProcessGitWorkspaceGateway( + _FakeCommandRunner([ + _result(stdout: ' M lib/main.dart\n'), + _result(stdout: 'origin/main\n'), + _result(stdout: '3\n'), + ]), + ); + expect( + await withUpstream.inspectWorktree('/repo'), + isA() + .having((state) => state.dirty, 'dirty', isTrue) + .having( + (state) => state.unpushedCommitCount, + 'unpushedCommitCount', + 3, + ), + ); + + final withoutUpstream = ProcessGitWorkspaceGateway( + _FakeCommandRunner([ + _result(), + _result(exitCode: 128), + ]), + ); + final clean = await withoutUpstream.inspectWorktree('/repo'); + expect(clean.dirty, isFalse); + expect(clean.unpushedCommitCount, 0); + }, + ); + + test( + 'removes a worktree and validates status and count commands', + () async { + final commands = _FakeCommandRunner([ + _result(), + _result(exitCode: 1, stderr: 'status failed'), + ]); + final gateway = ProcessGitWorkspaceGateway(commands); + + await gateway.removeWorktree('/repo', '/managed/topic'); + expect( + commands.invocations.first.arguments, + ['worktree', 'remove', '/managed/topic'], + ); + await expectLater( + gateway.inspectWorktree('/repo'), + throwsA(isA()), + ); + }, + ); + }); +} + +const _worktreePorcelain = ''' +worktree /repo +HEAD abc123 +branch refs/heads/main + +worktree /repo-feature +HEAD def456 +branch refs/heads/feature/settings + +'''; + +CommandResult _result({ + int exitCode = 0, + String stdout = '', + String stderr = '', +}) => CommandResult(exitCode: exitCode, stdout: stdout, stderr: stderr); + +final class _CommandInvocation { + const _CommandInvocation({ + required this.executable, + required this.arguments, + required this.workingDirectory, + }); + + final String executable; + final List arguments; + final String workingDirectory; +} + +final class _FakeCommandRunner implements CommandRunner { + _FakeCommandRunner(this._results); + + final List _results; + final List<_CommandInvocation> invocations = <_CommandInvocation>[]; + + @override + Future run( + String executable, + List arguments, { + required String workingDirectory, + }) async { + invocations.add( + _CommandInvocation( + executable: executable, + arguments: List.unmodifiable(arguments), + workingDirectory: workingDirectory, + ), + ); + return _results.removeAt(0); + } +} + +final class _FixedClock implements Clock { + static final DateTime now = DateTime.utc(2026, 8, 3); + + @override + DateTime nowUtc() => now; +} + +final class _FakeWorkspacePaths implements WorkspacePathGateway { + ({String query, int limit})? lastSuggestion; + + @override + String canonicalizeExistingDirectory(String path) => path; + + @override + Future createDirectory(String path) async {} + + @override + Future> suggest(String query, int limit) async { + lastSuggestion = (query: query, limit: limit); + return [ + const DirectorySuggestionDto(path: '/repo', name: 'repo'), + ]; + } +} + +final class _FakeGitGateway implements GitWorkspaceGateway { + String? root = '/repo'; + GitWorktreeState state = const GitWorktreeState(); + final List created = []; + final List removed = []; + final List snapshots = [ + const GitWorktreeSnapshot(path: '/repo', branch: 'main', head: 'abc'), + const GitWorktreeSnapshot(path: '/other', branch: 'other', head: 'def'), + ]; + + @override + Future repositoryRoot(String path) async => root; + + @override + Future> listWorktrees( + String repositoryRoot, + ) async => List.unmodifiable(snapshots); + + @override + Future> listBranches(String repositoryRoot) async => + const [ + GitBranchDto(name: 'main', current: true, checkedOut: true), + GitBranchDto(name: 'topic', current: false, checkedOut: false), + ]; + + @override + Future createWorktree(GitWorktreeCreateRequest request) async { + created.add(request); + snapshots.add( + GitWorktreeSnapshot( + path: request.path, + branch: request.branchName, + head: 'created-head', + ), + ); + } + + @override + Future inspectWorktree(String path) async => state; + + @override + Future removeWorktree(String repositoryRoot, String path) async { + removed.add(path); + } +} diff --git a/packages/coder_protocol/lib/src/models.dart b/packages/coder_protocol/lib/src/models.dart index e8feccd..87ebb09 100644 --- a/packages/coder_protocol/lib/src/models.dart +++ b/packages/coder_protocol/lib/src/models.dart @@ -84,6 +84,39 @@ enum ToolRisk { command, } +/// Storage kind of a registered workspace repository. +enum WorkspaceKind { + /// A Git repository whose checkouts and worktrees can be discovered. + git, + + /// A regular directory represented by one directory checkout. + directory, +} + +/// Filesystem placement backing an agent session. +enum WorktreeKind { + /// The workspace's original checkout. + checkout, + + /// A Git worktree created and owned by Tinyrack Coder. + managed, + + /// A Git worktree discovered on disk but not owned by Tinyrack Coder. + external, + + /// The sole checkout for a non-Git directory workspace. + directory, +} + +/// Supported sources for creating a managed Git worktree. +enum WorktreeCreateMode { + /// Creates a new branch from a base branch. + newBranch, + + /// Checks out an existing local branch. + existingBranch, +} + /// API formats supported by custom OpenAI-compatible connections. enum ProviderApiFormat { /// Uses the OpenAI Responses API. @@ -254,6 +287,7 @@ abstract class WorkspaceDto with _$WorkspaceDto { required String id, required String name, required String rootPath, + required WorkspaceKind kind, required DateTime createdAt, }) = _WorkspaceDto; @@ -262,13 +296,95 @@ abstract class WorkspaceDto with _$WorkspaceDto { _$WorkspaceDtoFromJson(json); } +@freezed +/// A checkout or Git worktree belonging to a registered workspace. +abstract class WorktreeDto with _$WorktreeDto { + /// Creates a worktree descriptor. + const factory WorktreeDto({ + required String id, + required String workspaceId, + required String name, + required String path, + required WorktreeKind kind, + required bool isCoderOwned, + required DateTime createdAt, + String? branch, + String? head, + DateTime? archivedAt, + }) = _WorktreeDto; + + /// Decodes a worktree descriptor. + factory WorktreeDto.fromJson(Map json) => + _$WorktreeDtoFromJson(json); +} + +@freezed +/// Atomic workspace and worktree catalog owned by one daemon. +abstract class WorkspaceCatalogDto with _$WorkspaceCatalogDto { + /// Creates a workspace catalog. + const factory WorkspaceCatalogDto({ + required List workspaces, + required List worktrees, + }) = _WorkspaceCatalogDto; + + /// Decodes a workspace catalog. + factory WorkspaceCatalogDto.fromJson(Map json) => + _$WorkspaceCatalogDtoFromJson(json); +} + +@freezed +/// Risk information that must be shown before archiving a worktree. +abstract class WorktreeArchivePreviewDto with _$WorktreeArchivePreviewDto { + /// Creates an archive preview. + const factory WorktreeArchivePreviewDto({ + required String worktreeId, + required bool dirty, + required int unpushedCommitCount, + required int runningSessionCount, + required bool removesDirectory, + }) = _WorktreeArchivePreviewDto; + + /// Decodes an archive preview. + factory WorktreeArchivePreviewDto.fromJson(Map json) => + _$WorktreeArchivePreviewDtoFromJson(json); +} + +@freezed +/// One daemon-side directory search result. +abstract class DirectorySuggestionDto with _$DirectorySuggestionDto { + /// Creates a directory suggestion. + const factory DirectorySuggestionDto({ + required String path, + required String name, + }) = _DirectorySuggestionDto; + + /// Decodes a directory suggestion. + factory DirectorySuggestionDto.fromJson(Map json) => + _$DirectorySuggestionDtoFromJson(json); +} + +@freezed +/// One local branch available to a workspace. +abstract class GitBranchDto with _$GitBranchDto { + /// Creates a Git branch descriptor. + const factory GitBranchDto({ + required String name, + required bool current, + required bool checkedOut, + }) = _GitBranchDto; + + /// Decodes a Git branch descriptor. + factory GitBranchDto.fromJson(Map json) => + _$GitBranchDtoFromJson(json); +} + @freezed /// AgentDto defines a public contract. abstract class AgentDto with _$AgentDto { /// The AgentDto public API member. const factory AgentDto({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, diff --git a/packages/coder_protocol/lib/src/models.freezed.dart b/packages/coder_protocol/lib/src/models.freezed.dart index 252a10a..52f2fac 100644 --- a/packages/coder_protocol/lib/src/models.freezed.dart +++ b/packages/coder_protocol/lib/src/models.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$WorkspaceDto { - String get id; String get name; String get rootPath; DateTime get createdAt; + String get id; String get name; String get rootPath; WorkspaceKind get kind; DateTime get createdAt; /// Create a copy of WorkspaceDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,16 +28,16 @@ $WorkspaceDtoCopyWith get copyWith => _$WorkspaceDtoCopyWithImpl Object.hash(runtimeType,id,name,rootPath,createdAt); +int get hashCode => Object.hash(runtimeType,id,name,rootPath,kind,createdAt); @override String toString() { - return 'WorkspaceDto(id: $id, name: $name, rootPath: $rootPath, createdAt: $createdAt)'; + return 'WorkspaceDto(id: $id, name: $name, rootPath: $rootPath, kind: $kind, createdAt: $createdAt)'; } @@ -48,7 +48,7 @@ abstract mixin class $WorkspaceDtoCopyWith<$Res> { factory $WorkspaceDtoCopyWith(WorkspaceDto value, $Res Function(WorkspaceDto) _then) = _$WorkspaceDtoCopyWithImpl; @useResult $Res call({ - String id, String name, String rootPath, DateTime createdAt + String id, String name, String rootPath, WorkspaceKind kind, DateTime createdAt }); @@ -65,21 +65,1137 @@ class _$WorkspaceDtoCopyWithImpl<$Res> /// Create a copy of WorkspaceDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? rootPath = null,Object? createdAt = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? rootPath = null,Object? kind = null,Object? createdAt = null,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,rootPath: null == rootPath ? _self.rootPath : rootPath // ignore: cast_nullable_to_non_nullable -as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as String,kind: null == kind ? _self.kind : kind // ignore: cast_nullable_to_non_nullable +as WorkspaceKind,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime, + )); +} + +} + + +/// Adds pattern-matching-related methods to [WorkspaceDto]. +extension WorkspaceDtoPatterns on WorkspaceDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _WorkspaceDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _WorkspaceDto value) $default,){ +final _that = this; +switch (_that) { +case _WorkspaceDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceDto value)? $default,){ +final _that = this; +switch (_that) { +case _WorkspaceDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String rootPath, WorkspaceKind kind, DateTime createdAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _WorkspaceDto() when $default != null: +return $default(_that.id,_that.name,_that.rootPath,_that.kind,_that.createdAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String name, String rootPath, WorkspaceKind kind, DateTime createdAt) $default,) {final _that = this; +switch (_that) { +case _WorkspaceDto(): +return $default(_that.id,_that.name,_that.rootPath,_that.kind,_that.createdAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String rootPath, WorkspaceKind kind, DateTime createdAt)? $default,) {final _that = this; +switch (_that) { +case _WorkspaceDto() when $default != null: +return $default(_that.id,_that.name,_that.rootPath,_that.kind,_that.createdAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _WorkspaceDto implements WorkspaceDto { + const _WorkspaceDto({required this.id, required this.name, required this.rootPath, required this.kind, required this.createdAt}); + factory _WorkspaceDto.fromJson(Map json) => _$WorkspaceDtoFromJson(json); + +@override final String id; +@override final String name; +@override final String rootPath; +@override final WorkspaceKind kind; +@override final DateTime createdAt; + +/// Create a copy of WorkspaceDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$WorkspaceDtoCopyWith<_WorkspaceDto> get copyWith => __$WorkspaceDtoCopyWithImpl<_WorkspaceDto>(this, _$identity); + +@override +Map toJson() { + return _$WorkspaceDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceDto&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.rootPath, rootPath) || other.rootPath == rootPath)&&(identical(other.kind, kind) || other.kind == kind)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,name,rootPath,kind,createdAt); + +@override +String toString() { + return 'WorkspaceDto(id: $id, name: $name, rootPath: $rootPath, kind: $kind, createdAt: $createdAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$WorkspaceDtoCopyWith<$Res> implements $WorkspaceDtoCopyWith<$Res> { + factory _$WorkspaceDtoCopyWith(_WorkspaceDto value, $Res Function(_WorkspaceDto) _then) = __$WorkspaceDtoCopyWithImpl; +@override @useResult +$Res call({ + String id, String name, String rootPath, WorkspaceKind kind, DateTime createdAt +}); + + + + +} +/// @nodoc +class __$WorkspaceDtoCopyWithImpl<$Res> + implements _$WorkspaceDtoCopyWith<$Res> { + __$WorkspaceDtoCopyWithImpl(this._self, this._then); + + final _WorkspaceDto _self; + final $Res Function(_WorkspaceDto) _then; + +/// Create a copy of WorkspaceDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? rootPath = null,Object? kind = null,Object? createdAt = null,}) { + return _then(_WorkspaceDto( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,rootPath: null == rootPath ? _self.rootPath : rootPath // ignore: cast_nullable_to_non_nullable +as String,kind: null == kind ? _self.kind : kind // ignore: cast_nullable_to_non_nullable +as WorkspaceKind,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable as DateTime, )); } + +} + + +/// @nodoc +mixin _$WorktreeDto { + + String get id; String get workspaceId; String get name; String get path; WorktreeKind get kind; bool get isCoderOwned; DateTime get createdAt; String? get branch; String? get head; DateTime? get archivedAt; +/// Create a copy of WorktreeDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WorktreeDtoCopyWith get copyWith => _$WorktreeDtoCopyWithImpl(this as WorktreeDto, _$identity); + + /// Serializes this WorktreeDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorktreeDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.kind, kind) || other.kind == kind)&&(identical(other.isCoderOwned, isCoderOwned) || other.isCoderOwned == isCoderOwned)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.branch, branch) || other.branch == branch)&&(identical(other.head, head) || other.head == head)&&(identical(other.archivedAt, archivedAt) || other.archivedAt == archivedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,workspaceId,name,path,kind,isCoderOwned,createdAt,branch,head,archivedAt); + +@override +String toString() { + return 'WorktreeDto(id: $id, workspaceId: $workspaceId, name: $name, path: $path, kind: $kind, isCoderOwned: $isCoderOwned, createdAt: $createdAt, branch: $branch, head: $head, archivedAt: $archivedAt)'; +} + + +} + +/// @nodoc +abstract mixin class $WorktreeDtoCopyWith<$Res> { + factory $WorktreeDtoCopyWith(WorktreeDto value, $Res Function(WorktreeDto) _then) = _$WorktreeDtoCopyWithImpl; +@useResult +$Res call({ + String id, String workspaceId, String name, String path, WorktreeKind kind, bool isCoderOwned, DateTime createdAt, String? branch, String? head, DateTime? archivedAt +}); + + + + +} +/// @nodoc +class _$WorktreeDtoCopyWithImpl<$Res> + implements $WorktreeDtoCopyWith<$Res> { + _$WorktreeDtoCopyWithImpl(this._self, this._then); + + final WorktreeDto _self; + final $Res Function(WorktreeDto) _then; + +/// Create a copy of WorktreeDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? workspaceId = null,Object? name = null,Object? path = null,Object? kind = null,Object? isCoderOwned = null,Object? createdAt = null,Object? branch = freezed,Object? head = freezed,Object? archivedAt = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable +as String,kind: null == kind ? _self.kind : kind // ignore: cast_nullable_to_non_nullable +as WorktreeKind,isCoderOwned: null == isCoderOwned ? _self.isCoderOwned : isCoderOwned // ignore: cast_nullable_to_non_nullable +as bool,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,branch: freezed == branch ? _self.branch : branch // ignore: cast_nullable_to_non_nullable +as String?,head: freezed == head ? _self.head : head // ignore: cast_nullable_to_non_nullable +as String?,archivedAt: freezed == archivedAt ? _self.archivedAt : archivedAt // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [WorktreeDto]. +extension WorktreeDtoPatterns on WorktreeDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _WorktreeDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _WorktreeDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _WorktreeDto value) $default,){ +final _that = this; +switch (_that) { +case _WorktreeDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorktreeDto value)? $default,){ +final _that = this; +switch (_that) { +case _WorktreeDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String workspaceId, String name, String path, WorktreeKind kind, bool isCoderOwned, DateTime createdAt, String? branch, String? head, DateTime? archivedAt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _WorktreeDto() when $default != null: +return $default(_that.id,_that.workspaceId,_that.name,_that.path,_that.kind,_that.isCoderOwned,_that.createdAt,_that.branch,_that.head,_that.archivedAt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, String workspaceId, String name, String path, WorktreeKind kind, bool isCoderOwned, DateTime createdAt, String? branch, String? head, DateTime? archivedAt) $default,) {final _that = this; +switch (_that) { +case _WorktreeDto(): +return $default(_that.id,_that.workspaceId,_that.name,_that.path,_that.kind,_that.isCoderOwned,_that.createdAt,_that.branch,_that.head,_that.archivedAt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String workspaceId, String name, String path, WorktreeKind kind, bool isCoderOwned, DateTime createdAt, String? branch, String? head, DateTime? archivedAt)? $default,) {final _that = this; +switch (_that) { +case _WorktreeDto() when $default != null: +return $default(_that.id,_that.workspaceId,_that.name,_that.path,_that.kind,_that.isCoderOwned,_that.createdAt,_that.branch,_that.head,_that.archivedAt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _WorktreeDto implements WorktreeDto { + const _WorktreeDto({required this.id, required this.workspaceId, required this.name, required this.path, required this.kind, required this.isCoderOwned, required this.createdAt, this.branch, this.head, this.archivedAt}); + factory _WorktreeDto.fromJson(Map json) => _$WorktreeDtoFromJson(json); + +@override final String id; +@override final String workspaceId; +@override final String name; +@override final String path; +@override final WorktreeKind kind; +@override final bool isCoderOwned; +@override final DateTime createdAt; +@override final String? branch; +@override final String? head; +@override final DateTime? archivedAt; + +/// Create a copy of WorktreeDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$WorktreeDtoCopyWith<_WorktreeDto> get copyWith => __$WorktreeDtoCopyWithImpl<_WorktreeDto>(this, _$identity); + +@override +Map toJson() { + return _$WorktreeDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorktreeDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.kind, kind) || other.kind == kind)&&(identical(other.isCoderOwned, isCoderOwned) || other.isCoderOwned == isCoderOwned)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.branch, branch) || other.branch == branch)&&(identical(other.head, head) || other.head == head)&&(identical(other.archivedAt, archivedAt) || other.archivedAt == archivedAt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,workspaceId,name,path,kind,isCoderOwned,createdAt,branch,head,archivedAt); + +@override +String toString() { + return 'WorktreeDto(id: $id, workspaceId: $workspaceId, name: $name, path: $path, kind: $kind, isCoderOwned: $isCoderOwned, createdAt: $createdAt, branch: $branch, head: $head, archivedAt: $archivedAt)'; +} + + +} + +/// @nodoc +abstract mixin class _$WorktreeDtoCopyWith<$Res> implements $WorktreeDtoCopyWith<$Res> { + factory _$WorktreeDtoCopyWith(_WorktreeDto value, $Res Function(_WorktreeDto) _then) = __$WorktreeDtoCopyWithImpl; +@override @useResult +$Res call({ + String id, String workspaceId, String name, String path, WorktreeKind kind, bool isCoderOwned, DateTime createdAt, String? branch, String? head, DateTime? archivedAt +}); + + + + +} +/// @nodoc +class __$WorktreeDtoCopyWithImpl<$Res> + implements _$WorktreeDtoCopyWith<$Res> { + __$WorktreeDtoCopyWithImpl(this._self, this._then); + + final _WorktreeDto _self; + final $Res Function(_WorktreeDto) _then; + +/// Create a copy of WorktreeDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? workspaceId = null,Object? name = null,Object? path = null,Object? kind = null,Object? isCoderOwned = null,Object? createdAt = null,Object? branch = freezed,Object? head = freezed,Object? archivedAt = freezed,}) { + return _then(_WorktreeDto( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable +as String,kind: null == kind ? _self.kind : kind // ignore: cast_nullable_to_non_nullable +as WorktreeKind,isCoderOwned: null == isCoderOwned ? _self.isCoderOwned : isCoderOwned // ignore: cast_nullable_to_non_nullable +as bool,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as DateTime,branch: freezed == branch ? _self.branch : branch // ignore: cast_nullable_to_non_nullable +as String?,head: freezed == head ? _self.head : head // ignore: cast_nullable_to_non_nullable +as String?,archivedAt: freezed == archivedAt ? _self.archivedAt : archivedAt // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + + +} + + +/// @nodoc +mixin _$WorkspaceCatalogDto { + + List get workspaces; List get worktrees; +/// Create a copy of WorkspaceCatalogDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WorkspaceCatalogDtoCopyWith get copyWith => _$WorkspaceCatalogDtoCopyWithImpl(this as WorkspaceCatalogDto, _$identity); + + /// Serializes this WorkspaceCatalogDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceCatalogDto&&const DeepCollectionEquality().equals(other.workspaces, workspaces)&&const DeepCollectionEquality().equals(other.worktrees, worktrees)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(workspaces),const DeepCollectionEquality().hash(worktrees)); + +@override +String toString() { + return 'WorkspaceCatalogDto(workspaces: $workspaces, worktrees: $worktrees)'; +} + + +} + +/// @nodoc +abstract mixin class $WorkspaceCatalogDtoCopyWith<$Res> { + factory $WorkspaceCatalogDtoCopyWith(WorkspaceCatalogDto value, $Res Function(WorkspaceCatalogDto) _then) = _$WorkspaceCatalogDtoCopyWithImpl; +@useResult +$Res call({ + List workspaces, List worktrees +}); + + + + +} +/// @nodoc +class _$WorkspaceCatalogDtoCopyWithImpl<$Res> + implements $WorkspaceCatalogDtoCopyWith<$Res> { + _$WorkspaceCatalogDtoCopyWithImpl(this._self, this._then); + + final WorkspaceCatalogDto _self; + final $Res Function(WorkspaceCatalogDto) _then; + +/// Create a copy of WorkspaceCatalogDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? workspaces = null,Object? worktrees = null,}) { + return _then(_self.copyWith( +workspaces: null == workspaces ? _self.workspaces : workspaces // ignore: cast_nullable_to_non_nullable +as List,worktrees: null == worktrees ? _self.worktrees : worktrees // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [WorkspaceCatalogDto]. +extension WorkspaceCatalogDtoPatterns on WorkspaceCatalogDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceCatalogDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _WorkspaceCatalogDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _WorkspaceCatalogDto value) $default,){ +final _that = this; +switch (_that) { +case _WorkspaceCatalogDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceCatalogDto value)? $default,){ +final _that = this; +switch (_that) { +case _WorkspaceCatalogDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List workspaces, List worktrees)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _WorkspaceCatalogDto() when $default != null: +return $default(_that.workspaces,_that.worktrees);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List workspaces, List worktrees) $default,) {final _that = this; +switch (_that) { +case _WorkspaceCatalogDto(): +return $default(_that.workspaces,_that.worktrees);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List workspaces, List worktrees)? $default,) {final _that = this; +switch (_that) { +case _WorkspaceCatalogDto() when $default != null: +return $default(_that.workspaces,_that.worktrees);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _WorkspaceCatalogDto implements WorkspaceCatalogDto { + const _WorkspaceCatalogDto({required final List workspaces, required final List worktrees}): _workspaces = workspaces,_worktrees = worktrees; + factory _WorkspaceCatalogDto.fromJson(Map json) => _$WorkspaceCatalogDtoFromJson(json); + + final List _workspaces; +@override List get workspaces { + if (_workspaces is EqualUnmodifiableListView) return _workspaces; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_workspaces); +} + + final List _worktrees; +@override List get worktrees { + if (_worktrees is EqualUnmodifiableListView) return _worktrees; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_worktrees); +} + + +/// Create a copy of WorkspaceCatalogDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$WorkspaceCatalogDtoCopyWith<_WorkspaceCatalogDto> get copyWith => __$WorkspaceCatalogDtoCopyWithImpl<_WorkspaceCatalogDto>(this, _$identity); + +@override +Map toJson() { + return _$WorkspaceCatalogDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceCatalogDto&&const DeepCollectionEquality().equals(other._workspaces, _workspaces)&&const DeepCollectionEquality().equals(other._worktrees, _worktrees)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_workspaces),const DeepCollectionEquality().hash(_worktrees)); + +@override +String toString() { + return 'WorkspaceCatalogDto(workspaces: $workspaces, worktrees: $worktrees)'; +} + + +} + +/// @nodoc +abstract mixin class _$WorkspaceCatalogDtoCopyWith<$Res> implements $WorkspaceCatalogDtoCopyWith<$Res> { + factory _$WorkspaceCatalogDtoCopyWith(_WorkspaceCatalogDto value, $Res Function(_WorkspaceCatalogDto) _then) = __$WorkspaceCatalogDtoCopyWithImpl; +@override @useResult +$Res call({ + List workspaces, List worktrees +}); + + + + +} +/// @nodoc +class __$WorkspaceCatalogDtoCopyWithImpl<$Res> + implements _$WorkspaceCatalogDtoCopyWith<$Res> { + __$WorkspaceCatalogDtoCopyWithImpl(this._self, this._then); + + final _WorkspaceCatalogDto _self; + final $Res Function(_WorkspaceCatalogDto) _then; + +/// Create a copy of WorkspaceCatalogDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? workspaces = null,Object? worktrees = null,}) { + return _then(_WorkspaceCatalogDto( +workspaces: null == workspaces ? _self._workspaces : workspaces // ignore: cast_nullable_to_non_nullable +as List,worktrees: null == worktrees ? _self._worktrees : worktrees // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + + +/// @nodoc +mixin _$WorktreeArchivePreviewDto { + + String get worktreeId; bool get dirty; int get unpushedCommitCount; int get runningSessionCount; bool get removesDirectory; +/// Create a copy of WorktreeArchivePreviewDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$WorktreeArchivePreviewDtoCopyWith get copyWith => _$WorktreeArchivePreviewDtoCopyWithImpl(this as WorktreeArchivePreviewDto, _$identity); + + /// Serializes this WorktreeArchivePreviewDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorktreeArchivePreviewDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.dirty, dirty) || other.dirty == dirty)&&(identical(other.unpushedCommitCount, unpushedCommitCount) || other.unpushedCommitCount == unpushedCommitCount)&&(identical(other.runningSessionCount, runningSessionCount) || other.runningSessionCount == runningSessionCount)&&(identical(other.removesDirectory, removesDirectory) || other.removesDirectory == removesDirectory)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,worktreeId,dirty,unpushedCommitCount,runningSessionCount,removesDirectory); + +@override +String toString() { + return 'WorktreeArchivePreviewDto(worktreeId: $worktreeId, dirty: $dirty, unpushedCommitCount: $unpushedCommitCount, runningSessionCount: $runningSessionCount, removesDirectory: $removesDirectory)'; +} + + +} + +/// @nodoc +abstract mixin class $WorktreeArchivePreviewDtoCopyWith<$Res> { + factory $WorktreeArchivePreviewDtoCopyWith(WorktreeArchivePreviewDto value, $Res Function(WorktreeArchivePreviewDto) _then) = _$WorktreeArchivePreviewDtoCopyWithImpl; +@useResult +$Res call({ + String worktreeId, bool dirty, int unpushedCommitCount, int runningSessionCount, bool removesDirectory +}); + + + + +} +/// @nodoc +class _$WorktreeArchivePreviewDtoCopyWithImpl<$Res> + implements $WorktreeArchivePreviewDtoCopyWith<$Res> { + _$WorktreeArchivePreviewDtoCopyWithImpl(this._self, this._then); + + final WorktreeArchivePreviewDto _self; + final $Res Function(WorktreeArchivePreviewDto) _then; + +/// Create a copy of WorktreeArchivePreviewDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? worktreeId = null,Object? dirty = null,Object? unpushedCommitCount = null,Object? runningSessionCount = null,Object? removesDirectory = null,}) { + return _then(_self.copyWith( +worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String,dirty: null == dirty ? _self.dirty : dirty // ignore: cast_nullable_to_non_nullable +as bool,unpushedCommitCount: null == unpushedCommitCount ? _self.unpushedCommitCount : unpushedCommitCount // ignore: cast_nullable_to_non_nullable +as int,runningSessionCount: null == runningSessionCount ? _self.runningSessionCount : runningSessionCount // ignore: cast_nullable_to_non_nullable +as int,removesDirectory: null == removesDirectory ? _self.removesDirectory : removesDirectory // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [WorktreeArchivePreviewDto]. +extension WorktreeArchivePreviewDtoPatterns on WorktreeArchivePreviewDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _WorktreeArchivePreviewDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _WorktreeArchivePreviewDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _WorktreeArchivePreviewDto value) $default,){ +final _that = this; +switch (_that) { +case _WorktreeArchivePreviewDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorktreeArchivePreviewDto value)? $default,){ +final _that = this; +switch (_that) { +case _WorktreeArchivePreviewDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String worktreeId, bool dirty, int unpushedCommitCount, int runningSessionCount, bool removesDirectory)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _WorktreeArchivePreviewDto() when $default != null: +return $default(_that.worktreeId,_that.dirty,_that.unpushedCommitCount,_that.runningSessionCount,_that.removesDirectory);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String worktreeId, bool dirty, int unpushedCommitCount, int runningSessionCount, bool removesDirectory) $default,) {final _that = this; +switch (_that) { +case _WorktreeArchivePreviewDto(): +return $default(_that.worktreeId,_that.dirty,_that.unpushedCommitCount,_that.runningSessionCount,_that.removesDirectory);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String worktreeId, bool dirty, int unpushedCommitCount, int runningSessionCount, bool removesDirectory)? $default,) {final _that = this; +switch (_that) { +case _WorktreeArchivePreviewDto() when $default != null: +return $default(_that.worktreeId,_that.dirty,_that.unpushedCommitCount,_that.runningSessionCount,_that.removesDirectory);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _WorktreeArchivePreviewDto implements WorktreeArchivePreviewDto { + const _WorktreeArchivePreviewDto({required this.worktreeId, required this.dirty, required this.unpushedCommitCount, required this.runningSessionCount, required this.removesDirectory}); + factory _WorktreeArchivePreviewDto.fromJson(Map json) => _$WorktreeArchivePreviewDtoFromJson(json); + +@override final String worktreeId; +@override final bool dirty; +@override final int unpushedCommitCount; +@override final int runningSessionCount; +@override final bool removesDirectory; + +/// Create a copy of WorktreeArchivePreviewDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$WorktreeArchivePreviewDtoCopyWith<_WorktreeArchivePreviewDto> get copyWith => __$WorktreeArchivePreviewDtoCopyWithImpl<_WorktreeArchivePreviewDto>(this, _$identity); + +@override +Map toJson() { + return _$WorktreeArchivePreviewDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorktreeArchivePreviewDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.dirty, dirty) || other.dirty == dirty)&&(identical(other.unpushedCommitCount, unpushedCommitCount) || other.unpushedCommitCount == unpushedCommitCount)&&(identical(other.runningSessionCount, runningSessionCount) || other.runningSessionCount == runningSessionCount)&&(identical(other.removesDirectory, removesDirectory) || other.removesDirectory == removesDirectory)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,worktreeId,dirty,unpushedCommitCount,runningSessionCount,removesDirectory); + +@override +String toString() { + return 'WorktreeArchivePreviewDto(worktreeId: $worktreeId, dirty: $dirty, unpushedCommitCount: $unpushedCommitCount, runningSessionCount: $runningSessionCount, removesDirectory: $removesDirectory)'; +} + + +} + +/// @nodoc +abstract mixin class _$WorktreeArchivePreviewDtoCopyWith<$Res> implements $WorktreeArchivePreviewDtoCopyWith<$Res> { + factory _$WorktreeArchivePreviewDtoCopyWith(_WorktreeArchivePreviewDto value, $Res Function(_WorktreeArchivePreviewDto) _then) = __$WorktreeArchivePreviewDtoCopyWithImpl; +@override @useResult +$Res call({ + String worktreeId, bool dirty, int unpushedCommitCount, int runningSessionCount, bool removesDirectory +}); + + + + +} +/// @nodoc +class __$WorktreeArchivePreviewDtoCopyWithImpl<$Res> + implements _$WorktreeArchivePreviewDtoCopyWith<$Res> { + __$WorktreeArchivePreviewDtoCopyWithImpl(this._self, this._then); + + final _WorktreeArchivePreviewDto _self; + final $Res Function(_WorktreeArchivePreviewDto) _then; + +/// Create a copy of WorktreeArchivePreviewDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? worktreeId = null,Object? dirty = null,Object? unpushedCommitCount = null,Object? runningSessionCount = null,Object? removesDirectory = null,}) { + return _then(_WorktreeArchivePreviewDto( +worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String,dirty: null == dirty ? _self.dirty : dirty // ignore: cast_nullable_to_non_nullable +as bool,unpushedCommitCount: null == unpushedCommitCount ? _self.unpushedCommitCount : unpushedCommitCount // ignore: cast_nullable_to_non_nullable +as int,runningSessionCount: null == runningSessionCount ? _self.runningSessionCount : runningSessionCount // ignore: cast_nullable_to_non_nullable +as int,removesDirectory: null == removesDirectory ? _self.removesDirectory : removesDirectory // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + + +/// @nodoc +mixin _$DirectorySuggestionDto { + + String get path; String get name; +/// Create a copy of DirectorySuggestionDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$DirectorySuggestionDtoCopyWith get copyWith => _$DirectorySuggestionDtoCopyWithImpl(this as DirectorySuggestionDto, _$identity); + + /// Serializes this DirectorySuggestionDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is DirectorySuggestionDto&&(identical(other.path, path) || other.path == path)&&(identical(other.name, name) || other.name == name)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,path,name); + +@override +String toString() { + return 'DirectorySuggestionDto(path: $path, name: $name)'; +} + + +} + +/// @nodoc +abstract mixin class $DirectorySuggestionDtoCopyWith<$Res> { + factory $DirectorySuggestionDtoCopyWith(DirectorySuggestionDto value, $Res Function(DirectorySuggestionDto) _then) = _$DirectorySuggestionDtoCopyWithImpl; +@useResult +$Res call({ + String path, String name +}); + + + + +} +/// @nodoc +class _$DirectorySuggestionDtoCopyWithImpl<$Res> + implements $DirectorySuggestionDtoCopyWith<$Res> { + _$DirectorySuggestionDtoCopyWithImpl(this._self, this._then); + + final DirectorySuggestionDto _self; + final $Res Function(DirectorySuggestionDto) _then; + +/// Create a copy of DirectorySuggestionDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? path = null,Object? name = null,}) { + return _then(_self.copyWith( +path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable +as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String, + )); +} + } -/// Adds pattern-matching-related methods to [WorkspaceDto]. -extension WorkspaceDtoPatterns on WorkspaceDto { +/// Adds pattern-matching-related methods to [DirectorySuggestionDto]. +extension DirectorySuggestionDtoPatterns on DirectorySuggestionDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -92,10 +1208,10 @@ extension WorkspaceDtoPatterns on WorkspaceDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _DirectorySuggestionDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _WorkspaceDto() when $default != null: +case _DirectorySuggestionDto() when $default != null: return $default(_that);case _: return orElse(); @@ -114,10 +1230,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _WorkspaceDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _DirectorySuggestionDto value) $default,){ final _that = this; switch (_that) { -case _WorkspaceDto(): +case _DirectorySuggestionDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -135,10 +1251,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _DirectorySuggestionDto value)? $default,){ final _that = this; switch (_that) { -case _WorkspaceDto() when $default != null: +case _DirectorySuggestionDto() when $default != null: return $default(_that);case _: return null; @@ -156,10 +1272,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, String rootPath, DateTime createdAt)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String path, String name)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _WorkspaceDto() when $default != null: -return $default(_that.id,_that.name,_that.rootPath,_that.createdAt);case _: +case _DirectorySuggestionDto() when $default != null: +return $default(_that.path,_that.name);case _: return orElse(); } @@ -177,10 +1293,10 @@ return $default(_that.id,_that.name,_that.rootPath,_that.createdAt);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String name, String rootPath, DateTime createdAt) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String path, String name) $default,) {final _that = this; switch (_that) { -case _WorkspaceDto(): -return $default(_that.id,_that.name,_that.rootPath,_that.createdAt);case _: +case _DirectorySuggestionDto(): +return $default(_that.path,_that.name);case _: throw StateError('Unexpected subclass'); } @@ -197,10 +1313,10 @@ return $default(_that.id,_that.name,_that.rootPath,_that.createdAt);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, String rootPath, DateTime createdAt)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String path, String name)? $default,) {final _that = this; switch (_that) { -case _WorkspaceDto() when $default != null: -return $default(_that.id,_that.name,_that.rootPath,_that.createdAt);case _: +case _DirectorySuggestionDto() when $default != null: +return $default(_that.path,_that.name);case _: return null; } @@ -211,49 +1327,47 @@ return $default(_that.id,_that.name,_that.rootPath,_that.createdAt);case _: /// @nodoc @JsonSerializable() -class _WorkspaceDto implements WorkspaceDto { - const _WorkspaceDto({required this.id, required this.name, required this.rootPath, required this.createdAt}); - factory _WorkspaceDto.fromJson(Map json) => _$WorkspaceDtoFromJson(json); +class _DirectorySuggestionDto implements DirectorySuggestionDto { + const _DirectorySuggestionDto({required this.path, required this.name}); + factory _DirectorySuggestionDto.fromJson(Map json) => _$DirectorySuggestionDtoFromJson(json); -@override final String id; +@override final String path; @override final String name; -@override final String rootPath; -@override final DateTime createdAt; -/// Create a copy of WorkspaceDto +/// Create a copy of DirectorySuggestionDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$WorkspaceDtoCopyWith<_WorkspaceDto> get copyWith => __$WorkspaceDtoCopyWithImpl<_WorkspaceDto>(this, _$identity); +_$DirectorySuggestionDtoCopyWith<_DirectorySuggestionDto> get copyWith => __$DirectorySuggestionDtoCopyWithImpl<_DirectorySuggestionDto>(this, _$identity); @override Map toJson() { - return _$WorkspaceDtoToJson(this, ); + return _$DirectorySuggestionDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceDto&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.rootPath, rootPath) || other.rootPath == rootPath)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _DirectorySuggestionDto&&(identical(other.path, path) || other.path == path)&&(identical(other.name, name) || other.name == name)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,name,rootPath,createdAt); +int get hashCode => Object.hash(runtimeType,path,name); @override String toString() { - return 'WorkspaceDto(id: $id, name: $name, rootPath: $rootPath, createdAt: $createdAt)'; + return 'DirectorySuggestionDto(path: $path, name: $name)'; } } /// @nodoc -abstract mixin class _$WorkspaceDtoCopyWith<$Res> implements $WorkspaceDtoCopyWith<$Res> { - factory _$WorkspaceDtoCopyWith(_WorkspaceDto value, $Res Function(_WorkspaceDto) _then) = __$WorkspaceDtoCopyWithImpl; +abstract mixin class _$DirectorySuggestionDtoCopyWith<$Res> implements $DirectorySuggestionDtoCopyWith<$Res> { + factory _$DirectorySuggestionDtoCopyWith(_DirectorySuggestionDto value, $Res Function(_DirectorySuggestionDto) _then) = __$DirectorySuggestionDtoCopyWithImpl; @override @useResult $Res call({ - String id, String name, String rootPath, DateTime createdAt + String path, String name }); @@ -261,22 +1375,289 @@ $Res call({ } /// @nodoc -class __$WorkspaceDtoCopyWithImpl<$Res> - implements _$WorkspaceDtoCopyWith<$Res> { - __$WorkspaceDtoCopyWithImpl(this._self, this._then); +class __$DirectorySuggestionDtoCopyWithImpl<$Res> + implements _$DirectorySuggestionDtoCopyWith<$Res> { + __$DirectorySuggestionDtoCopyWithImpl(this._self, this._then); - final _WorkspaceDto _self; - final $Res Function(_WorkspaceDto) _then; + final _DirectorySuggestionDto _self; + final $Res Function(_DirectorySuggestionDto) _then; -/// Create a copy of WorkspaceDto +/// Create a copy of DirectorySuggestionDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? rootPath = null,Object? createdAt = null,}) { - return _then(_WorkspaceDto( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +@override @pragma('vm:prefer-inline') $Res call({Object? path = null,Object? name = null,}) { + return _then(_DirectorySuggestionDto( +path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable -as String,rootPath: null == rootPath ? _self.rootPath : rootPath // ignore: cast_nullable_to_non_nullable -as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable -as DateTime, +as String, + )); +} + + +} + + +/// @nodoc +mixin _$GitBranchDto { + + String get name; bool get current; bool get checkedOut; +/// Create a copy of GitBranchDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$GitBranchDtoCopyWith get copyWith => _$GitBranchDtoCopyWithImpl(this as GitBranchDto, _$identity); + + /// Serializes this GitBranchDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is GitBranchDto&&(identical(other.name, name) || other.name == name)&&(identical(other.current, current) || other.current == current)&&(identical(other.checkedOut, checkedOut) || other.checkedOut == checkedOut)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,current,checkedOut); + +@override +String toString() { + return 'GitBranchDto(name: $name, current: $current, checkedOut: $checkedOut)'; +} + + +} + +/// @nodoc +abstract mixin class $GitBranchDtoCopyWith<$Res> { + factory $GitBranchDtoCopyWith(GitBranchDto value, $Res Function(GitBranchDto) _then) = _$GitBranchDtoCopyWithImpl; +@useResult +$Res call({ + String name, bool current, bool checkedOut +}); + + + + +} +/// @nodoc +class _$GitBranchDtoCopyWithImpl<$Res> + implements $GitBranchDtoCopyWith<$Res> { + _$GitBranchDtoCopyWithImpl(this._self, this._then); + + final GitBranchDto _self; + final $Res Function(GitBranchDto) _then; + +/// Create a copy of GitBranchDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? current = null,Object? checkedOut = null,}) { + return _then(_self.copyWith( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as bool,checkedOut: null == checkedOut ? _self.checkedOut : checkedOut // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [GitBranchDto]. +extension GitBranchDtoPatterns on GitBranchDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _GitBranchDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _GitBranchDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _GitBranchDto value) $default,){ +final _that = this; +switch (_that) { +case _GitBranchDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GitBranchDto value)? $default,){ +final _that = this; +switch (_that) { +case _GitBranchDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String name, bool current, bool checkedOut)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _GitBranchDto() when $default != null: +return $default(_that.name,_that.current,_that.checkedOut);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String name, bool current, bool checkedOut) $default,) {final _that = this; +switch (_that) { +case _GitBranchDto(): +return $default(_that.name,_that.current,_that.checkedOut);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String name, bool current, bool checkedOut)? $default,) {final _that = this; +switch (_that) { +case _GitBranchDto() when $default != null: +return $default(_that.name,_that.current,_that.checkedOut);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _GitBranchDto implements GitBranchDto { + const _GitBranchDto({required this.name, required this.current, required this.checkedOut}); + factory _GitBranchDto.fromJson(Map json) => _$GitBranchDtoFromJson(json); + +@override final String name; +@override final bool current; +@override final bool checkedOut; + +/// Create a copy of GitBranchDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$GitBranchDtoCopyWith<_GitBranchDto> get copyWith => __$GitBranchDtoCopyWithImpl<_GitBranchDto>(this, _$identity); + +@override +Map toJson() { + return _$GitBranchDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _GitBranchDto&&(identical(other.name, name) || other.name == name)&&(identical(other.current, current) || other.current == current)&&(identical(other.checkedOut, checkedOut) || other.checkedOut == checkedOut)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,name,current,checkedOut); + +@override +String toString() { + return 'GitBranchDto(name: $name, current: $current, checkedOut: $checkedOut)'; +} + + +} + +/// @nodoc +abstract mixin class _$GitBranchDtoCopyWith<$Res> implements $GitBranchDtoCopyWith<$Res> { + factory _$GitBranchDtoCopyWith(_GitBranchDto value, $Res Function(_GitBranchDto) _then) = __$GitBranchDtoCopyWithImpl; +@override @useResult +$Res call({ + String name, bool current, bool checkedOut +}); + + + + +} +/// @nodoc +class __$GitBranchDtoCopyWithImpl<$Res> + implements _$GitBranchDtoCopyWith<$Res> { + __$GitBranchDtoCopyWithImpl(this._self, this._then); + + final _GitBranchDto _self; + final $Res Function(_GitBranchDto) _then; + +/// Create a copy of GitBranchDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? current = null,Object? checkedOut = null,}) { + return _then(_GitBranchDto( +name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable +as String,current: null == current ? _self.current : current // ignore: cast_nullable_to_non_nullable +as bool,checkedOut: null == checkedOut ? _self.checkedOut : checkedOut // ignore: cast_nullable_to_non_nullable +as bool, )); } @@ -287,7 +1668,7 @@ as DateTime, /// @nodoc mixin _$AgentDto { - String get id; String get workspaceId; String get title; String get providerConnectionId; String get model; AgentStatus get status; PermissionMode get permissionMode; DateTime get createdAt; DateTime get updatedAt; String get reasoningEffort; String? get activeTurnId; String? get lastError; + String get id; String get worktreeId; String get title; String get providerConnectionId; String get model; AgentStatus get status; PermissionMode get permissionMode; DateTime get createdAt; DateTime get updatedAt; String get reasoningEffort; String? get activeTurnId; String? get lastError; /// Create a copy of AgentDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -300,16 +1681,16 @@ $AgentDtoCopyWith get copyWith => _$AgentDtoCopyWithImpl(thi @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.status, status) || other.status == status)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.activeTurnId, activeTurnId) || other.activeTurnId == activeTurnId)&&(identical(other.lastError, lastError) || other.lastError == lastError)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentDto&&(identical(other.id, id) || other.id == id)&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.status, status) || other.status == status)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.activeTurnId, activeTurnId) || other.activeTurnId == activeTurnId)&&(identical(other.lastError, lastError) || other.lastError == lastError)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,workspaceId,title,providerConnectionId,model,status,permissionMode,createdAt,updatedAt,reasoningEffort,activeTurnId,lastError); +int get hashCode => Object.hash(runtimeType,id,worktreeId,title,providerConnectionId,model,status,permissionMode,createdAt,updatedAt,reasoningEffort,activeTurnId,lastError); @override String toString() { - return 'AgentDto(id: $id, workspaceId: $workspaceId, title: $title, providerConnectionId: $providerConnectionId, model: $model, status: $status, permissionMode: $permissionMode, createdAt: $createdAt, updatedAt: $updatedAt, reasoningEffort: $reasoningEffort, activeTurnId: $activeTurnId, lastError: $lastError)'; + return 'AgentDto(id: $id, worktreeId: $worktreeId, title: $title, providerConnectionId: $providerConnectionId, model: $model, status: $status, permissionMode: $permissionMode, createdAt: $createdAt, updatedAt: $updatedAt, reasoningEffort: $reasoningEffort, activeTurnId: $activeTurnId, lastError: $lastError)'; } @@ -320,7 +1701,7 @@ abstract mixin class $AgentDtoCopyWith<$Res> { factory $AgentDtoCopyWith(AgentDto value, $Res Function(AgentDto) _then) = _$AgentDtoCopyWithImpl; @useResult $Res call({ - String id, String workspaceId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError + String id, String worktreeId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError }); @@ -337,10 +1718,10 @@ class _$AgentDtoCopyWithImpl<$Res> /// Create a copy of AgentDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? workspaceId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? status = null,Object? permissionMode = null,Object? createdAt = null,Object? updatedAt = null,Object? reasoningEffort = null,Object? activeTurnId = freezed,Object? lastError = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? worktreeId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? status = null,Object? permissionMode = null,Object? createdAt = null,Object? updatedAt = null,Object? reasoningEffort = null,Object? activeTurnId = freezed,Object? lastError = freezed,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable @@ -436,10 +1817,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String workspaceId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String worktreeId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _AgentDto() when $default != null: -return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionId,_that.model,_that.status,_that.permissionMode,_that.createdAt,_that.updatedAt,_that.reasoningEffort,_that.activeTurnId,_that.lastError);case _: +return $default(_that.id,_that.worktreeId,_that.title,_that.providerConnectionId,_that.model,_that.status,_that.permissionMode,_that.createdAt,_that.updatedAt,_that.reasoningEffort,_that.activeTurnId,_that.lastError);case _: return orElse(); } @@ -457,10 +1838,10 @@ return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionI /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String workspaceId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String worktreeId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError) $default,) {final _that = this; switch (_that) { case _AgentDto(): -return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionId,_that.model,_that.status,_that.permissionMode,_that.createdAt,_that.updatedAt,_that.reasoningEffort,_that.activeTurnId,_that.lastError);case _: +return $default(_that.id,_that.worktreeId,_that.title,_that.providerConnectionId,_that.model,_that.status,_that.permissionMode,_that.createdAt,_that.updatedAt,_that.reasoningEffort,_that.activeTurnId,_that.lastError);case _: throw StateError('Unexpected subclass'); } @@ -477,10 +1858,10 @@ return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionI /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String workspaceId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String worktreeId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError)? $default,) {final _that = this; switch (_that) { case _AgentDto() when $default != null: -return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionId,_that.model,_that.status,_that.permissionMode,_that.createdAt,_that.updatedAt,_that.reasoningEffort,_that.activeTurnId,_that.lastError);case _: +return $default(_that.id,_that.worktreeId,_that.title,_that.providerConnectionId,_that.model,_that.status,_that.permissionMode,_that.createdAt,_that.updatedAt,_that.reasoningEffort,_that.activeTurnId,_that.lastError);case _: return null; } @@ -492,11 +1873,11 @@ return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionI @JsonSerializable() class _AgentDto implements AgentDto { - const _AgentDto({required this.id, required this.workspaceId, required this.title, required this.providerConnectionId, required this.model, required this.status, required this.permissionMode, required this.createdAt, required this.updatedAt, this.reasoningEffort = 'medium', this.activeTurnId, this.lastError}); + const _AgentDto({required this.id, required this.worktreeId, required this.title, required this.providerConnectionId, required this.model, required this.status, required this.permissionMode, required this.createdAt, required this.updatedAt, this.reasoningEffort = 'medium', this.activeTurnId, this.lastError}); factory _AgentDto.fromJson(Map json) => _$AgentDtoFromJson(json); @override final String id; -@override final String workspaceId; +@override final String worktreeId; @override final String title; @override final String providerConnectionId; @override final String model; @@ -521,16 +1902,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.status, status) || other.status == status)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.activeTurnId, activeTurnId) || other.activeTurnId == activeTurnId)&&(identical(other.lastError, lastError) || other.lastError == lastError)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentDto&&(identical(other.id, id) || other.id == id)&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.status, status) || other.status == status)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.activeTurnId, activeTurnId) || other.activeTurnId == activeTurnId)&&(identical(other.lastError, lastError) || other.lastError == lastError)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,workspaceId,title,providerConnectionId,model,status,permissionMode,createdAt,updatedAt,reasoningEffort,activeTurnId,lastError); +int get hashCode => Object.hash(runtimeType,id,worktreeId,title,providerConnectionId,model,status,permissionMode,createdAt,updatedAt,reasoningEffort,activeTurnId,lastError); @override String toString() { - return 'AgentDto(id: $id, workspaceId: $workspaceId, title: $title, providerConnectionId: $providerConnectionId, model: $model, status: $status, permissionMode: $permissionMode, createdAt: $createdAt, updatedAt: $updatedAt, reasoningEffort: $reasoningEffort, activeTurnId: $activeTurnId, lastError: $lastError)'; + return 'AgentDto(id: $id, worktreeId: $worktreeId, title: $title, providerConnectionId: $providerConnectionId, model: $model, status: $status, permissionMode: $permissionMode, createdAt: $createdAt, updatedAt: $updatedAt, reasoningEffort: $reasoningEffort, activeTurnId: $activeTurnId, lastError: $lastError)'; } @@ -541,7 +1922,7 @@ abstract mixin class _$AgentDtoCopyWith<$Res> implements $AgentDtoCopyWith<$Res> factory _$AgentDtoCopyWith(_AgentDto value, $Res Function(_AgentDto) _then) = __$AgentDtoCopyWithImpl; @override @useResult $Res call({ - String id, String workspaceId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError + String id, String worktreeId, String title, String providerConnectionId, String model, AgentStatus status, PermissionMode permissionMode, DateTime createdAt, DateTime updatedAt, String reasoningEffort, String? activeTurnId, String? lastError }); @@ -558,10 +1939,10 @@ class __$AgentDtoCopyWithImpl<$Res> /// Create a copy of AgentDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? workspaceId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? status = null,Object? permissionMode = null,Object? createdAt = null,Object? updatedAt = null,Object? reasoningEffort = null,Object? activeTurnId = freezed,Object? lastError = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? worktreeId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? status = null,Object? permissionMode = null,Object? createdAt = null,Object? updatedAt = null,Object? reasoningEffort = null,Object? activeTurnId = freezed,Object? lastError = freezed,}) { return _then(_AgentDto( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable diff --git a/packages/coder_protocol/lib/src/models.g.dart b/packages/coder_protocol/lib/src/models.g.dart index 7fb48d2..0d73961 100644 --- a/packages/coder_protocol/lib/src/models.g.dart +++ b/packages/coder_protocol/lib/src/models.g.dart @@ -11,6 +11,7 @@ _WorkspaceDto _$WorkspaceDtoFromJson(Map json) => id: json['id'] as String, name: json['name'] as String, rootPath: json['rootPath'] as String, + kind: $enumDecode(_$WorkspaceKindEnumMap, json['kind']), createdAt: DateTime.parse(json['createdAt'] as String), ); @@ -19,12 +20,116 @@ Map _$WorkspaceDtoToJson(_WorkspaceDto instance) => 'id': instance.id, 'name': instance.name, 'rootPath': instance.rootPath, + 'kind': _$WorkspaceKindEnumMap[instance.kind]!, 'createdAt': instance.createdAt.toIso8601String(), }; -_AgentDto _$AgentDtoFromJson(Map json) => _AgentDto( +const _$WorkspaceKindEnumMap = { + WorkspaceKind.git: 'git', + WorkspaceKind.directory: 'directory', +}; + +_WorktreeDto _$WorktreeDtoFromJson(Map json) => _WorktreeDto( id: json['id'] as String, workspaceId: json['workspaceId'] as String, + name: json['name'] as String, + path: json['path'] as String, + kind: $enumDecode(_$WorktreeKindEnumMap, json['kind']), + isCoderOwned: json['isCoderOwned'] as bool, + createdAt: DateTime.parse(json['createdAt'] as String), + branch: json['branch'] as String?, + head: json['head'] as String?, + archivedAt: json['archivedAt'] == null + ? null + : DateTime.parse(json['archivedAt'] as String), +); + +Map _$WorktreeDtoToJson(_WorktreeDto instance) => + { + 'id': instance.id, + 'workspaceId': instance.workspaceId, + 'name': instance.name, + 'path': instance.path, + 'kind': _$WorktreeKindEnumMap[instance.kind]!, + 'isCoderOwned': instance.isCoderOwned, + 'createdAt': instance.createdAt.toIso8601String(), + 'branch': instance.branch, + 'head': instance.head, + 'archivedAt': instance.archivedAt?.toIso8601String(), + }; + +const _$WorktreeKindEnumMap = { + WorktreeKind.checkout: 'checkout', + WorktreeKind.managed: 'managed', + WorktreeKind.external: 'external', + WorktreeKind.directory: 'directory', +}; + +_WorkspaceCatalogDto _$WorkspaceCatalogDtoFromJson(Map json) => + _WorkspaceCatalogDto( + workspaces: (json['workspaces'] as List) + .map((e) => WorkspaceDto.fromJson(e as Map)) + .toList(), + worktrees: (json['worktrees'] as List) + .map((e) => WorktreeDto.fromJson(e as Map)) + .toList(), + ); + +Map _$WorkspaceCatalogDtoToJson( + _WorkspaceCatalogDto instance, +) => { + 'workspaces': instance.workspaces, + 'worktrees': instance.worktrees, +}; + +_WorktreeArchivePreviewDto _$WorktreeArchivePreviewDtoFromJson( + Map json, +) => _WorktreeArchivePreviewDto( + worktreeId: json['worktreeId'] as String, + dirty: json['dirty'] as bool, + unpushedCommitCount: (json['unpushedCommitCount'] as num).toInt(), + runningSessionCount: (json['runningSessionCount'] as num).toInt(), + removesDirectory: json['removesDirectory'] as bool, +); + +Map _$WorktreeArchivePreviewDtoToJson( + _WorktreeArchivePreviewDto instance, +) => { + 'worktreeId': instance.worktreeId, + 'dirty': instance.dirty, + 'unpushedCommitCount': instance.unpushedCommitCount, + 'runningSessionCount': instance.runningSessionCount, + 'removesDirectory': instance.removesDirectory, +}; + +_DirectorySuggestionDto _$DirectorySuggestionDtoFromJson( + Map json, +) => _DirectorySuggestionDto( + path: json['path'] as String, + name: json['name'] as String, +); + +Map _$DirectorySuggestionDtoToJson( + _DirectorySuggestionDto instance, +) => {'path': instance.path, 'name': instance.name}; + +_GitBranchDto _$GitBranchDtoFromJson(Map json) => + _GitBranchDto( + name: json['name'] as String, + current: json['current'] as bool, + checkedOut: json['checkedOut'] as bool, + ); + +Map _$GitBranchDtoToJson(_GitBranchDto instance) => + { + 'name': instance.name, + 'current': instance.current, + 'checkedOut': instance.checkedOut, + }; + +_AgentDto _$AgentDtoFromJson(Map json) => _AgentDto( + id: json['id'] as String, + worktreeId: json['worktreeId'] as String, title: json['title'] as String, providerConnectionId: json['providerConnectionId'] as String, model: json['model'] as String, @@ -39,7 +144,7 @@ _AgentDto _$AgentDtoFromJson(Map json) => _AgentDto( Map _$AgentDtoToJson(_AgentDto instance) => { 'id': instance.id, - 'workspaceId': instance.workspaceId, + 'worktreeId': instance.worktreeId, 'title': instance.title, 'providerConnectionId': instance.providerConnectionId, 'model': instance.model, diff --git a/packages/coder_protocol/lib/src/protocol.dart b/packages/coder_protocol/lib/src/protocol.dart index 47cbf1d..1386982 100644 --- a/packages/coder_protocol/lib/src/protocol.dart +++ b/packages/coder_protocol/lib/src/protocol.dart @@ -1,19 +1,40 @@ import 'dart:convert'; /// The coderProtocolVersion public API member. -const int coderProtocolVersion = 3; +const int coderProtocolVersion = 5; /// Public API exposed by this library. abstract final class RpcMethod { /// The hello public API member. static const String hello = 'hello'; - /// The workspaceList public API member. - static const String workspaceList = 'workspace.list'; + /// Returns an atomic workspace and worktree catalog. + static const String workspaceCatalog = 'workspace.catalog'; /// The workspaceRegister public API member. static const String workspaceRegister = 'workspace.register'; + /// Refreshes Git checkout and worktree metadata. + static const String workspaceRefresh = 'workspace.refresh'; + + /// Removes a workspace registration without deleting its source checkout. + static const String workspaceUnregister = 'workspace.unregister'; + + /// Searches directories on the daemon host. + static const String directorySuggest = 'directory.suggest'; + + /// Lists local branches for a Git workspace. + static const String gitBranchesList = 'git.branches.list'; + + /// Creates a managed Git worktree. + static const String worktreeCreate = 'worktree.create'; + + /// Returns archive risks without mutating a worktree. + static const String worktreeArchivePreview = 'worktree.archive.preview'; + + /// Archives a worktree and removes it only when Coder owns it. + static const String worktreeArchive = 'worktree.archive'; + /// The agentList public API member. static const String agentList = 'agent.list'; diff --git a/packages/coder_protocol/lib/src/rpc_models.dart b/packages/coder_protocol/lib/src/rpc_models.dart index 545d928..bc56de0 100644 --- a/packages/coder_protocol/lib/src/rpc_models.dart +++ b/packages/coder_protocol/lib/src/rpc_models.dart @@ -25,7 +25,8 @@ abstract class HelloParamsDto with _$HelloParamsDto { abstract class WorkspaceRegisterParamsDto with _$WorkspaceRegisterParamsDto { /// The WorkspaceRegisterParamsDto public API member. const factory WorkspaceRegisterParamsDto({ - required String id, + required String workspaceId, + required String checkoutId, required String rootPath, required String name, }) = _WorkspaceRegisterParamsDto; @@ -35,11 +36,92 @@ abstract class WorkspaceRegisterParamsDto with _$WorkspaceRegisterParamsDto { _$WorkspaceRegisterParamsDtoFromJson(json); } +@freezed +/// Selects one registered workspace for refresh or removal. +abstract class WorkspaceIdParamsDto with _$WorkspaceIdParamsDto { + /// Creates workspace identifier parameters. + const factory WorkspaceIdParamsDto({required String workspaceId}) = + _WorkspaceIdParamsDto; + + /// Decodes workspace identifier parameters. + factory WorkspaceIdParamsDto.fromJson(Map json) => + _$WorkspaceIdParamsDtoFromJson(json); +} + +@freezed +/// Searches directories on the daemon host. +abstract class DirectorySuggestParamsDto with _$DirectorySuggestParamsDto { + /// Creates directory search parameters. + const factory DirectorySuggestParamsDto({ + required String query, + @Default(30) int limit, + }) = _DirectorySuggestParamsDto; + + /// Decodes directory search parameters. + factory DirectorySuggestParamsDto.fromJson(Map json) => + _$DirectorySuggestParamsDtoFromJson(json); +} + +@freezed +/// Requests local branches for one Git workspace. +abstract class GitBranchesListParamsDto with _$GitBranchesListParamsDto { + /// Creates branch-list parameters. + const factory GitBranchesListParamsDto({required String workspaceId}) = + _GitBranchesListParamsDto; + + /// Decodes branch-list parameters. + factory GitBranchesListParamsDto.fromJson(Map json) => + _$GitBranchesListParamsDtoFromJson(json); +} + +@freezed +/// Creates a managed Git worktree from a new or existing local branch. +abstract class WorktreeCreateParamsDto with _$WorktreeCreateParamsDto { + /// Creates managed-worktree parameters. + const factory WorktreeCreateParamsDto({ + required String id, + required String workspaceId, + required WorktreeCreateMode mode, + required String branchName, + String? baseBranch, + }) = _WorktreeCreateParamsDto; + + /// Decodes managed-worktree parameters. + factory WorktreeCreateParamsDto.fromJson(Map json) => + _$WorktreeCreateParamsDtoFromJson(json); +} + +@freezed +/// Identifies one worktree. +abstract class WorktreeIdParamsDto with _$WorktreeIdParamsDto { + /// Creates worktree identifier parameters. + const factory WorktreeIdParamsDto({required String worktreeId}) = + _WorktreeIdParamsDto; + + /// Decodes worktree identifier parameters. + factory WorktreeIdParamsDto.fromJson(Map json) => + _$WorktreeIdParamsDtoFromJson(json); +} + +@freezed +/// Confirms archive risks for one worktree. +abstract class WorktreeArchiveParamsDto with _$WorktreeArchiveParamsDto { + /// Creates worktree archive parameters. + const factory WorktreeArchiveParamsDto({ + required String worktreeId, + required bool force, + }) = _WorktreeArchiveParamsDto; + + /// Decodes worktree archive parameters. + factory WorktreeArchiveParamsDto.fromJson(Map json) => + _$WorktreeArchiveParamsDtoFromJson(json); +} + @freezed /// AgentListParamsDto defines a public contract. abstract class AgentListParamsDto with _$AgentListParamsDto { /// The AgentListParamsDto public API member. - const factory AgentListParamsDto({String? workspaceId}) = _AgentListParamsDto; + const factory AgentListParamsDto({String? worktreeId}) = _AgentListParamsDto; /// Creates a [AgentListParamsDto]. factory AgentListParamsDto.fromJson(Map json) => @@ -52,7 +134,7 @@ abstract class AgentCreateParamsDto with _$AgentCreateParamsDto { /// The AgentCreateParamsDto public API member. const factory AgentCreateParamsDto({ required String id, - required String workspaceId, + required String worktreeId, required String title, required String providerConnectionId, required String model, @@ -285,28 +367,96 @@ abstract class TimelineSubscribeParamsDto with _$TimelineSubscribeParamsDto { } @freezed -/// WorkspaceListResultDto defines a public contract. -abstract class WorkspaceListResultDto with _$WorkspaceListResultDto { - /// The WorkspaceListResultDto public API member. - const factory WorkspaceListResultDto({ - required List workspaces, - }) = _WorkspaceListResultDto; +/// Result containing an atomic workspace catalog. +abstract class WorkspaceCatalogResultDto with _$WorkspaceCatalogResultDto { + /// Creates a workspace catalog result. + const factory WorkspaceCatalogResultDto({ + required WorkspaceCatalogDto catalog, + }) = _WorkspaceCatalogResultDto; - /// Creates a [WorkspaceListResultDto]. - factory WorkspaceListResultDto.fromJson(Map json) => - _$WorkspaceListResultDtoFromJson(json); + /// Decodes a workspace catalog result. + factory WorkspaceCatalogResultDto.fromJson(Map json) => + _$WorkspaceCatalogResultDtoFromJson(json); } @freezed -/// WorkspaceResultDto defines a public contract. -abstract class WorkspaceResultDto with _$WorkspaceResultDto { - /// The WorkspaceResultDto public API member. - const factory WorkspaceResultDto({required WorkspaceDto workspace}) = - _WorkspaceResultDto; +/// Result of registering one workspace and its discovered checkouts. +abstract class WorkspaceRegisterResultDto with _$WorkspaceRegisterResultDto { + /// Creates a workspace registration result. + const factory WorkspaceRegisterResultDto({ + required WorkspaceDto workspace, + required List worktrees, + }) = _WorkspaceRegisterResultDto; - /// Creates a [WorkspaceResultDto]. - factory WorkspaceResultDto.fromJson(Map json) => - _$WorkspaceResultDtoFromJson(json); + /// Decodes a workspace registration result. + factory WorkspaceRegisterResultDto.fromJson(Map json) => + _$WorkspaceRegisterResultDtoFromJson(json); +} + +@freezed +/// Boolean result for unregistering a workspace. +abstract class WorkspaceUnregisterResultDto + with _$WorkspaceUnregisterResultDto { + /// Creates an unregister result. + const factory WorkspaceUnregisterResultDto({required bool unregistered}) = + _WorkspaceUnregisterResultDto; + + /// Decodes an unregister result. + factory WorkspaceUnregisterResultDto.fromJson(Map json) => + _$WorkspaceUnregisterResultDtoFromJson(json); +} + +@freezed +/// Result of daemon-side directory search. +abstract class DirectorySuggestResultDto with _$DirectorySuggestResultDto { + /// Creates directory suggestions. + const factory DirectorySuggestResultDto({ + required List suggestions, + }) = _DirectorySuggestResultDto; + + /// Decodes directory suggestions. + factory DirectorySuggestResultDto.fromJson(Map json) => + _$DirectorySuggestResultDtoFromJson(json); +} + +@freezed +/// Result containing local Git branches. +abstract class GitBranchesListResultDto with _$GitBranchesListResultDto { + /// Creates a branch-list result. + const factory GitBranchesListResultDto({ + required List branches, + }) = _GitBranchesListResultDto; + + /// Decodes a branch-list result. + factory GitBranchesListResultDto.fromJson(Map json) => + _$GitBranchesListResultDtoFromJson(json); +} + +@freezed +/// Result containing one worktree. +abstract class WorktreeResultDto with _$WorktreeResultDto { + /// Creates a worktree result. + const factory WorktreeResultDto({required WorktreeDto worktree}) = + _WorktreeResultDto; + + /// Decodes a worktree result. + factory WorktreeResultDto.fromJson(Map json) => + _$WorktreeResultDtoFromJson(json); +} + +@freezed +/// Result containing archive risk information. +abstract class WorktreeArchivePreviewResultDto + with _$WorktreeArchivePreviewResultDto { + /// Creates an archive preview result. + const factory WorktreeArchivePreviewResultDto({ + required WorktreeArchivePreviewDto preview, + }) = _WorktreeArchivePreviewResultDto; + + /// Decodes an archive preview result. + factory WorktreeArchivePreviewResultDto.fromJson( + Map json, + ) => _$WorktreeArchivePreviewResultDtoFromJson(json); } @freezed diff --git a/packages/coder_protocol/lib/src/rpc_models.freezed.dart b/packages/coder_protocol/lib/src/rpc_models.freezed.dart index 4f73524..970d66a 100644 --- a/packages/coder_protocol/lib/src/rpc_models.freezed.dart +++ b/packages/coder_protocol/lib/src/rpc_models.freezed.dart @@ -293,7 +293,7 @@ as Map, /// @nodoc mixin _$WorkspaceRegisterParamsDto { - String get id; String get rootPath; String get name; + String get workspaceId; String get checkoutId; String get rootPath; String get name; /// Create a copy of WorkspaceRegisterParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -306,16 +306,16 @@ $WorkspaceRegisterParamsDtoCopyWith get copyWith => @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceRegisterParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.rootPath, rootPath) || other.rootPath == rootPath)&&(identical(other.name, name) || other.name == name)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceRegisterParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.checkoutId, checkoutId) || other.checkoutId == checkoutId)&&(identical(other.rootPath, rootPath) || other.rootPath == rootPath)&&(identical(other.name, name) || other.name == name)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,rootPath,name); +int get hashCode => Object.hash(runtimeType,workspaceId,checkoutId,rootPath,name); @override String toString() { - return 'WorkspaceRegisterParamsDto(id: $id, rootPath: $rootPath, name: $name)'; + return 'WorkspaceRegisterParamsDto(workspaceId: $workspaceId, checkoutId: $checkoutId, rootPath: $rootPath, name: $name)'; } @@ -326,7 +326,7 @@ abstract mixin class $WorkspaceRegisterParamsDtoCopyWith<$Res> { factory $WorkspaceRegisterParamsDtoCopyWith(WorkspaceRegisterParamsDto value, $Res Function(WorkspaceRegisterParamsDto) _then) = _$WorkspaceRegisterParamsDtoCopyWithImpl; @useResult $Res call({ - String id, String rootPath, String name + String workspaceId, String checkoutId, String rootPath, String name }); @@ -343,9 +343,10 @@ class _$WorkspaceRegisterParamsDtoCopyWithImpl<$Res> /// Create a copy of WorkspaceRegisterParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? rootPath = null,Object? name = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? workspaceId = null,Object? checkoutId = null,Object? rootPath = null,Object? name = null,}) { return _then(_self.copyWith( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,checkoutId: null == checkoutId ? _self.checkoutId : checkoutId // ignore: cast_nullable_to_non_nullable as String,rootPath: null == rootPath ? _self.rootPath : rootPath // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String, @@ -433,10 +434,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String rootPath, String name)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String workspaceId, String checkoutId, String rootPath, String name)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _WorkspaceRegisterParamsDto() when $default != null: -return $default(_that.id,_that.rootPath,_that.name);case _: +return $default(_that.workspaceId,_that.checkoutId,_that.rootPath,_that.name);case _: return orElse(); } @@ -454,10 +455,10 @@ return $default(_that.id,_that.rootPath,_that.name);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String rootPath, String name) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String workspaceId, String checkoutId, String rootPath, String name) $default,) {final _that = this; switch (_that) { case _WorkspaceRegisterParamsDto(): -return $default(_that.id,_that.rootPath,_that.name);case _: +return $default(_that.workspaceId,_that.checkoutId,_that.rootPath,_that.name);case _: throw StateError('Unexpected subclass'); } @@ -474,10 +475,10 @@ return $default(_that.id,_that.rootPath,_that.name);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String rootPath, String name)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String workspaceId, String checkoutId, String rootPath, String name)? $default,) {final _that = this; switch (_that) { case _WorkspaceRegisterParamsDto() when $default != null: -return $default(_that.id,_that.rootPath,_that.name);case _: +return $default(_that.workspaceId,_that.checkoutId,_that.rootPath,_that.name);case _: return null; } @@ -489,10 +490,11 @@ return $default(_that.id,_that.rootPath,_that.name);case _: @JsonSerializable() class _WorkspaceRegisterParamsDto implements WorkspaceRegisterParamsDto { - const _WorkspaceRegisterParamsDto({required this.id, required this.rootPath, required this.name}); + const _WorkspaceRegisterParamsDto({required this.workspaceId, required this.checkoutId, required this.rootPath, required this.name}); factory _WorkspaceRegisterParamsDto.fromJson(Map json) => _$WorkspaceRegisterParamsDtoFromJson(json); -@override final String id; +@override final String workspaceId; +@override final String checkoutId; @override final String rootPath; @override final String name; @@ -509,16 +511,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceRegisterParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.rootPath, rootPath) || other.rootPath == rootPath)&&(identical(other.name, name) || other.name == name)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceRegisterParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.checkoutId, checkoutId) || other.checkoutId == checkoutId)&&(identical(other.rootPath, rootPath) || other.rootPath == rootPath)&&(identical(other.name, name) || other.name == name)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,rootPath,name); +int get hashCode => Object.hash(runtimeType,workspaceId,checkoutId,rootPath,name); @override String toString() { - return 'WorkspaceRegisterParamsDto(id: $id, rootPath: $rootPath, name: $name)'; + return 'WorkspaceRegisterParamsDto(workspaceId: $workspaceId, checkoutId: $checkoutId, rootPath: $rootPath, name: $name)'; } @@ -529,7 +531,7 @@ abstract mixin class _$WorkspaceRegisterParamsDtoCopyWith<$Res> implements $Work factory _$WorkspaceRegisterParamsDtoCopyWith(_WorkspaceRegisterParamsDto value, $Res Function(_WorkspaceRegisterParamsDto) _then) = __$WorkspaceRegisterParamsDtoCopyWithImpl; @override @useResult $Res call({ - String id, String rootPath, String name + String workspaceId, String checkoutId, String rootPath, String name }); @@ -546,9 +548,10 @@ class __$WorkspaceRegisterParamsDtoCopyWithImpl<$Res> /// Create a copy of WorkspaceRegisterParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? rootPath = null,Object? name = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? workspaceId = null,Object? checkoutId = null,Object? rootPath = null,Object? name = null,}) { return _then(_WorkspaceRegisterParamsDto( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,checkoutId: null == checkoutId ? _self.checkoutId : checkoutId // ignore: cast_nullable_to_non_nullable as String,rootPath: null == rootPath ? _self.rootPath : rootPath // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String, @@ -560,22 +563,22 @@ as String, /// @nodoc -mixin _$AgentListParamsDto { +mixin _$WorkspaceIdParamsDto { - String? get workspaceId; -/// Create a copy of AgentListParamsDto + String get workspaceId; +/// Create a copy of WorkspaceIdParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$AgentListParamsDtoCopyWith get copyWith => _$AgentListParamsDtoCopyWithImpl(this as AgentListParamsDto, _$identity); +$WorkspaceIdParamsDtoCopyWith get copyWith => _$WorkspaceIdParamsDtoCopyWithImpl(this as WorkspaceIdParamsDto, _$identity); - /// Serializes this AgentListParamsDto to a JSON map. + /// Serializes this WorkspaceIdParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentListParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceIdParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -584,18 +587,18 @@ int get hashCode => Object.hash(runtimeType,workspaceId); @override String toString() { - return 'AgentListParamsDto(workspaceId: $workspaceId)'; + return 'WorkspaceIdParamsDto(workspaceId: $workspaceId)'; } } /// @nodoc -abstract mixin class $AgentListParamsDtoCopyWith<$Res> { - factory $AgentListParamsDtoCopyWith(AgentListParamsDto value, $Res Function(AgentListParamsDto) _then) = _$AgentListParamsDtoCopyWithImpl; +abstract mixin class $WorkspaceIdParamsDtoCopyWith<$Res> { + factory $WorkspaceIdParamsDtoCopyWith(WorkspaceIdParamsDto value, $Res Function(WorkspaceIdParamsDto) _then) = _$WorkspaceIdParamsDtoCopyWithImpl; @useResult $Res call({ - String? workspaceId + String workspaceId }); @@ -603,27 +606,27 @@ $Res call({ } /// @nodoc -class _$AgentListParamsDtoCopyWithImpl<$Res> - implements $AgentListParamsDtoCopyWith<$Res> { - _$AgentListParamsDtoCopyWithImpl(this._self, this._then); +class _$WorkspaceIdParamsDtoCopyWithImpl<$Res> + implements $WorkspaceIdParamsDtoCopyWith<$Res> { + _$WorkspaceIdParamsDtoCopyWithImpl(this._self, this._then); - final AgentListParamsDto _self; - final $Res Function(AgentListParamsDto) _then; + final WorkspaceIdParamsDto _self; + final $Res Function(WorkspaceIdParamsDto) _then; -/// Create a copy of AgentListParamsDto +/// Create a copy of WorkspaceIdParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? workspaceId = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? workspaceId = null,}) { return _then(_self.copyWith( -workspaceId: freezed == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable -as String?, +workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String, )); } } -/// Adds pattern-matching-related methods to [AgentListParamsDto]. -extension AgentListParamsDtoPatterns on AgentListParamsDto { +/// Adds pattern-matching-related methods to [WorkspaceIdParamsDto]. +extension WorkspaceIdParamsDtoPatterns on WorkspaceIdParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -636,10 +639,10 @@ extension AgentListParamsDtoPatterns on AgentListParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _AgentListParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceIdParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _AgentListParamsDto() when $default != null: +case _WorkspaceIdParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -658,10 +661,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _AgentListParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorkspaceIdParamsDto value) $default,){ final _that = this; switch (_that) { -case _AgentListParamsDto(): +case _WorkspaceIdParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -679,10 +682,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentListParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceIdParamsDto value)? $default,){ final _that = this; switch (_that) { -case _AgentListParamsDto() when $default != null: +case _WorkspaceIdParamsDto() when $default != null: return $default(_that);case _: return null; @@ -700,9 +703,9 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String? workspaceId)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String workspaceId)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _AgentListParamsDto() when $default != null: +case _WorkspaceIdParamsDto() when $default != null: return $default(_that.workspaceId);case _: return orElse(); @@ -721,9 +724,9 @@ return $default(_that.workspaceId);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String? workspaceId) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String workspaceId) $default,) {final _that = this; switch (_that) { -case _AgentListParamsDto(): +case _WorkspaceIdParamsDto(): return $default(_that.workspaceId);case _: throw StateError('Unexpected subclass'); @@ -741,9 +744,9 @@ return $default(_that.workspaceId);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? workspaceId)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String workspaceId)? $default,) {final _that = this; switch (_that) { -case _AgentListParamsDto() when $default != null: +case _WorkspaceIdParamsDto() when $default != null: return $default(_that.workspaceId);case _: return null; @@ -755,26 +758,26 @@ return $default(_that.workspaceId);case _: /// @nodoc @JsonSerializable() -class _AgentListParamsDto implements AgentListParamsDto { - const _AgentListParamsDto({this.workspaceId}); - factory _AgentListParamsDto.fromJson(Map json) => _$AgentListParamsDtoFromJson(json); +class _WorkspaceIdParamsDto implements WorkspaceIdParamsDto { + const _WorkspaceIdParamsDto({required this.workspaceId}); + factory _WorkspaceIdParamsDto.fromJson(Map json) => _$WorkspaceIdParamsDtoFromJson(json); -@override final String? workspaceId; +@override final String workspaceId; -/// Create a copy of AgentListParamsDto +/// Create a copy of WorkspaceIdParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$AgentListParamsDtoCopyWith<_AgentListParamsDto> get copyWith => __$AgentListParamsDtoCopyWithImpl<_AgentListParamsDto>(this, _$identity); +_$WorkspaceIdParamsDtoCopyWith<_WorkspaceIdParamsDto> get copyWith => __$WorkspaceIdParamsDtoCopyWithImpl<_WorkspaceIdParamsDto>(this, _$identity); @override Map toJson() { - return _$AgentListParamsDtoToJson(this, ); + return _$WorkspaceIdParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentListParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceIdParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -783,18 +786,18 @@ int get hashCode => Object.hash(runtimeType,workspaceId); @override String toString() { - return 'AgentListParamsDto(workspaceId: $workspaceId)'; + return 'WorkspaceIdParamsDto(workspaceId: $workspaceId)'; } } /// @nodoc -abstract mixin class _$AgentListParamsDtoCopyWith<$Res> implements $AgentListParamsDtoCopyWith<$Res> { - factory _$AgentListParamsDtoCopyWith(_AgentListParamsDto value, $Res Function(_AgentListParamsDto) _then) = __$AgentListParamsDtoCopyWithImpl; +abstract mixin class _$WorkspaceIdParamsDtoCopyWith<$Res> implements $WorkspaceIdParamsDtoCopyWith<$Res> { + factory _$WorkspaceIdParamsDtoCopyWith(_WorkspaceIdParamsDto value, $Res Function(_WorkspaceIdParamsDto) _then) = __$WorkspaceIdParamsDtoCopyWithImpl; @override @useResult $Res call({ - String? workspaceId + String workspaceId }); @@ -802,19 +805,19 @@ $Res call({ } /// @nodoc -class __$AgentListParamsDtoCopyWithImpl<$Res> - implements _$AgentListParamsDtoCopyWith<$Res> { - __$AgentListParamsDtoCopyWithImpl(this._self, this._then); +class __$WorkspaceIdParamsDtoCopyWithImpl<$Res> + implements _$WorkspaceIdParamsDtoCopyWith<$Res> { + __$WorkspaceIdParamsDtoCopyWithImpl(this._self, this._then); - final _AgentListParamsDto _self; - final $Res Function(_AgentListParamsDto) _then; + final _WorkspaceIdParamsDto _self; + final $Res Function(_WorkspaceIdParamsDto) _then; -/// Create a copy of AgentListParamsDto +/// Create a copy of WorkspaceIdParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? workspaceId = freezed,}) { - return _then(_AgentListParamsDto( -workspaceId: freezed == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable -as String?, +@override @pragma('vm:prefer-inline') $Res call({Object? workspaceId = null,}) { + return _then(_WorkspaceIdParamsDto( +workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String, )); } @@ -823,42 +826,42 @@ as String?, /// @nodoc -mixin _$AgentCreateParamsDto { +mixin _$DirectorySuggestParamsDto { - String get id; String get workspaceId; String get title; String get providerConnectionId; String get model; String get reasoningEffort; PermissionMode get permissionMode; -/// Create a copy of AgentCreateParamsDto + String get query; int get limit; +/// Create a copy of DirectorySuggestParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$AgentCreateParamsDtoCopyWith get copyWith => _$AgentCreateParamsDtoCopyWithImpl(this as AgentCreateParamsDto, _$identity); +$DirectorySuggestParamsDtoCopyWith get copyWith => _$DirectorySuggestParamsDtoCopyWithImpl(this as DirectorySuggestParamsDto, _$identity); - /// Serializes this AgentCreateParamsDto to a JSON map. + /// Serializes this DirectorySuggestParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is DirectorySuggestParamsDto&&(identical(other.query, query) || other.query == query)&&(identical(other.limit, limit) || other.limit == limit)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,workspaceId,title,providerConnectionId,model,reasoningEffort,permissionMode); +int get hashCode => Object.hash(runtimeType,query,limit); @override String toString() { - return 'AgentCreateParamsDto(id: $id, workspaceId: $workspaceId, title: $title, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort, permissionMode: $permissionMode)'; + return 'DirectorySuggestParamsDto(query: $query, limit: $limit)'; } } /// @nodoc -abstract mixin class $AgentCreateParamsDtoCopyWith<$Res> { - factory $AgentCreateParamsDtoCopyWith(AgentCreateParamsDto value, $Res Function(AgentCreateParamsDto) _then) = _$AgentCreateParamsDtoCopyWithImpl; +abstract mixin class $DirectorySuggestParamsDtoCopyWith<$Res> { + factory $DirectorySuggestParamsDtoCopyWith(DirectorySuggestParamsDto value, $Res Function(DirectorySuggestParamsDto) _then) = _$DirectorySuggestParamsDtoCopyWithImpl; @useResult $Res call({ - String id, String workspaceId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode + String query, int limit }); @@ -866,33 +869,28 @@ $Res call({ } /// @nodoc -class _$AgentCreateParamsDtoCopyWithImpl<$Res> - implements $AgentCreateParamsDtoCopyWith<$Res> { - _$AgentCreateParamsDtoCopyWithImpl(this._self, this._then); +class _$DirectorySuggestParamsDtoCopyWithImpl<$Res> + implements $DirectorySuggestParamsDtoCopyWith<$Res> { + _$DirectorySuggestParamsDtoCopyWithImpl(this._self, this._then); - final AgentCreateParamsDto _self; - final $Res Function(AgentCreateParamsDto) _then; + final DirectorySuggestParamsDto _self; + final $Res Function(DirectorySuggestParamsDto) _then; -/// Create a copy of AgentCreateParamsDto +/// Create a copy of DirectorySuggestParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? workspaceId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,Object? permissionMode = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? query = null,Object? limit = null,}) { return _then(_self.copyWith( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable -as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable -as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable -as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable -as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable -as String,permissionMode: null == permissionMode ? _self.permissionMode : permissionMode // ignore: cast_nullable_to_non_nullable -as PermissionMode, +query: null == query ? _self.query : query // ignore: cast_nullable_to_non_nullable +as String,limit: null == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable +as int, )); } } -/// Adds pattern-matching-related methods to [AgentCreateParamsDto]. -extension AgentCreateParamsDtoPatterns on AgentCreateParamsDto { +/// Adds pattern-matching-related methods to [DirectorySuggestParamsDto]. +extension DirectorySuggestParamsDtoPatterns on DirectorySuggestParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -905,10 +903,10 @@ extension AgentCreateParamsDtoPatterns on AgentCreateParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _AgentCreateParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _DirectorySuggestParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _AgentCreateParamsDto() when $default != null: +case _DirectorySuggestParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -927,10 +925,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _AgentCreateParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _DirectorySuggestParamsDto value) $default,){ final _that = this; switch (_that) { -case _AgentCreateParamsDto(): +case _DirectorySuggestParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -948,10 +946,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentCreateParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _DirectorySuggestParamsDto value)? $default,){ final _that = this; switch (_that) { -case _AgentCreateParamsDto() when $default != null: +case _DirectorySuggestParamsDto() when $default != null: return $default(_that);case _: return null; @@ -969,10 +967,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String workspaceId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String query, int limit)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _AgentCreateParamsDto() when $default != null: -return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionId,_that.model,_that.reasoningEffort,_that.permissionMode);case _: +case _DirectorySuggestParamsDto() when $default != null: +return $default(_that.query,_that.limit);case _: return orElse(); } @@ -990,10 +988,10 @@ return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionI /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String workspaceId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String query, int limit) $default,) {final _that = this; switch (_that) { -case _AgentCreateParamsDto(): -return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionId,_that.model,_that.reasoningEffort,_that.permissionMode);case _: +case _DirectorySuggestParamsDto(): +return $default(_that.query,_that.limit);case _: throw StateError('Unexpected subclass'); } @@ -1010,10 +1008,10 @@ return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionI /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String workspaceId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String query, int limit)? $default,) {final _that = this; switch (_that) { -case _AgentCreateParamsDto() when $default != null: -return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionId,_that.model,_that.reasoningEffort,_that.permissionMode);case _: +case _DirectorySuggestParamsDto() when $default != null: +return $default(_that.query,_that.limit);case _: return null; } @@ -1024,52 +1022,47 @@ return $default(_that.id,_that.workspaceId,_that.title,_that.providerConnectionI /// @nodoc @JsonSerializable() -class _AgentCreateParamsDto implements AgentCreateParamsDto { - const _AgentCreateParamsDto({required this.id, required this.workspaceId, required this.title, required this.providerConnectionId, required this.model, required this.reasoningEffort, required this.permissionMode}); - factory _AgentCreateParamsDto.fromJson(Map json) => _$AgentCreateParamsDtoFromJson(json); +class _DirectorySuggestParamsDto implements DirectorySuggestParamsDto { + const _DirectorySuggestParamsDto({required this.query, this.limit = 30}); + factory _DirectorySuggestParamsDto.fromJson(Map json) => _$DirectorySuggestParamsDtoFromJson(json); -@override final String id; -@override final String workspaceId; -@override final String title; -@override final String providerConnectionId; -@override final String model; -@override final String reasoningEffort; -@override final PermissionMode permissionMode; +@override final String query; +@override@JsonKey() final int limit; -/// Create a copy of AgentCreateParamsDto +/// Create a copy of DirectorySuggestParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$AgentCreateParamsDtoCopyWith<_AgentCreateParamsDto> get copyWith => __$AgentCreateParamsDtoCopyWithImpl<_AgentCreateParamsDto>(this, _$identity); +_$DirectorySuggestParamsDtoCopyWith<_DirectorySuggestParamsDto> get copyWith => __$DirectorySuggestParamsDtoCopyWithImpl<_DirectorySuggestParamsDto>(this, _$identity); @override Map toJson() { - return _$AgentCreateParamsDtoToJson(this, ); + return _$DirectorySuggestParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _DirectorySuggestParamsDto&&(identical(other.query, query) || other.query == query)&&(identical(other.limit, limit) || other.limit == limit)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,workspaceId,title,providerConnectionId,model,reasoningEffort,permissionMode); +int get hashCode => Object.hash(runtimeType,query,limit); @override String toString() { - return 'AgentCreateParamsDto(id: $id, workspaceId: $workspaceId, title: $title, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort, permissionMode: $permissionMode)'; + return 'DirectorySuggestParamsDto(query: $query, limit: $limit)'; } } /// @nodoc -abstract mixin class _$AgentCreateParamsDtoCopyWith<$Res> implements $AgentCreateParamsDtoCopyWith<$Res> { - factory _$AgentCreateParamsDtoCopyWith(_AgentCreateParamsDto value, $Res Function(_AgentCreateParamsDto) _then) = __$AgentCreateParamsDtoCopyWithImpl; +abstract mixin class _$DirectorySuggestParamsDtoCopyWith<$Res> implements $DirectorySuggestParamsDtoCopyWith<$Res> { + factory _$DirectorySuggestParamsDtoCopyWith(_DirectorySuggestParamsDto value, $Res Function(_DirectorySuggestParamsDto) _then) = __$DirectorySuggestParamsDtoCopyWithImpl; @override @useResult $Res call({ - String id, String workspaceId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode + String query, int limit }); @@ -1077,25 +1070,20 @@ $Res call({ } /// @nodoc -class __$AgentCreateParamsDtoCopyWithImpl<$Res> - implements _$AgentCreateParamsDtoCopyWith<$Res> { - __$AgentCreateParamsDtoCopyWithImpl(this._self, this._then); +class __$DirectorySuggestParamsDtoCopyWithImpl<$Res> + implements _$DirectorySuggestParamsDtoCopyWith<$Res> { + __$DirectorySuggestParamsDtoCopyWithImpl(this._self, this._then); - final _AgentCreateParamsDto _self; - final $Res Function(_AgentCreateParamsDto) _then; + final _DirectorySuggestParamsDto _self; + final $Res Function(_DirectorySuggestParamsDto) _then; -/// Create a copy of AgentCreateParamsDto +/// Create a copy of DirectorySuggestParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? workspaceId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,Object? permissionMode = null,}) { - return _then(_AgentCreateParamsDto( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable -as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable -as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable -as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable -as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable -as String,permissionMode: null == permissionMode ? _self.permissionMode : permissionMode // ignore: cast_nullable_to_non_nullable -as PermissionMode, +@override @pragma('vm:prefer-inline') $Res call({Object? query = null,Object? limit = null,}) { + return _then(_DirectorySuggestParamsDto( +query: null == query ? _self.query : query // ignore: cast_nullable_to_non_nullable +as String,limit: null == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable +as int, )); } @@ -1104,42 +1092,42 @@ as PermissionMode, /// @nodoc -mixin _$AgentConfigurationUpdateParamsDto { +mixin _$GitBranchesListParamsDto { - String get agentId; String get providerConnectionId; String get model; String get reasoningEffort; -/// Create a copy of AgentConfigurationUpdateParamsDto + String get workspaceId; +/// Create a copy of GitBranchesListParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$AgentConfigurationUpdateParamsDtoCopyWith get copyWith => _$AgentConfigurationUpdateParamsDtoCopyWithImpl(this as AgentConfigurationUpdateParamsDto, _$identity); +$GitBranchesListParamsDtoCopyWith get copyWith => _$GitBranchesListParamsDtoCopyWithImpl(this as GitBranchesListParamsDto, _$identity); - /// Serializes this AgentConfigurationUpdateParamsDto to a JSON map. + /// Serializes this GitBranchesListParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentConfigurationUpdateParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is GitBranchesListParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId,providerConnectionId,model,reasoningEffort); +int get hashCode => Object.hash(runtimeType,workspaceId); @override String toString() { - return 'AgentConfigurationUpdateParamsDto(agentId: $agentId, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort)'; + return 'GitBranchesListParamsDto(workspaceId: $workspaceId)'; } } /// @nodoc -abstract mixin class $AgentConfigurationUpdateParamsDtoCopyWith<$Res> { - factory $AgentConfigurationUpdateParamsDtoCopyWith(AgentConfigurationUpdateParamsDto value, $Res Function(AgentConfigurationUpdateParamsDto) _then) = _$AgentConfigurationUpdateParamsDtoCopyWithImpl; +abstract mixin class $GitBranchesListParamsDtoCopyWith<$Res> { + factory $GitBranchesListParamsDtoCopyWith(GitBranchesListParamsDto value, $Res Function(GitBranchesListParamsDto) _then) = _$GitBranchesListParamsDtoCopyWithImpl; @useResult $Res call({ - String agentId, String providerConnectionId, String model, String reasoningEffort + String workspaceId }); @@ -1147,21 +1135,18 @@ $Res call({ } /// @nodoc -class _$AgentConfigurationUpdateParamsDtoCopyWithImpl<$Res> - implements $AgentConfigurationUpdateParamsDtoCopyWith<$Res> { - _$AgentConfigurationUpdateParamsDtoCopyWithImpl(this._self, this._then); +class _$GitBranchesListParamsDtoCopyWithImpl<$Res> + implements $GitBranchesListParamsDtoCopyWith<$Res> { + _$GitBranchesListParamsDtoCopyWithImpl(this._self, this._then); - final AgentConfigurationUpdateParamsDto _self; - final $Res Function(AgentConfigurationUpdateParamsDto) _then; + final GitBranchesListParamsDto _self; + final $Res Function(GitBranchesListParamsDto) _then; -/// Create a copy of AgentConfigurationUpdateParamsDto +/// Create a copy of GitBranchesListParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? workspaceId = null,}) { return _then(_self.copyWith( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable -as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable -as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable +workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable as String, )); } @@ -1169,8 +1154,8 @@ as String, } -/// Adds pattern-matching-related methods to [AgentConfigurationUpdateParamsDto]. -extension AgentConfigurationUpdateParamsDtoPatterns on AgentConfigurationUpdateParamsDto { +/// Adds pattern-matching-related methods to [GitBranchesListParamsDto]. +extension GitBranchesListParamsDtoPatterns on GitBranchesListParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -1183,10 +1168,10 @@ extension AgentConfigurationUpdateParamsDtoPatterns on AgentConfigurationUpdateP /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _AgentConfigurationUpdateParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _GitBranchesListParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _AgentConfigurationUpdateParamsDto() when $default != null: +case _GitBranchesListParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -1205,10 +1190,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _AgentConfigurationUpdateParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _GitBranchesListParamsDto value) $default,){ final _that = this; switch (_that) { -case _AgentConfigurationUpdateParamsDto(): +case _GitBranchesListParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -1226,10 +1211,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentConfigurationUpdateParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GitBranchesListParamsDto value)? $default,){ final _that = this; switch (_that) { -case _AgentConfigurationUpdateParamsDto() when $default != null: +case _GitBranchesListParamsDto() when $default != null: return $default(_that);case _: return null; @@ -1247,10 +1232,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId, String providerConnectionId, String model, String reasoningEffort)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String workspaceId)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _AgentConfigurationUpdateParamsDto() when $default != null: -return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reasoningEffort);case _: +case _GitBranchesListParamsDto() when $default != null: +return $default(_that.workspaceId);case _: return orElse(); } @@ -1268,10 +1253,10 @@ return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reaso /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String agentId, String providerConnectionId, String model, String reasoningEffort) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String workspaceId) $default,) {final _that = this; switch (_that) { -case _AgentConfigurationUpdateParamsDto(): -return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reasoningEffort);case _: +case _GitBranchesListParamsDto(): +return $default(_that.workspaceId);case _: throw StateError('Unexpected subclass'); } @@ -1288,10 +1273,10 @@ return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reaso /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId, String providerConnectionId, String model, String reasoningEffort)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String workspaceId)? $default,) {final _that = this; switch (_that) { -case _AgentConfigurationUpdateParamsDto() when $default != null: -return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reasoningEffort);case _: +case _GitBranchesListParamsDto() when $default != null: +return $default(_that.workspaceId);case _: return null; } @@ -1302,49 +1287,46 @@ return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reaso /// @nodoc @JsonSerializable() -class _AgentConfigurationUpdateParamsDto implements AgentConfigurationUpdateParamsDto { - const _AgentConfigurationUpdateParamsDto({required this.agentId, required this.providerConnectionId, required this.model, required this.reasoningEffort}); - factory _AgentConfigurationUpdateParamsDto.fromJson(Map json) => _$AgentConfigurationUpdateParamsDtoFromJson(json); +class _GitBranchesListParamsDto implements GitBranchesListParamsDto { + const _GitBranchesListParamsDto({required this.workspaceId}); + factory _GitBranchesListParamsDto.fromJson(Map json) => _$GitBranchesListParamsDtoFromJson(json); -@override final String agentId; -@override final String providerConnectionId; -@override final String model; -@override final String reasoningEffort; +@override final String workspaceId; -/// Create a copy of AgentConfigurationUpdateParamsDto +/// Create a copy of GitBranchesListParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$AgentConfigurationUpdateParamsDtoCopyWith<_AgentConfigurationUpdateParamsDto> get copyWith => __$AgentConfigurationUpdateParamsDtoCopyWithImpl<_AgentConfigurationUpdateParamsDto>(this, _$identity); +_$GitBranchesListParamsDtoCopyWith<_GitBranchesListParamsDto> get copyWith => __$GitBranchesListParamsDtoCopyWithImpl<_GitBranchesListParamsDto>(this, _$identity); @override Map toJson() { - return _$AgentConfigurationUpdateParamsDtoToJson(this, ); + return _$GitBranchesListParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentConfigurationUpdateParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _GitBranchesListParamsDto&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId,providerConnectionId,model,reasoningEffort); +int get hashCode => Object.hash(runtimeType,workspaceId); @override String toString() { - return 'AgentConfigurationUpdateParamsDto(agentId: $agentId, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort)'; + return 'GitBranchesListParamsDto(workspaceId: $workspaceId)'; } } /// @nodoc -abstract mixin class _$AgentConfigurationUpdateParamsDtoCopyWith<$Res> implements $AgentConfigurationUpdateParamsDtoCopyWith<$Res> { - factory _$AgentConfigurationUpdateParamsDtoCopyWith(_AgentConfigurationUpdateParamsDto value, $Res Function(_AgentConfigurationUpdateParamsDto) _then) = __$AgentConfigurationUpdateParamsDtoCopyWithImpl; +abstract mixin class _$GitBranchesListParamsDtoCopyWith<$Res> implements $GitBranchesListParamsDtoCopyWith<$Res> { + factory _$GitBranchesListParamsDtoCopyWith(_GitBranchesListParamsDto value, $Res Function(_GitBranchesListParamsDto) _then) = __$GitBranchesListParamsDtoCopyWithImpl; @override @useResult $Res call({ - String agentId, String providerConnectionId, String model, String reasoningEffort + String workspaceId }); @@ -1352,21 +1334,18 @@ $Res call({ } /// @nodoc -class __$AgentConfigurationUpdateParamsDtoCopyWithImpl<$Res> - implements _$AgentConfigurationUpdateParamsDtoCopyWith<$Res> { - __$AgentConfigurationUpdateParamsDtoCopyWithImpl(this._self, this._then); +class __$GitBranchesListParamsDtoCopyWithImpl<$Res> + implements _$GitBranchesListParamsDtoCopyWith<$Res> { + __$GitBranchesListParamsDtoCopyWithImpl(this._self, this._then); - final _AgentConfigurationUpdateParamsDto _self; - final $Res Function(_AgentConfigurationUpdateParamsDto) _then; + final _GitBranchesListParamsDto _self; + final $Res Function(_GitBranchesListParamsDto) _then; -/// Create a copy of AgentConfigurationUpdateParamsDto +/// Create a copy of GitBranchesListParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,}) { - return _then(_AgentConfigurationUpdateParamsDto( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable -as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable -as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable +@override @pragma('vm:prefer-inline') $Res call({Object? workspaceId = null,}) { + return _then(_GitBranchesListParamsDto( +workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable as String, )); } @@ -1376,42 +1355,42 @@ as String, /// @nodoc -mixin _$ProviderConnectApiKeyParamsDto { +mixin _$WorktreeCreateParamsDto { - String get definitionId; String get apiKey; bool get makeDefault; -/// Create a copy of ProviderConnectApiKeyParamsDto + String get id; String get workspaceId; WorktreeCreateMode get mode; String get branchName; String? get baseBranch; +/// Create a copy of WorktreeCreateParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderConnectApiKeyParamsDtoCopyWith get copyWith => _$ProviderConnectApiKeyParamsDtoCopyWithImpl(this as ProviderConnectApiKeyParamsDto, _$identity); +$WorktreeCreateParamsDtoCopyWith get copyWith => _$WorktreeCreateParamsDtoCopyWithImpl(this as WorktreeCreateParamsDto, _$identity); - /// Serializes this ProviderConnectApiKeyParamsDto to a JSON map. + /// Serializes this WorktreeCreateParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderConnectApiKeyParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorktreeCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.branchName, branchName) || other.branchName == branchName)&&(identical(other.baseBranch, baseBranch) || other.baseBranch == baseBranch)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,definitionId,apiKey,makeDefault); +int get hashCode => Object.hash(runtimeType,id,workspaceId,mode,branchName,baseBranch); @override String toString() { - return 'ProviderConnectApiKeyParamsDto(definitionId: $definitionId, apiKey: $apiKey, makeDefault: $makeDefault)'; + return 'WorktreeCreateParamsDto(id: $id, workspaceId: $workspaceId, mode: $mode, branchName: $branchName, baseBranch: $baseBranch)'; } } /// @nodoc -abstract mixin class $ProviderConnectApiKeyParamsDtoCopyWith<$Res> { - factory $ProviderConnectApiKeyParamsDtoCopyWith(ProviderConnectApiKeyParamsDto value, $Res Function(ProviderConnectApiKeyParamsDto) _then) = _$ProviderConnectApiKeyParamsDtoCopyWithImpl; +abstract mixin class $WorktreeCreateParamsDtoCopyWith<$Res> { + factory $WorktreeCreateParamsDtoCopyWith(WorktreeCreateParamsDto value, $Res Function(WorktreeCreateParamsDto) _then) = _$WorktreeCreateParamsDtoCopyWithImpl; @useResult $Res call({ - String definitionId, String apiKey, bool makeDefault + String id, String workspaceId, WorktreeCreateMode mode, String branchName, String? baseBranch }); @@ -1419,29 +1398,31 @@ $Res call({ } /// @nodoc -class _$ProviderConnectApiKeyParamsDtoCopyWithImpl<$Res> - implements $ProviderConnectApiKeyParamsDtoCopyWith<$Res> { - _$ProviderConnectApiKeyParamsDtoCopyWithImpl(this._self, this._then); +class _$WorktreeCreateParamsDtoCopyWithImpl<$Res> + implements $WorktreeCreateParamsDtoCopyWith<$Res> { + _$WorktreeCreateParamsDtoCopyWithImpl(this._self, this._then); - final ProviderConnectApiKeyParamsDto _self; - final $Res Function(ProviderConnectApiKeyParamsDto) _then; + final WorktreeCreateParamsDto _self; + final $Res Function(WorktreeCreateParamsDto) _then; -/// Create a copy of ProviderConnectApiKeyParamsDto +/// Create a copy of WorktreeCreateParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? definitionId = null,Object? apiKey = null,Object? makeDefault = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? workspaceId = null,Object? mode = null,Object? branchName = null,Object? baseBranch = freezed,}) { return _then(_self.copyWith( -definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable -as String,apiKey: null == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable -as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool, +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,mode: null == mode ? _self.mode : mode // ignore: cast_nullable_to_non_nullable +as WorktreeCreateMode,branchName: null == branchName ? _self.branchName : branchName // ignore: cast_nullable_to_non_nullable +as String,baseBranch: freezed == baseBranch ? _self.baseBranch : baseBranch // ignore: cast_nullable_to_non_nullable +as String?, )); } } -/// Adds pattern-matching-related methods to [ProviderConnectApiKeyParamsDto]. -extension ProviderConnectApiKeyParamsDtoPatterns on ProviderConnectApiKeyParamsDto { +/// Adds pattern-matching-related methods to [WorktreeCreateParamsDto]. +extension WorktreeCreateParamsDtoPatterns on WorktreeCreateParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -1454,10 +1435,10 @@ extension ProviderConnectApiKeyParamsDtoPatterns on ProviderConnectApiKeyParamsD /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderConnectApiKeyParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorktreeCreateParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderConnectApiKeyParamsDto() when $default != null: +case _WorktreeCreateParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -1476,10 +1457,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderConnectApiKeyParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorktreeCreateParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderConnectApiKeyParamsDto(): +case _WorktreeCreateParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -1497,10 +1478,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderConnectApiKeyParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorktreeCreateParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderConnectApiKeyParamsDto() when $default != null: +case _WorktreeCreateParamsDto() when $default != null: return $default(_that);case _: return null; @@ -1518,10 +1499,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String definitionId, String apiKey, bool makeDefault)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String workspaceId, WorktreeCreateMode mode, String branchName, String? baseBranch)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderConnectApiKeyParamsDto() when $default != null: -return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: +case _WorktreeCreateParamsDto() when $default != null: +return $default(_that.id,_that.workspaceId,_that.mode,_that.branchName,_that.baseBranch);case _: return orElse(); } @@ -1539,10 +1520,10 @@ return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String definitionId, String apiKey, bool makeDefault) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String workspaceId, WorktreeCreateMode mode, String branchName, String? baseBranch) $default,) {final _that = this; switch (_that) { -case _ProviderConnectApiKeyParamsDto(): -return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: +case _WorktreeCreateParamsDto(): +return $default(_that.id,_that.workspaceId,_that.mode,_that.branchName,_that.baseBranch);case _: throw StateError('Unexpected subclass'); } @@ -1559,10 +1540,10 @@ return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String definitionId, String apiKey, bool makeDefault)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String workspaceId, WorktreeCreateMode mode, String branchName, String? baseBranch)? $default,) {final _that = this; switch (_that) { -case _ProviderConnectApiKeyParamsDto() when $default != null: -return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: +case _WorktreeCreateParamsDto() when $default != null: +return $default(_that.id,_that.workspaceId,_that.mode,_that.branchName,_that.baseBranch);case _: return null; } @@ -1573,48 +1554,50 @@ return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: /// @nodoc @JsonSerializable() -class _ProviderConnectApiKeyParamsDto implements ProviderConnectApiKeyParamsDto { - const _ProviderConnectApiKeyParamsDto({required this.definitionId, required this.apiKey, required this.makeDefault}); - factory _ProviderConnectApiKeyParamsDto.fromJson(Map json) => _$ProviderConnectApiKeyParamsDtoFromJson(json); +class _WorktreeCreateParamsDto implements WorktreeCreateParamsDto { + const _WorktreeCreateParamsDto({required this.id, required this.workspaceId, required this.mode, required this.branchName, this.baseBranch}); + factory _WorktreeCreateParamsDto.fromJson(Map json) => _$WorktreeCreateParamsDtoFromJson(json); -@override final String definitionId; -@override final String apiKey; -@override final bool makeDefault; +@override final String id; +@override final String workspaceId; +@override final WorktreeCreateMode mode; +@override final String branchName; +@override final String? baseBranch; -/// Create a copy of ProviderConnectApiKeyParamsDto +/// Create a copy of WorktreeCreateParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderConnectApiKeyParamsDtoCopyWith<_ProviderConnectApiKeyParamsDto> get copyWith => __$ProviderConnectApiKeyParamsDtoCopyWithImpl<_ProviderConnectApiKeyParamsDto>(this, _$identity); +_$WorktreeCreateParamsDtoCopyWith<_WorktreeCreateParamsDto> get copyWith => __$WorktreeCreateParamsDtoCopyWithImpl<_WorktreeCreateParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderConnectApiKeyParamsDtoToJson(this, ); + return _$WorktreeCreateParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderConnectApiKeyParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorktreeCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.mode, mode) || other.mode == mode)&&(identical(other.branchName, branchName) || other.branchName == branchName)&&(identical(other.baseBranch, baseBranch) || other.baseBranch == baseBranch)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,definitionId,apiKey,makeDefault); +int get hashCode => Object.hash(runtimeType,id,workspaceId,mode,branchName,baseBranch); @override String toString() { - return 'ProviderConnectApiKeyParamsDto(definitionId: $definitionId, apiKey: $apiKey, makeDefault: $makeDefault)'; + return 'WorktreeCreateParamsDto(id: $id, workspaceId: $workspaceId, mode: $mode, branchName: $branchName, baseBranch: $baseBranch)'; } } /// @nodoc -abstract mixin class _$ProviderConnectApiKeyParamsDtoCopyWith<$Res> implements $ProviderConnectApiKeyParamsDtoCopyWith<$Res> { - factory _$ProviderConnectApiKeyParamsDtoCopyWith(_ProviderConnectApiKeyParamsDto value, $Res Function(_ProviderConnectApiKeyParamsDto) _then) = __$ProviderConnectApiKeyParamsDtoCopyWithImpl; +abstract mixin class _$WorktreeCreateParamsDtoCopyWith<$Res> implements $WorktreeCreateParamsDtoCopyWith<$Res> { + factory _$WorktreeCreateParamsDtoCopyWith(_WorktreeCreateParamsDto value, $Res Function(_WorktreeCreateParamsDto) _then) = __$WorktreeCreateParamsDtoCopyWithImpl; @override @useResult $Res call({ - String definitionId, String apiKey, bool makeDefault + String id, String workspaceId, WorktreeCreateMode mode, String branchName, String? baseBranch }); @@ -1622,21 +1605,23 @@ $Res call({ } /// @nodoc -class __$ProviderConnectApiKeyParamsDtoCopyWithImpl<$Res> - implements _$ProviderConnectApiKeyParamsDtoCopyWith<$Res> { - __$ProviderConnectApiKeyParamsDtoCopyWithImpl(this._self, this._then); +class __$WorktreeCreateParamsDtoCopyWithImpl<$Res> + implements _$WorktreeCreateParamsDtoCopyWith<$Res> { + __$WorktreeCreateParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderConnectApiKeyParamsDto _self; - final $Res Function(_ProviderConnectApiKeyParamsDto) _then; + final _WorktreeCreateParamsDto _self; + final $Res Function(_WorktreeCreateParamsDto) _then; -/// Create a copy of ProviderConnectApiKeyParamsDto +/// Create a copy of WorktreeCreateParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? definitionId = null,Object? apiKey = null,Object? makeDefault = null,}) { - return _then(_ProviderConnectApiKeyParamsDto( -definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable -as String,apiKey: null == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable -as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool, +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? workspaceId = null,Object? mode = null,Object? branchName = null,Object? baseBranch = freezed,}) { + return _then(_WorktreeCreateParamsDto( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable +as String,mode: null == mode ? _self.mode : mode // ignore: cast_nullable_to_non_nullable +as WorktreeCreateMode,branchName: null == branchName ? _self.branchName : branchName // ignore: cast_nullable_to_non_nullable +as String,baseBranch: freezed == baseBranch ? _self.baseBranch : baseBranch // ignore: cast_nullable_to_non_nullable +as String?, )); } @@ -1645,42 +1630,42 @@ as bool, /// @nodoc -mixin _$ProviderConnectNoneParamsDto { +mixin _$WorktreeIdParamsDto { - String get definitionId; bool get makeDefault; -/// Create a copy of ProviderConnectNoneParamsDto + String get worktreeId; +/// Create a copy of WorktreeIdParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderConnectNoneParamsDtoCopyWith get copyWith => _$ProviderConnectNoneParamsDtoCopyWithImpl(this as ProviderConnectNoneParamsDto, _$identity); +$WorktreeIdParamsDtoCopyWith get copyWith => _$WorktreeIdParamsDtoCopyWithImpl(this as WorktreeIdParamsDto, _$identity); - /// Serializes this ProviderConnectNoneParamsDto to a JSON map. + /// Serializes this WorktreeIdParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderConnectNoneParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorktreeIdParamsDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,definitionId,makeDefault); +int get hashCode => Object.hash(runtimeType,worktreeId); @override String toString() { - return 'ProviderConnectNoneParamsDto(definitionId: $definitionId, makeDefault: $makeDefault)'; + return 'WorktreeIdParamsDto(worktreeId: $worktreeId)'; } } /// @nodoc -abstract mixin class $ProviderConnectNoneParamsDtoCopyWith<$Res> { - factory $ProviderConnectNoneParamsDtoCopyWith(ProviderConnectNoneParamsDto value, $Res Function(ProviderConnectNoneParamsDto) _then) = _$ProviderConnectNoneParamsDtoCopyWithImpl; +abstract mixin class $WorktreeIdParamsDtoCopyWith<$Res> { + factory $WorktreeIdParamsDtoCopyWith(WorktreeIdParamsDto value, $Res Function(WorktreeIdParamsDto) _then) = _$WorktreeIdParamsDtoCopyWithImpl; @useResult $Res call({ - String definitionId, bool makeDefault + String worktreeId }); @@ -1688,28 +1673,27 @@ $Res call({ } /// @nodoc -class _$ProviderConnectNoneParamsDtoCopyWithImpl<$Res> - implements $ProviderConnectNoneParamsDtoCopyWith<$Res> { - _$ProviderConnectNoneParamsDtoCopyWithImpl(this._self, this._then); +class _$WorktreeIdParamsDtoCopyWithImpl<$Res> + implements $WorktreeIdParamsDtoCopyWith<$Res> { + _$WorktreeIdParamsDtoCopyWithImpl(this._self, this._then); - final ProviderConnectNoneParamsDto _self; - final $Res Function(ProviderConnectNoneParamsDto) _then; + final WorktreeIdParamsDto _self; + final $Res Function(WorktreeIdParamsDto) _then; -/// Create a copy of ProviderConnectNoneParamsDto +/// Create a copy of WorktreeIdParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? definitionId = null,Object? makeDefault = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? worktreeId = null,}) { return _then(_self.copyWith( -definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable -as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool, +worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String, )); } } -/// Adds pattern-matching-related methods to [ProviderConnectNoneParamsDto]. -extension ProviderConnectNoneParamsDtoPatterns on ProviderConnectNoneParamsDto { +/// Adds pattern-matching-related methods to [WorktreeIdParamsDto]. +extension WorktreeIdParamsDtoPatterns on WorktreeIdParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -1722,10 +1706,10 @@ extension ProviderConnectNoneParamsDtoPatterns on ProviderConnectNoneParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderConnectNoneParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorktreeIdParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderConnectNoneParamsDto() when $default != null: +case _WorktreeIdParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -1744,10 +1728,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderConnectNoneParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorktreeIdParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderConnectNoneParamsDto(): +case _WorktreeIdParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -1765,10 +1749,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderConnectNoneParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorktreeIdParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderConnectNoneParamsDto() when $default != null: +case _WorktreeIdParamsDto() when $default != null: return $default(_that);case _: return null; @@ -1786,10 +1770,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String definitionId, bool makeDefault)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String worktreeId)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderConnectNoneParamsDto() when $default != null: -return $default(_that.definitionId,_that.makeDefault);case _: +case _WorktreeIdParamsDto() when $default != null: +return $default(_that.worktreeId);case _: return orElse(); } @@ -1807,10 +1791,10 @@ return $default(_that.definitionId,_that.makeDefault);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String definitionId, bool makeDefault) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String worktreeId) $default,) {final _that = this; switch (_that) { -case _ProviderConnectNoneParamsDto(): -return $default(_that.definitionId,_that.makeDefault);case _: +case _WorktreeIdParamsDto(): +return $default(_that.worktreeId);case _: throw StateError('Unexpected subclass'); } @@ -1827,10 +1811,10 @@ return $default(_that.definitionId,_that.makeDefault);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String definitionId, bool makeDefault)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String worktreeId)? $default,) {final _that = this; switch (_that) { -case _ProviderConnectNoneParamsDto() when $default != null: -return $default(_that.definitionId,_that.makeDefault);case _: +case _WorktreeIdParamsDto() when $default != null: +return $default(_that.worktreeId);case _: return null; } @@ -1841,47 +1825,46 @@ return $default(_that.definitionId,_that.makeDefault);case _: /// @nodoc @JsonSerializable() -class _ProviderConnectNoneParamsDto implements ProviderConnectNoneParamsDto { - const _ProviderConnectNoneParamsDto({required this.definitionId, required this.makeDefault}); - factory _ProviderConnectNoneParamsDto.fromJson(Map json) => _$ProviderConnectNoneParamsDtoFromJson(json); +class _WorktreeIdParamsDto implements WorktreeIdParamsDto { + const _WorktreeIdParamsDto({required this.worktreeId}); + factory _WorktreeIdParamsDto.fromJson(Map json) => _$WorktreeIdParamsDtoFromJson(json); -@override final String definitionId; -@override final bool makeDefault; +@override final String worktreeId; -/// Create a copy of ProviderConnectNoneParamsDto +/// Create a copy of WorktreeIdParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderConnectNoneParamsDtoCopyWith<_ProviderConnectNoneParamsDto> get copyWith => __$ProviderConnectNoneParamsDtoCopyWithImpl<_ProviderConnectNoneParamsDto>(this, _$identity); +_$WorktreeIdParamsDtoCopyWith<_WorktreeIdParamsDto> get copyWith => __$WorktreeIdParamsDtoCopyWithImpl<_WorktreeIdParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderConnectNoneParamsDtoToJson(this, ); + return _$WorktreeIdParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderConnectNoneParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorktreeIdParamsDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,definitionId,makeDefault); +int get hashCode => Object.hash(runtimeType,worktreeId); @override String toString() { - return 'ProviderConnectNoneParamsDto(definitionId: $definitionId, makeDefault: $makeDefault)'; + return 'WorktreeIdParamsDto(worktreeId: $worktreeId)'; } } /// @nodoc -abstract mixin class _$ProviderConnectNoneParamsDtoCopyWith<$Res> implements $ProviderConnectNoneParamsDtoCopyWith<$Res> { - factory _$ProviderConnectNoneParamsDtoCopyWith(_ProviderConnectNoneParamsDto value, $Res Function(_ProviderConnectNoneParamsDto) _then) = __$ProviderConnectNoneParamsDtoCopyWithImpl; +abstract mixin class _$WorktreeIdParamsDtoCopyWith<$Res> implements $WorktreeIdParamsDtoCopyWith<$Res> { + factory _$WorktreeIdParamsDtoCopyWith(_WorktreeIdParamsDto value, $Res Function(_WorktreeIdParamsDto) _then) = __$WorktreeIdParamsDtoCopyWithImpl; @override @useResult $Res call({ - String definitionId, bool makeDefault + String worktreeId }); @@ -1889,20 +1872,19 @@ $Res call({ } /// @nodoc -class __$ProviderConnectNoneParamsDtoCopyWithImpl<$Res> - implements _$ProviderConnectNoneParamsDtoCopyWith<$Res> { - __$ProviderConnectNoneParamsDtoCopyWithImpl(this._self, this._then); +class __$WorktreeIdParamsDtoCopyWithImpl<$Res> + implements _$WorktreeIdParamsDtoCopyWith<$Res> { + __$WorktreeIdParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderConnectNoneParamsDto _self; - final $Res Function(_ProviderConnectNoneParamsDto) _then; + final _WorktreeIdParamsDto _self; + final $Res Function(_WorktreeIdParamsDto) _then; -/// Create a copy of ProviderConnectNoneParamsDto +/// Create a copy of WorktreeIdParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? definitionId = null,Object? makeDefault = null,}) { - return _then(_ProviderConnectNoneParamsDto( -definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable -as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool, +@override @pragma('vm:prefer-inline') $Res call({Object? worktreeId = null,}) { + return _then(_WorktreeIdParamsDto( +worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String, )); } @@ -1911,42 +1893,42 @@ as bool, /// @nodoc -mixin _$ProviderConnectionIdParamsDto { +mixin _$WorktreeArchiveParamsDto { - String get connectionId; -/// Create a copy of ProviderConnectionIdParamsDto + String get worktreeId; bool get force; +/// Create a copy of WorktreeArchiveParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderConnectionIdParamsDtoCopyWith get copyWith => _$ProviderConnectionIdParamsDtoCopyWithImpl(this as ProviderConnectionIdParamsDto, _$identity); +$WorktreeArchiveParamsDtoCopyWith get copyWith => _$WorktreeArchiveParamsDtoCopyWithImpl(this as WorktreeArchiveParamsDto, _$identity); - /// Serializes this ProviderConnectionIdParamsDto to a JSON map. + /// Serializes this WorktreeArchiveParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderConnectionIdParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorktreeArchiveParamsDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.force, force) || other.force == force)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId); +int get hashCode => Object.hash(runtimeType,worktreeId,force); @override String toString() { - return 'ProviderConnectionIdParamsDto(connectionId: $connectionId)'; + return 'WorktreeArchiveParamsDto(worktreeId: $worktreeId, force: $force)'; } } /// @nodoc -abstract mixin class $ProviderConnectionIdParamsDtoCopyWith<$Res> { - factory $ProviderConnectionIdParamsDtoCopyWith(ProviderConnectionIdParamsDto value, $Res Function(ProviderConnectionIdParamsDto) _then) = _$ProviderConnectionIdParamsDtoCopyWithImpl; +abstract mixin class $WorktreeArchiveParamsDtoCopyWith<$Res> { + factory $WorktreeArchiveParamsDtoCopyWith(WorktreeArchiveParamsDto value, $Res Function(WorktreeArchiveParamsDto) _then) = _$WorktreeArchiveParamsDtoCopyWithImpl; @useResult $Res call({ - String connectionId + String worktreeId, bool force }); @@ -1954,27 +1936,28 @@ $Res call({ } /// @nodoc -class _$ProviderConnectionIdParamsDtoCopyWithImpl<$Res> - implements $ProviderConnectionIdParamsDtoCopyWith<$Res> { - _$ProviderConnectionIdParamsDtoCopyWithImpl(this._self, this._then); +class _$WorktreeArchiveParamsDtoCopyWithImpl<$Res> + implements $WorktreeArchiveParamsDtoCopyWith<$Res> { + _$WorktreeArchiveParamsDtoCopyWithImpl(this._self, this._then); - final ProviderConnectionIdParamsDto _self; - final $Res Function(ProviderConnectionIdParamsDto) _then; + final WorktreeArchiveParamsDto _self; + final $Res Function(WorktreeArchiveParamsDto) _then; -/// Create a copy of ProviderConnectionIdParamsDto +/// Create a copy of WorktreeArchiveParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? worktreeId = null,Object? force = null,}) { return _then(_self.copyWith( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String, +worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String,force: null == force ? _self.force : force // ignore: cast_nullable_to_non_nullable +as bool, )); } } -/// Adds pattern-matching-related methods to [ProviderConnectionIdParamsDto]. -extension ProviderConnectionIdParamsDtoPatterns on ProviderConnectionIdParamsDto { +/// Adds pattern-matching-related methods to [WorktreeArchiveParamsDto]. +extension WorktreeArchiveParamsDtoPatterns on WorktreeArchiveParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -1987,10 +1970,10 @@ extension ProviderConnectionIdParamsDtoPatterns on ProviderConnectionIdParamsDto /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderConnectionIdParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorktreeArchiveParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderConnectionIdParamsDto() when $default != null: +case _WorktreeArchiveParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -2009,10 +1992,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderConnectionIdParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorktreeArchiveParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderConnectionIdParamsDto(): +case _WorktreeArchiveParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -2030,10 +2013,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderConnectionIdParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorktreeArchiveParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderConnectionIdParamsDto() when $default != null: +case _WorktreeArchiveParamsDto() when $default != null: return $default(_that);case _: return null; @@ -2051,10 +2034,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String worktreeId, bool force)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderConnectionIdParamsDto() when $default != null: -return $default(_that.connectionId);case _: +case _WorktreeArchiveParamsDto() when $default != null: +return $default(_that.worktreeId,_that.force);case _: return orElse(); } @@ -2072,10 +2055,10 @@ return $default(_that.connectionId);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String connectionId) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String worktreeId, bool force) $default,) {final _that = this; switch (_that) { -case _ProviderConnectionIdParamsDto(): -return $default(_that.connectionId);case _: +case _WorktreeArchiveParamsDto(): +return $default(_that.worktreeId,_that.force);case _: throw StateError('Unexpected subclass'); } @@ -2092,10 +2075,10 @@ return $default(_that.connectionId);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String worktreeId, bool force)? $default,) {final _that = this; switch (_that) { -case _ProviderConnectionIdParamsDto() when $default != null: -return $default(_that.connectionId);case _: +case _WorktreeArchiveParamsDto() when $default != null: +return $default(_that.worktreeId,_that.force);case _: return null; } @@ -2106,46 +2089,47 @@ return $default(_that.connectionId);case _: /// @nodoc @JsonSerializable() -class _ProviderConnectionIdParamsDto implements ProviderConnectionIdParamsDto { - const _ProviderConnectionIdParamsDto({required this.connectionId}); - factory _ProviderConnectionIdParamsDto.fromJson(Map json) => _$ProviderConnectionIdParamsDtoFromJson(json); +class _WorktreeArchiveParamsDto implements WorktreeArchiveParamsDto { + const _WorktreeArchiveParamsDto({required this.worktreeId, required this.force}); + factory _WorktreeArchiveParamsDto.fromJson(Map json) => _$WorktreeArchiveParamsDtoFromJson(json); -@override final String connectionId; +@override final String worktreeId; +@override final bool force; -/// Create a copy of ProviderConnectionIdParamsDto +/// Create a copy of WorktreeArchiveParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderConnectionIdParamsDtoCopyWith<_ProviderConnectionIdParamsDto> get copyWith => __$ProviderConnectionIdParamsDtoCopyWithImpl<_ProviderConnectionIdParamsDto>(this, _$identity); +_$WorktreeArchiveParamsDtoCopyWith<_WorktreeArchiveParamsDto> get copyWith => __$WorktreeArchiveParamsDtoCopyWithImpl<_WorktreeArchiveParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderConnectionIdParamsDtoToJson(this, ); + return _$WorktreeArchiveParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderConnectionIdParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorktreeArchiveParamsDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.force, force) || other.force == force)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId); +int get hashCode => Object.hash(runtimeType,worktreeId,force); @override String toString() { - return 'ProviderConnectionIdParamsDto(connectionId: $connectionId)'; + return 'WorktreeArchiveParamsDto(worktreeId: $worktreeId, force: $force)'; } } /// @nodoc -abstract mixin class _$ProviderConnectionIdParamsDtoCopyWith<$Res> implements $ProviderConnectionIdParamsDtoCopyWith<$Res> { - factory _$ProviderConnectionIdParamsDtoCopyWith(_ProviderConnectionIdParamsDto value, $Res Function(_ProviderConnectionIdParamsDto) _then) = __$ProviderConnectionIdParamsDtoCopyWithImpl; +abstract mixin class _$WorktreeArchiveParamsDtoCopyWith<$Res> implements $WorktreeArchiveParamsDtoCopyWith<$Res> { + factory _$WorktreeArchiveParamsDtoCopyWith(_WorktreeArchiveParamsDto value, $Res Function(_WorktreeArchiveParamsDto) _then) = __$WorktreeArchiveParamsDtoCopyWithImpl; @override @useResult $Res call({ - String connectionId + String worktreeId, bool force }); @@ -2153,19 +2137,20 @@ $Res call({ } /// @nodoc -class __$ProviderConnectionIdParamsDtoCopyWithImpl<$Res> - implements _$ProviderConnectionIdParamsDtoCopyWith<$Res> { - __$ProviderConnectionIdParamsDtoCopyWithImpl(this._self, this._then); +class __$WorktreeArchiveParamsDtoCopyWithImpl<$Res> + implements _$WorktreeArchiveParamsDtoCopyWith<$Res> { + __$WorktreeArchiveParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderConnectionIdParamsDto _self; - final $Res Function(_ProviderConnectionIdParamsDto) _then; + final _WorktreeArchiveParamsDto _self; + final $Res Function(_WorktreeArchiveParamsDto) _then; -/// Create a copy of ProviderConnectionIdParamsDto +/// Create a copy of WorktreeArchiveParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,}) { - return _then(_ProviderConnectionIdParamsDto( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String, +@override @pragma('vm:prefer-inline') $Res call({Object? worktreeId = null,Object? force = null,}) { + return _then(_WorktreeArchiveParamsDto( +worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String,force: null == force ? _self.force : force // ignore: cast_nullable_to_non_nullable +as bool, )); } @@ -2174,42 +2159,42 @@ as String, /// @nodoc -mixin _$ProviderModelParamsDto { +mixin _$AgentListParamsDto { - String get connectionId; String get modelId; -/// Create a copy of ProviderModelParamsDto + String? get worktreeId; +/// Create a copy of AgentListParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderModelParamsDtoCopyWith get copyWith => _$ProviderModelParamsDtoCopyWithImpl(this as ProviderModelParamsDto, _$identity); +$AgentListParamsDtoCopyWith get copyWith => _$AgentListParamsDtoCopyWithImpl(this as AgentListParamsDto, _$identity); - /// Serializes this ProviderModelParamsDto to a JSON map. + /// Serializes this AgentListParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderModelParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentListParamsDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId,modelId); +int get hashCode => Object.hash(runtimeType,worktreeId); @override String toString() { - return 'ProviderModelParamsDto(connectionId: $connectionId, modelId: $modelId)'; + return 'AgentListParamsDto(worktreeId: $worktreeId)'; } } /// @nodoc -abstract mixin class $ProviderModelParamsDtoCopyWith<$Res> { - factory $ProviderModelParamsDtoCopyWith(ProviderModelParamsDto value, $Res Function(ProviderModelParamsDto) _then) = _$ProviderModelParamsDtoCopyWithImpl; +abstract mixin class $AgentListParamsDtoCopyWith<$Res> { + factory $AgentListParamsDtoCopyWith(AgentListParamsDto value, $Res Function(AgentListParamsDto) _then) = _$AgentListParamsDtoCopyWithImpl; @useResult $Res call({ - String connectionId, String modelId + String? worktreeId }); @@ -2217,28 +2202,27 @@ $Res call({ } /// @nodoc -class _$ProviderModelParamsDtoCopyWithImpl<$Res> - implements $ProviderModelParamsDtoCopyWith<$Res> { - _$ProviderModelParamsDtoCopyWithImpl(this._self, this._then); +class _$AgentListParamsDtoCopyWithImpl<$Res> + implements $AgentListParamsDtoCopyWith<$Res> { + _$AgentListParamsDtoCopyWithImpl(this._self, this._then); - final ProviderModelParamsDto _self; - final $Res Function(ProviderModelParamsDto) _then; + final AgentListParamsDto _self; + final $Res Function(AgentListParamsDto) _then; -/// Create a copy of ProviderModelParamsDto +/// Create a copy of AgentListParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,Object? modelId = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? worktreeId = freezed,}) { return _then(_self.copyWith( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable -as String, +worktreeId: freezed == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String?, )); } } -/// Adds pattern-matching-related methods to [ProviderModelParamsDto]. -extension ProviderModelParamsDtoPatterns on ProviderModelParamsDto { +/// Adds pattern-matching-related methods to [AgentListParamsDto]. +extension AgentListParamsDtoPatterns on AgentListParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -2251,10 +2235,10 @@ extension ProviderModelParamsDtoPatterns on ProviderModelParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderModelParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _AgentListParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderModelParamsDto() when $default != null: +case _AgentListParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -2273,10 +2257,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderModelParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _AgentListParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderModelParamsDto(): +case _AgentListParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -2294,10 +2278,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderModelParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentListParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderModelParamsDto() when $default != null: +case _AgentListParamsDto() when $default != null: return $default(_that);case _: return null; @@ -2315,10 +2299,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId, String modelId)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String? worktreeId)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderModelParamsDto() when $default != null: -return $default(_that.connectionId,_that.modelId);case _: +case _AgentListParamsDto() when $default != null: +return $default(_that.worktreeId);case _: return orElse(); } @@ -2336,10 +2320,10 @@ return $default(_that.connectionId,_that.modelId);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String connectionId, String modelId) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String? worktreeId) $default,) {final _that = this; switch (_that) { -case _ProviderModelParamsDto(): -return $default(_that.connectionId,_that.modelId);case _: +case _AgentListParamsDto(): +return $default(_that.worktreeId);case _: throw StateError('Unexpected subclass'); } @@ -2356,10 +2340,10 @@ return $default(_that.connectionId,_that.modelId);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId, String modelId)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? worktreeId)? $default,) {final _that = this; switch (_that) { -case _ProviderModelParamsDto() when $default != null: -return $default(_that.connectionId,_that.modelId);case _: +case _AgentListParamsDto() when $default != null: +return $default(_that.worktreeId);case _: return null; } @@ -2370,47 +2354,46 @@ return $default(_that.connectionId,_that.modelId);case _: /// @nodoc @JsonSerializable() -class _ProviderModelParamsDto implements ProviderModelParamsDto { - const _ProviderModelParamsDto({required this.connectionId, required this.modelId}); - factory _ProviderModelParamsDto.fromJson(Map json) => _$ProviderModelParamsDtoFromJson(json); +class _AgentListParamsDto implements AgentListParamsDto { + const _AgentListParamsDto({this.worktreeId}); + factory _AgentListParamsDto.fromJson(Map json) => _$AgentListParamsDtoFromJson(json); -@override final String connectionId; -@override final String modelId; +@override final String? worktreeId; -/// Create a copy of ProviderModelParamsDto +/// Create a copy of AgentListParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderModelParamsDtoCopyWith<_ProviderModelParamsDto> get copyWith => __$ProviderModelParamsDtoCopyWithImpl<_ProviderModelParamsDto>(this, _$identity); +_$AgentListParamsDtoCopyWith<_AgentListParamsDto> get copyWith => __$AgentListParamsDtoCopyWithImpl<_AgentListParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderModelParamsDtoToJson(this, ); + return _$AgentListParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderModelParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentListParamsDto&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId,modelId); +int get hashCode => Object.hash(runtimeType,worktreeId); @override String toString() { - return 'ProviderModelParamsDto(connectionId: $connectionId, modelId: $modelId)'; + return 'AgentListParamsDto(worktreeId: $worktreeId)'; } } /// @nodoc -abstract mixin class _$ProviderModelParamsDtoCopyWith<$Res> implements $ProviderModelParamsDtoCopyWith<$Res> { - factory _$ProviderModelParamsDtoCopyWith(_ProviderModelParamsDto value, $Res Function(_ProviderModelParamsDto) _then) = __$ProviderModelParamsDtoCopyWithImpl; +abstract mixin class _$AgentListParamsDtoCopyWith<$Res> implements $AgentListParamsDtoCopyWith<$Res> { + factory _$AgentListParamsDtoCopyWith(_AgentListParamsDto value, $Res Function(_AgentListParamsDto) _then) = __$AgentListParamsDtoCopyWithImpl; @override @useResult $Res call({ - String connectionId, String modelId + String? worktreeId }); @@ -2418,20 +2401,19 @@ $Res call({ } /// @nodoc -class __$ProviderModelParamsDtoCopyWithImpl<$Res> - implements _$ProviderModelParamsDtoCopyWith<$Res> { - __$ProviderModelParamsDtoCopyWithImpl(this._self, this._then); +class __$AgentListParamsDtoCopyWithImpl<$Res> + implements _$AgentListParamsDtoCopyWith<$Res> { + __$AgentListParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderModelParamsDto _self; - final $Res Function(_ProviderModelParamsDto) _then; + final _AgentListParamsDto _self; + final $Res Function(_AgentListParamsDto) _then; -/// Create a copy of ProviderModelParamsDto +/// Create a copy of AgentListParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,Object? modelId = null,}) { - return _then(_ProviderModelParamsDto( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable -as String, +@override @pragma('vm:prefer-inline') $Res call({Object? worktreeId = freezed,}) { + return _then(_AgentListParamsDto( +worktreeId: freezed == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String?, )); } @@ -2440,42 +2422,42 @@ as String, /// @nodoc -mixin _$ProviderAuthStartParamsDto { +mixin _$AgentCreateParamsDto { - String get definitionId; String get methodId; bool get makeDefault; -/// Create a copy of ProviderAuthStartParamsDto + String get id; String get worktreeId; String get title; String get providerConnectionId; String get model; String get reasoningEffort; PermissionMode get permissionMode; +/// Create a copy of AgentCreateParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderAuthStartParamsDtoCopyWith get copyWith => _$ProviderAuthStartParamsDtoCopyWithImpl(this as ProviderAuthStartParamsDto, _$identity); +$AgentCreateParamsDtoCopyWith get copyWith => _$AgentCreateParamsDtoCopyWithImpl(this as AgentCreateParamsDto, _$identity); - /// Serializes this ProviderAuthStartParamsDto to a JSON map. + /// Serializes this AgentCreateParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderAuthStartParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.methodId, methodId) || other.methodId == methodId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,definitionId,methodId,makeDefault); +int get hashCode => Object.hash(runtimeType,id,worktreeId,title,providerConnectionId,model,reasoningEffort,permissionMode); @override String toString() { - return 'ProviderAuthStartParamsDto(definitionId: $definitionId, methodId: $methodId, makeDefault: $makeDefault)'; + return 'AgentCreateParamsDto(id: $id, worktreeId: $worktreeId, title: $title, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort, permissionMode: $permissionMode)'; } } /// @nodoc -abstract mixin class $ProviderAuthStartParamsDtoCopyWith<$Res> { - factory $ProviderAuthStartParamsDtoCopyWith(ProviderAuthStartParamsDto value, $Res Function(ProviderAuthStartParamsDto) _then) = _$ProviderAuthStartParamsDtoCopyWithImpl; +abstract mixin class $AgentCreateParamsDtoCopyWith<$Res> { + factory $AgentCreateParamsDtoCopyWith(AgentCreateParamsDto value, $Res Function(AgentCreateParamsDto) _then) = _$AgentCreateParamsDtoCopyWithImpl; @useResult $Res call({ - String definitionId, String methodId, bool makeDefault + String id, String worktreeId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode }); @@ -2483,29 +2465,33 @@ $Res call({ } /// @nodoc -class _$ProviderAuthStartParamsDtoCopyWithImpl<$Res> - implements $ProviderAuthStartParamsDtoCopyWith<$Res> { - _$ProviderAuthStartParamsDtoCopyWithImpl(this._self, this._then); +class _$AgentCreateParamsDtoCopyWithImpl<$Res> + implements $AgentCreateParamsDtoCopyWith<$Res> { + _$AgentCreateParamsDtoCopyWithImpl(this._self, this._then); - final ProviderAuthStartParamsDto _self; - final $Res Function(ProviderAuthStartParamsDto) _then; + final AgentCreateParamsDto _self; + final $Res Function(AgentCreateParamsDto) _then; -/// Create a copy of ProviderAuthStartParamsDto +/// Create a copy of AgentCreateParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? definitionId = null,Object? methodId = null,Object? makeDefault = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? worktreeId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,Object? permissionMode = null,}) { return _then(_self.copyWith( -definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable -as String,methodId: null == methodId ? _self.methodId : methodId // ignore: cast_nullable_to_non_nullable -as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool, +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable +as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable +as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable +as String,permissionMode: null == permissionMode ? _self.permissionMode : permissionMode // ignore: cast_nullable_to_non_nullable +as PermissionMode, )); } } -/// Adds pattern-matching-related methods to [ProviderAuthStartParamsDto]. -extension ProviderAuthStartParamsDtoPatterns on ProviderAuthStartParamsDto { +/// Adds pattern-matching-related methods to [AgentCreateParamsDto]. +extension AgentCreateParamsDtoPatterns on AgentCreateParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -2518,10 +2504,10 @@ extension ProviderAuthStartParamsDtoPatterns on ProviderAuthStartParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderAuthStartParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _AgentCreateParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderAuthStartParamsDto() when $default != null: +case _AgentCreateParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -2540,10 +2526,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderAuthStartParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _AgentCreateParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderAuthStartParamsDto(): +case _AgentCreateParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -2561,10 +2547,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderAuthStartParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentCreateParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderAuthStartParamsDto() when $default != null: +case _AgentCreateParamsDto() when $default != null: return $default(_that);case _: return null; @@ -2582,10 +2568,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String definitionId, String methodId, bool makeDefault)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String worktreeId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderAuthStartParamsDto() when $default != null: -return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: +case _AgentCreateParamsDto() when $default != null: +return $default(_that.id,_that.worktreeId,_that.title,_that.providerConnectionId,_that.model,_that.reasoningEffort,_that.permissionMode);case _: return orElse(); } @@ -2603,10 +2589,10 @@ return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String definitionId, String methodId, bool makeDefault) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String worktreeId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode) $default,) {final _that = this; switch (_that) { -case _ProviderAuthStartParamsDto(): -return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: +case _AgentCreateParamsDto(): +return $default(_that.id,_that.worktreeId,_that.title,_that.providerConnectionId,_that.model,_that.reasoningEffort,_that.permissionMode);case _: throw StateError('Unexpected subclass'); } @@ -2623,10 +2609,10 @@ return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String definitionId, String methodId, bool makeDefault)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String worktreeId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode)? $default,) {final _that = this; switch (_that) { -case _ProviderAuthStartParamsDto() when $default != null: -return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: +case _AgentCreateParamsDto() when $default != null: +return $default(_that.id,_that.worktreeId,_that.title,_that.providerConnectionId,_that.model,_that.reasoningEffort,_that.permissionMode);case _: return null; } @@ -2637,48 +2623,52 @@ return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: /// @nodoc @JsonSerializable() -class _ProviderAuthStartParamsDto implements ProviderAuthStartParamsDto { - const _ProviderAuthStartParamsDto({required this.definitionId, required this.methodId, required this.makeDefault}); - factory _ProviderAuthStartParamsDto.fromJson(Map json) => _$ProviderAuthStartParamsDtoFromJson(json); +class _AgentCreateParamsDto implements AgentCreateParamsDto { + const _AgentCreateParamsDto({required this.id, required this.worktreeId, required this.title, required this.providerConnectionId, required this.model, required this.reasoningEffort, required this.permissionMode}); + factory _AgentCreateParamsDto.fromJson(Map json) => _$AgentCreateParamsDtoFromJson(json); -@override final String definitionId; -@override final String methodId; -@override final bool makeDefault; +@override final String id; +@override final String worktreeId; +@override final String title; +@override final String providerConnectionId; +@override final String model; +@override final String reasoningEffort; +@override final PermissionMode permissionMode; -/// Create a copy of ProviderAuthStartParamsDto +/// Create a copy of AgentCreateParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderAuthStartParamsDtoCopyWith<_ProviderAuthStartParamsDto> get copyWith => __$ProviderAuthStartParamsDtoCopyWithImpl<_ProviderAuthStartParamsDto>(this, _$identity); +_$AgentCreateParamsDtoCopyWith<_AgentCreateParamsDto> get copyWith => __$AgentCreateParamsDtoCopyWithImpl<_AgentCreateParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderAuthStartParamsDtoToJson(this, ); + return _$AgentCreateParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderAuthStartParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.methodId, methodId) || other.methodId == methodId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.worktreeId, worktreeId) || other.worktreeId == worktreeId)&&(identical(other.title, title) || other.title == title)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)&&(identical(other.permissionMode, permissionMode) || other.permissionMode == permissionMode)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,definitionId,methodId,makeDefault); +int get hashCode => Object.hash(runtimeType,id,worktreeId,title,providerConnectionId,model,reasoningEffort,permissionMode); @override String toString() { - return 'ProviderAuthStartParamsDto(definitionId: $definitionId, methodId: $methodId, makeDefault: $makeDefault)'; + return 'AgentCreateParamsDto(id: $id, worktreeId: $worktreeId, title: $title, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort, permissionMode: $permissionMode)'; } } /// @nodoc -abstract mixin class _$ProviderAuthStartParamsDtoCopyWith<$Res> implements $ProviderAuthStartParamsDtoCopyWith<$Res> { - factory _$ProviderAuthStartParamsDtoCopyWith(_ProviderAuthStartParamsDto value, $Res Function(_ProviderAuthStartParamsDto) _then) = __$ProviderAuthStartParamsDtoCopyWithImpl; +abstract mixin class _$AgentCreateParamsDtoCopyWith<$Res> implements $AgentCreateParamsDtoCopyWith<$Res> { + factory _$AgentCreateParamsDtoCopyWith(_AgentCreateParamsDto value, $Res Function(_AgentCreateParamsDto) _then) = __$AgentCreateParamsDtoCopyWithImpl; @override @useResult $Res call({ - String definitionId, String methodId, bool makeDefault + String id, String worktreeId, String title, String providerConnectionId, String model, String reasoningEffort, PermissionMode permissionMode }); @@ -2686,21 +2676,25 @@ $Res call({ } /// @nodoc -class __$ProviderAuthStartParamsDtoCopyWithImpl<$Res> - implements _$ProviderAuthStartParamsDtoCopyWith<$Res> { - __$ProviderAuthStartParamsDtoCopyWithImpl(this._self, this._then); +class __$AgentCreateParamsDtoCopyWithImpl<$Res> + implements _$AgentCreateParamsDtoCopyWith<$Res> { + __$AgentCreateParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderAuthStartParamsDto _self; - final $Res Function(_ProviderAuthStartParamsDto) _then; + final _AgentCreateParamsDto _self; + final $Res Function(_AgentCreateParamsDto) _then; -/// Create a copy of ProviderAuthStartParamsDto +/// Create a copy of AgentCreateParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? definitionId = null,Object? methodId = null,Object? makeDefault = null,}) { - return _then(_ProviderAuthStartParamsDto( -definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable -as String,methodId: null == methodId ? _self.methodId : methodId // ignore: cast_nullable_to_non_nullable -as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool, +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? worktreeId = null,Object? title = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,Object? permissionMode = null,}) { + return _then(_AgentCreateParamsDto( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,worktreeId: null == worktreeId ? _self.worktreeId : worktreeId // ignore: cast_nullable_to_non_nullable +as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable +as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable +as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable +as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable +as String,permissionMode: null == permissionMode ? _self.permissionMode : permissionMode // ignore: cast_nullable_to_non_nullable +as PermissionMode, )); } @@ -2709,42 +2703,42 @@ as bool, /// @nodoc -mixin _$ProviderAuthAttemptParamsDto { +mixin _$AgentConfigurationUpdateParamsDto { - String get attemptId; -/// Create a copy of ProviderAuthAttemptParamsDto + String get agentId; String get providerConnectionId; String get model; String get reasoningEffort; +/// Create a copy of AgentConfigurationUpdateParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderAuthAttemptParamsDtoCopyWith get copyWith => _$ProviderAuthAttemptParamsDtoCopyWithImpl(this as ProviderAuthAttemptParamsDto, _$identity); +$AgentConfigurationUpdateParamsDtoCopyWith get copyWith => _$AgentConfigurationUpdateParamsDtoCopyWithImpl(this as AgentConfigurationUpdateParamsDto, _$identity); - /// Serializes this ProviderAuthAttemptParamsDto to a JSON map. + /// Serializes this AgentConfigurationUpdateParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderAuthAttemptParamsDto&&(identical(other.attemptId, attemptId) || other.attemptId == attemptId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentConfigurationUpdateParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,attemptId); +int get hashCode => Object.hash(runtimeType,agentId,providerConnectionId,model,reasoningEffort); @override String toString() { - return 'ProviderAuthAttemptParamsDto(attemptId: $attemptId)'; + return 'AgentConfigurationUpdateParamsDto(agentId: $agentId, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort)'; } } /// @nodoc -abstract mixin class $ProviderAuthAttemptParamsDtoCopyWith<$Res> { - factory $ProviderAuthAttemptParamsDtoCopyWith(ProviderAuthAttemptParamsDto value, $Res Function(ProviderAuthAttemptParamsDto) _then) = _$ProviderAuthAttemptParamsDtoCopyWithImpl; +abstract mixin class $AgentConfigurationUpdateParamsDtoCopyWith<$Res> { + factory $AgentConfigurationUpdateParamsDtoCopyWith(AgentConfigurationUpdateParamsDto value, $Res Function(AgentConfigurationUpdateParamsDto) _then) = _$AgentConfigurationUpdateParamsDtoCopyWithImpl; @useResult $Res call({ - String attemptId + String agentId, String providerConnectionId, String model, String reasoningEffort }); @@ -2752,18 +2746,21 @@ $Res call({ } /// @nodoc -class _$ProviderAuthAttemptParamsDtoCopyWithImpl<$Res> - implements $ProviderAuthAttemptParamsDtoCopyWith<$Res> { - _$ProviderAuthAttemptParamsDtoCopyWithImpl(this._self, this._then); +class _$AgentConfigurationUpdateParamsDtoCopyWithImpl<$Res> + implements $AgentConfigurationUpdateParamsDtoCopyWith<$Res> { + _$AgentConfigurationUpdateParamsDtoCopyWithImpl(this._self, this._then); - final ProviderAuthAttemptParamsDto _self; - final $Res Function(ProviderAuthAttemptParamsDto) _then; + final AgentConfigurationUpdateParamsDto _self; + final $Res Function(AgentConfigurationUpdateParamsDto) _then; -/// Create a copy of ProviderAuthAttemptParamsDto +/// Create a copy of AgentConfigurationUpdateParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? attemptId = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,}) { return _then(_self.copyWith( -attemptId: null == attemptId ? _self.attemptId : attemptId // ignore: cast_nullable_to_non_nullable +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable +as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable +as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable as String, )); } @@ -2771,8 +2768,8 @@ as String, } -/// Adds pattern-matching-related methods to [ProviderAuthAttemptParamsDto]. -extension ProviderAuthAttemptParamsDtoPatterns on ProviderAuthAttemptParamsDto { +/// Adds pattern-matching-related methods to [AgentConfigurationUpdateParamsDto]. +extension AgentConfigurationUpdateParamsDtoPatterns on AgentConfigurationUpdateParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -2785,10 +2782,10 @@ extension ProviderAuthAttemptParamsDtoPatterns on ProviderAuthAttemptParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderAuthAttemptParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _AgentConfigurationUpdateParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderAuthAttemptParamsDto() when $default != null: +case _AgentConfigurationUpdateParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -2807,10 +2804,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderAuthAttemptParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _AgentConfigurationUpdateParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderAuthAttemptParamsDto(): +case _AgentConfigurationUpdateParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -2828,10 +2825,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderAuthAttemptParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentConfigurationUpdateParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderAuthAttemptParamsDto() when $default != null: +case _AgentConfigurationUpdateParamsDto() when $default != null: return $default(_that);case _: return null; @@ -2849,10 +2846,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String attemptId)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId, String providerConnectionId, String model, String reasoningEffort)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderAuthAttemptParamsDto() when $default != null: -return $default(_that.attemptId);case _: +case _AgentConfigurationUpdateParamsDto() when $default != null: +return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reasoningEffort);case _: return orElse(); } @@ -2870,10 +2867,10 @@ return $default(_that.attemptId);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String attemptId) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String agentId, String providerConnectionId, String model, String reasoningEffort) $default,) {final _that = this; switch (_that) { -case _ProviderAuthAttemptParamsDto(): -return $default(_that.attemptId);case _: +case _AgentConfigurationUpdateParamsDto(): +return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reasoningEffort);case _: throw StateError('Unexpected subclass'); } @@ -2890,10 +2887,10 @@ return $default(_that.attemptId);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String attemptId)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId, String providerConnectionId, String model, String reasoningEffort)? $default,) {final _that = this; switch (_that) { -case _ProviderAuthAttemptParamsDto() when $default != null: -return $default(_that.attemptId);case _: +case _AgentConfigurationUpdateParamsDto() when $default != null: +return $default(_that.agentId,_that.providerConnectionId,_that.model,_that.reasoningEffort);case _: return null; } @@ -2904,46 +2901,49 @@ return $default(_that.attemptId);case _: /// @nodoc @JsonSerializable() -class _ProviderAuthAttemptParamsDto implements ProviderAuthAttemptParamsDto { - const _ProviderAuthAttemptParamsDto({required this.attemptId}); - factory _ProviderAuthAttemptParamsDto.fromJson(Map json) => _$ProviderAuthAttemptParamsDtoFromJson(json); +class _AgentConfigurationUpdateParamsDto implements AgentConfigurationUpdateParamsDto { + const _AgentConfigurationUpdateParamsDto({required this.agentId, required this.providerConnectionId, required this.model, required this.reasoningEffort}); + factory _AgentConfigurationUpdateParamsDto.fromJson(Map json) => _$AgentConfigurationUpdateParamsDtoFromJson(json); -@override final String attemptId; +@override final String agentId; +@override final String providerConnectionId; +@override final String model; +@override final String reasoningEffort; -/// Create a copy of ProviderAuthAttemptParamsDto +/// Create a copy of AgentConfigurationUpdateParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderAuthAttemptParamsDtoCopyWith<_ProviderAuthAttemptParamsDto> get copyWith => __$ProviderAuthAttemptParamsDtoCopyWithImpl<_ProviderAuthAttemptParamsDto>(this, _$identity); +_$AgentConfigurationUpdateParamsDtoCopyWith<_AgentConfigurationUpdateParamsDto> get copyWith => __$AgentConfigurationUpdateParamsDtoCopyWithImpl<_AgentConfigurationUpdateParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderAuthAttemptParamsDtoToJson(this, ); + return _$AgentConfigurationUpdateParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderAuthAttemptParamsDto&&(identical(other.attemptId, attemptId) || other.attemptId == attemptId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentConfigurationUpdateParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.providerConnectionId, providerConnectionId) || other.providerConnectionId == providerConnectionId)&&(identical(other.model, model) || other.model == model)&&(identical(other.reasoningEffort, reasoningEffort) || other.reasoningEffort == reasoningEffort)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,attemptId); +int get hashCode => Object.hash(runtimeType,agentId,providerConnectionId,model,reasoningEffort); @override String toString() { - return 'ProviderAuthAttemptParamsDto(attemptId: $attemptId)'; + return 'AgentConfigurationUpdateParamsDto(agentId: $agentId, providerConnectionId: $providerConnectionId, model: $model, reasoningEffort: $reasoningEffort)'; } } /// @nodoc -abstract mixin class _$ProviderAuthAttemptParamsDtoCopyWith<$Res> implements $ProviderAuthAttemptParamsDtoCopyWith<$Res> { - factory _$ProviderAuthAttemptParamsDtoCopyWith(_ProviderAuthAttemptParamsDto value, $Res Function(_ProviderAuthAttemptParamsDto) _then) = __$ProviderAuthAttemptParamsDtoCopyWithImpl; +abstract mixin class _$AgentConfigurationUpdateParamsDtoCopyWith<$Res> implements $AgentConfigurationUpdateParamsDtoCopyWith<$Res> { + factory _$AgentConfigurationUpdateParamsDtoCopyWith(_AgentConfigurationUpdateParamsDto value, $Res Function(_AgentConfigurationUpdateParamsDto) _then) = __$AgentConfigurationUpdateParamsDtoCopyWithImpl; @override @useResult $Res call({ - String attemptId + String agentId, String providerConnectionId, String model, String reasoningEffort }); @@ -2951,18 +2951,21 @@ $Res call({ } /// @nodoc -class __$ProviderAuthAttemptParamsDtoCopyWithImpl<$Res> - implements _$ProviderAuthAttemptParamsDtoCopyWith<$Res> { - __$ProviderAuthAttemptParamsDtoCopyWithImpl(this._self, this._then); +class __$AgentConfigurationUpdateParamsDtoCopyWithImpl<$Res> + implements _$AgentConfigurationUpdateParamsDtoCopyWith<$Res> { + __$AgentConfigurationUpdateParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderAuthAttemptParamsDto _self; - final $Res Function(_ProviderAuthAttemptParamsDto) _then; + final _AgentConfigurationUpdateParamsDto _self; + final $Res Function(_AgentConfigurationUpdateParamsDto) _then; -/// Create a copy of ProviderAuthAttemptParamsDto +/// Create a copy of AgentConfigurationUpdateParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? attemptId = null,}) { - return _then(_ProviderAuthAttemptParamsDto( -attemptId: null == attemptId ? _self.attemptId : attemptId // ignore: cast_nullable_to_non_nullable +@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,Object? providerConnectionId = null,Object? model = null,Object? reasoningEffort = null,}) { + return _then(_AgentConfigurationUpdateParamsDto( +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String,providerConnectionId: null == providerConnectionId ? _self.providerConnectionId : providerConnectionId // ignore: cast_nullable_to_non_nullable +as String,model: null == model ? _self.model : model // ignore: cast_nullable_to_non_nullable +as String,reasoningEffort: null == reasoningEffort ? _self.reasoningEffort : reasoningEffort // ignore: cast_nullable_to_non_nullable as String, )); } @@ -2972,42 +2975,42 @@ as String, /// @nodoc -mixin _$ProviderDefaultSetParamsDto { +mixin _$ProviderConnectApiKeyParamsDto { - String get connectionId; -/// Create a copy of ProviderDefaultSetParamsDto + String get definitionId; String get apiKey; bool get makeDefault; +/// Create a copy of ProviderConnectApiKeyParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderDefaultSetParamsDtoCopyWith get copyWith => _$ProviderDefaultSetParamsDtoCopyWithImpl(this as ProviderDefaultSetParamsDto, _$identity); +$ProviderConnectApiKeyParamsDtoCopyWith get copyWith => _$ProviderConnectApiKeyParamsDtoCopyWithImpl(this as ProviderConnectApiKeyParamsDto, _$identity); - /// Serializes this ProviderDefaultSetParamsDto to a JSON map. + /// Serializes this ProviderConnectApiKeyParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderDefaultSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderConnectApiKeyParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId); +int get hashCode => Object.hash(runtimeType,definitionId,apiKey,makeDefault); @override String toString() { - return 'ProviderDefaultSetParamsDto(connectionId: $connectionId)'; + return 'ProviderConnectApiKeyParamsDto(definitionId: $definitionId, apiKey: $apiKey, makeDefault: $makeDefault)'; } } /// @nodoc -abstract mixin class $ProviderDefaultSetParamsDtoCopyWith<$Res> { - factory $ProviderDefaultSetParamsDtoCopyWith(ProviderDefaultSetParamsDto value, $Res Function(ProviderDefaultSetParamsDto) _then) = _$ProviderDefaultSetParamsDtoCopyWithImpl; +abstract mixin class $ProviderConnectApiKeyParamsDtoCopyWith<$Res> { + factory $ProviderConnectApiKeyParamsDtoCopyWith(ProviderConnectApiKeyParamsDto value, $Res Function(ProviderConnectApiKeyParamsDto) _then) = _$ProviderConnectApiKeyParamsDtoCopyWithImpl; @useResult $Res call({ - String connectionId + String definitionId, String apiKey, bool makeDefault }); @@ -3015,27 +3018,29 @@ $Res call({ } /// @nodoc -class _$ProviderDefaultSetParamsDtoCopyWithImpl<$Res> - implements $ProviderDefaultSetParamsDtoCopyWith<$Res> { - _$ProviderDefaultSetParamsDtoCopyWithImpl(this._self, this._then); +class _$ProviderConnectApiKeyParamsDtoCopyWithImpl<$Res> + implements $ProviderConnectApiKeyParamsDtoCopyWith<$Res> { + _$ProviderConnectApiKeyParamsDtoCopyWithImpl(this._self, this._then); - final ProviderDefaultSetParamsDto _self; - final $Res Function(ProviderDefaultSetParamsDto) _then; + final ProviderConnectApiKeyParamsDto _self; + final $Res Function(ProviderConnectApiKeyParamsDto) _then; -/// Create a copy of ProviderDefaultSetParamsDto +/// Create a copy of ProviderConnectApiKeyParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? definitionId = null,Object? apiKey = null,Object? makeDefault = null,}) { return _then(_self.copyWith( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String, +definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable +as String,apiKey: null == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable +as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool, )); } } -/// Adds pattern-matching-related methods to [ProviderDefaultSetParamsDto]. -extension ProviderDefaultSetParamsDtoPatterns on ProviderDefaultSetParamsDto { +/// Adds pattern-matching-related methods to [ProviderConnectApiKeyParamsDto]. +extension ProviderConnectApiKeyParamsDtoPatterns on ProviderConnectApiKeyParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -3048,10 +3053,10 @@ extension ProviderDefaultSetParamsDtoPatterns on ProviderDefaultSetParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderDefaultSetParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderConnectApiKeyParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderDefaultSetParamsDto() when $default != null: +case _ProviderConnectApiKeyParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -3070,10 +3075,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderDefaultSetParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _ProviderConnectApiKeyParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderDefaultSetParamsDto(): +case _ProviderConnectApiKeyParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -3091,10 +3096,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderDefaultSetParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderConnectApiKeyParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderDefaultSetParamsDto() when $default != null: +case _ProviderConnectApiKeyParamsDto() when $default != null: return $default(_that);case _: return null; @@ -3112,10 +3117,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String definitionId, String apiKey, bool makeDefault)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderDefaultSetParamsDto() when $default != null: -return $default(_that.connectionId);case _: +case _ProviderConnectApiKeyParamsDto() when $default != null: +return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: return orElse(); } @@ -3133,10 +3138,10 @@ return $default(_that.connectionId);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String connectionId) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String definitionId, String apiKey, bool makeDefault) $default,) {final _that = this; switch (_that) { -case _ProviderDefaultSetParamsDto(): -return $default(_that.connectionId);case _: +case _ProviderConnectApiKeyParamsDto(): +return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: throw StateError('Unexpected subclass'); } @@ -3153,10 +3158,10 @@ return $default(_that.connectionId);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String definitionId, String apiKey, bool makeDefault)? $default,) {final _that = this; switch (_that) { -case _ProviderDefaultSetParamsDto() when $default != null: -return $default(_that.connectionId);case _: +case _ProviderConnectApiKeyParamsDto() when $default != null: +return $default(_that.definitionId,_that.apiKey,_that.makeDefault);case _: return null; } @@ -3167,46 +3172,48 @@ return $default(_that.connectionId);case _: /// @nodoc @JsonSerializable() -class _ProviderDefaultSetParamsDto implements ProviderDefaultSetParamsDto { - const _ProviderDefaultSetParamsDto({required this.connectionId}); - factory _ProviderDefaultSetParamsDto.fromJson(Map json) => _$ProviderDefaultSetParamsDtoFromJson(json); +class _ProviderConnectApiKeyParamsDto implements ProviderConnectApiKeyParamsDto { + const _ProviderConnectApiKeyParamsDto({required this.definitionId, required this.apiKey, required this.makeDefault}); + factory _ProviderConnectApiKeyParamsDto.fromJson(Map json) => _$ProviderConnectApiKeyParamsDtoFromJson(json); -@override final String connectionId; +@override final String definitionId; +@override final String apiKey; +@override final bool makeDefault; -/// Create a copy of ProviderDefaultSetParamsDto +/// Create a copy of ProviderConnectApiKeyParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderDefaultSetParamsDtoCopyWith<_ProviderDefaultSetParamsDto> get copyWith => __$ProviderDefaultSetParamsDtoCopyWithImpl<_ProviderDefaultSetParamsDto>(this, _$identity); +_$ProviderConnectApiKeyParamsDtoCopyWith<_ProviderConnectApiKeyParamsDto> get copyWith => __$ProviderConnectApiKeyParamsDtoCopyWithImpl<_ProviderConnectApiKeyParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderDefaultSetParamsDtoToJson(this, ); + return _$ProviderConnectApiKeyParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderDefaultSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderConnectApiKeyParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId); +int get hashCode => Object.hash(runtimeType,definitionId,apiKey,makeDefault); @override String toString() { - return 'ProviderDefaultSetParamsDto(connectionId: $connectionId)'; + return 'ProviderConnectApiKeyParamsDto(definitionId: $definitionId, apiKey: $apiKey, makeDefault: $makeDefault)'; } } /// @nodoc -abstract mixin class _$ProviderDefaultSetParamsDtoCopyWith<$Res> implements $ProviderDefaultSetParamsDtoCopyWith<$Res> { - factory _$ProviderDefaultSetParamsDtoCopyWith(_ProviderDefaultSetParamsDto value, $Res Function(_ProviderDefaultSetParamsDto) _then) = __$ProviderDefaultSetParamsDtoCopyWithImpl; +abstract mixin class _$ProviderConnectApiKeyParamsDtoCopyWith<$Res> implements $ProviderConnectApiKeyParamsDtoCopyWith<$Res> { + factory _$ProviderConnectApiKeyParamsDtoCopyWith(_ProviderConnectApiKeyParamsDto value, $Res Function(_ProviderConnectApiKeyParamsDto) _then) = __$ProviderConnectApiKeyParamsDtoCopyWithImpl; @override @useResult $Res call({ - String connectionId + String definitionId, String apiKey, bool makeDefault }); @@ -3214,19 +3221,21 @@ $Res call({ } /// @nodoc -class __$ProviderDefaultSetParamsDtoCopyWithImpl<$Res> - implements _$ProviderDefaultSetParamsDtoCopyWith<$Res> { - __$ProviderDefaultSetParamsDtoCopyWithImpl(this._self, this._then); +class __$ProviderConnectApiKeyParamsDtoCopyWithImpl<$Res> + implements _$ProviderConnectApiKeyParamsDtoCopyWith<$Res> { + __$ProviderConnectApiKeyParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderDefaultSetParamsDto _self; - final $Res Function(_ProviderDefaultSetParamsDto) _then; + final _ProviderConnectApiKeyParamsDto _self; + final $Res Function(_ProviderConnectApiKeyParamsDto) _then; -/// Create a copy of ProviderDefaultSetParamsDto +/// Create a copy of ProviderConnectApiKeyParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,}) { - return _then(_ProviderDefaultSetParamsDto( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String, +@override @pragma('vm:prefer-inline') $Res call({Object? definitionId = null,Object? apiKey = null,Object? makeDefault = null,}) { + return _then(_ProviderConnectApiKeyParamsDto( +definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable +as String,apiKey: null == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable +as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool, )); } @@ -3235,42 +3244,42 @@ as String, /// @nodoc -mixin _$ProviderDefaultModelSetParamsDto { +mixin _$ProviderConnectNoneParamsDto { - String get connectionId; String get modelId; -/// Create a copy of ProviderDefaultModelSetParamsDto + String get definitionId; bool get makeDefault; +/// Create a copy of ProviderConnectNoneParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderDefaultModelSetParamsDtoCopyWith get copyWith => _$ProviderDefaultModelSetParamsDtoCopyWithImpl(this as ProviderDefaultModelSetParamsDto, _$identity); +$ProviderConnectNoneParamsDtoCopyWith get copyWith => _$ProviderConnectNoneParamsDtoCopyWithImpl(this as ProviderConnectNoneParamsDto, _$identity); - /// Serializes this ProviderDefaultModelSetParamsDto to a JSON map. + /// Serializes this ProviderConnectNoneParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderDefaultModelSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderConnectNoneParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId,modelId); +int get hashCode => Object.hash(runtimeType,definitionId,makeDefault); @override String toString() { - return 'ProviderDefaultModelSetParamsDto(connectionId: $connectionId, modelId: $modelId)'; + return 'ProviderConnectNoneParamsDto(definitionId: $definitionId, makeDefault: $makeDefault)'; } } /// @nodoc -abstract mixin class $ProviderDefaultModelSetParamsDtoCopyWith<$Res> { - factory $ProviderDefaultModelSetParamsDtoCopyWith(ProviderDefaultModelSetParamsDto value, $Res Function(ProviderDefaultModelSetParamsDto) _then) = _$ProviderDefaultModelSetParamsDtoCopyWithImpl; +abstract mixin class $ProviderConnectNoneParamsDtoCopyWith<$Res> { + factory $ProviderConnectNoneParamsDtoCopyWith(ProviderConnectNoneParamsDto value, $Res Function(ProviderConnectNoneParamsDto) _then) = _$ProviderConnectNoneParamsDtoCopyWithImpl; @useResult $Res call({ - String connectionId, String modelId + String definitionId, bool makeDefault }); @@ -3278,28 +3287,28 @@ $Res call({ } /// @nodoc -class _$ProviderDefaultModelSetParamsDtoCopyWithImpl<$Res> - implements $ProviderDefaultModelSetParamsDtoCopyWith<$Res> { - _$ProviderDefaultModelSetParamsDtoCopyWithImpl(this._self, this._then); +class _$ProviderConnectNoneParamsDtoCopyWithImpl<$Res> + implements $ProviderConnectNoneParamsDtoCopyWith<$Res> { + _$ProviderConnectNoneParamsDtoCopyWithImpl(this._self, this._then); - final ProviderDefaultModelSetParamsDto _self; - final $Res Function(ProviderDefaultModelSetParamsDto) _then; + final ProviderConnectNoneParamsDto _self; + final $Res Function(ProviderConnectNoneParamsDto) _then; -/// Create a copy of ProviderDefaultModelSetParamsDto +/// Create a copy of ProviderConnectNoneParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,Object? modelId = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? definitionId = null,Object? makeDefault = null,}) { return _then(_self.copyWith( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable -as String, +definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable +as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool, )); } } -/// Adds pattern-matching-related methods to [ProviderDefaultModelSetParamsDto]. -extension ProviderDefaultModelSetParamsDtoPatterns on ProviderDefaultModelSetParamsDto { +/// Adds pattern-matching-related methods to [ProviderConnectNoneParamsDto]. +extension ProviderConnectNoneParamsDtoPatterns on ProviderConnectNoneParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -3312,10 +3321,10 @@ extension ProviderDefaultModelSetParamsDtoPatterns on ProviderDefaultModelSetPar /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderDefaultModelSetParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderConnectNoneParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderDefaultModelSetParamsDto() when $default != null: +case _ProviderConnectNoneParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -3334,10 +3343,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderDefaultModelSetParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _ProviderConnectNoneParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderDefaultModelSetParamsDto(): +case _ProviderConnectNoneParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -3355,10 +3364,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderDefaultModelSetParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderConnectNoneParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderDefaultModelSetParamsDto() when $default != null: +case _ProviderConnectNoneParamsDto() when $default != null: return $default(_that);case _: return null; @@ -3376,10 +3385,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId, String modelId)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String definitionId, bool makeDefault)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderDefaultModelSetParamsDto() when $default != null: -return $default(_that.connectionId,_that.modelId);case _: +case _ProviderConnectNoneParamsDto() when $default != null: +return $default(_that.definitionId,_that.makeDefault);case _: return orElse(); } @@ -3397,10 +3406,10 @@ return $default(_that.connectionId,_that.modelId);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String connectionId, String modelId) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String definitionId, bool makeDefault) $default,) {final _that = this; switch (_that) { -case _ProviderDefaultModelSetParamsDto(): -return $default(_that.connectionId,_that.modelId);case _: +case _ProviderConnectNoneParamsDto(): +return $default(_that.definitionId,_that.makeDefault);case _: throw StateError('Unexpected subclass'); } @@ -3417,10 +3426,10 @@ return $default(_that.connectionId,_that.modelId);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId, String modelId)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String definitionId, bool makeDefault)? $default,) {final _that = this; switch (_that) { -case _ProviderDefaultModelSetParamsDto() when $default != null: -return $default(_that.connectionId,_that.modelId);case _: +case _ProviderConnectNoneParamsDto() when $default != null: +return $default(_that.definitionId,_that.makeDefault);case _: return null; } @@ -3431,47 +3440,47 @@ return $default(_that.connectionId,_that.modelId);case _: /// @nodoc @JsonSerializable() -class _ProviderDefaultModelSetParamsDto implements ProviderDefaultModelSetParamsDto { - const _ProviderDefaultModelSetParamsDto({required this.connectionId, required this.modelId}); - factory _ProviderDefaultModelSetParamsDto.fromJson(Map json) => _$ProviderDefaultModelSetParamsDtoFromJson(json); +class _ProviderConnectNoneParamsDto implements ProviderConnectNoneParamsDto { + const _ProviderConnectNoneParamsDto({required this.definitionId, required this.makeDefault}); + factory _ProviderConnectNoneParamsDto.fromJson(Map json) => _$ProviderConnectNoneParamsDtoFromJson(json); -@override final String connectionId; -@override final String modelId; +@override final String definitionId; +@override final bool makeDefault; -/// Create a copy of ProviderDefaultModelSetParamsDto +/// Create a copy of ProviderConnectNoneParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderDefaultModelSetParamsDtoCopyWith<_ProviderDefaultModelSetParamsDto> get copyWith => __$ProviderDefaultModelSetParamsDtoCopyWithImpl<_ProviderDefaultModelSetParamsDto>(this, _$identity); +_$ProviderConnectNoneParamsDtoCopyWith<_ProviderConnectNoneParamsDto> get copyWith => __$ProviderConnectNoneParamsDtoCopyWithImpl<_ProviderConnectNoneParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderDefaultModelSetParamsDtoToJson(this, ); + return _$ProviderConnectNoneParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderDefaultModelSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderConnectNoneParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId,modelId); +int get hashCode => Object.hash(runtimeType,definitionId,makeDefault); @override String toString() { - return 'ProviderDefaultModelSetParamsDto(connectionId: $connectionId, modelId: $modelId)'; + return 'ProviderConnectNoneParamsDto(definitionId: $definitionId, makeDefault: $makeDefault)'; } } /// @nodoc -abstract mixin class _$ProviderDefaultModelSetParamsDtoCopyWith<$Res> implements $ProviderDefaultModelSetParamsDtoCopyWith<$Res> { - factory _$ProviderDefaultModelSetParamsDtoCopyWith(_ProviderDefaultModelSetParamsDto value, $Res Function(_ProviderDefaultModelSetParamsDto) _then) = __$ProviderDefaultModelSetParamsDtoCopyWithImpl; +abstract mixin class _$ProviderConnectNoneParamsDtoCopyWith<$Res> implements $ProviderConnectNoneParamsDtoCopyWith<$Res> { + factory _$ProviderConnectNoneParamsDtoCopyWith(_ProviderConnectNoneParamsDto value, $Res Function(_ProviderConnectNoneParamsDto) _then) = __$ProviderConnectNoneParamsDtoCopyWithImpl; @override @useResult $Res call({ - String connectionId, String modelId + String definitionId, bool makeDefault }); @@ -3479,20 +3488,20 @@ $Res call({ } /// @nodoc -class __$ProviderDefaultModelSetParamsDtoCopyWithImpl<$Res> - implements _$ProviderDefaultModelSetParamsDtoCopyWith<$Res> { - __$ProviderDefaultModelSetParamsDtoCopyWithImpl(this._self, this._then); +class __$ProviderConnectNoneParamsDtoCopyWithImpl<$Res> + implements _$ProviderConnectNoneParamsDtoCopyWith<$Res> { + __$ProviderConnectNoneParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderDefaultModelSetParamsDto _self; - final $Res Function(_ProviderDefaultModelSetParamsDto) _then; + final _ProviderConnectNoneParamsDto _self; + final $Res Function(_ProviderConnectNoneParamsDto) _then; -/// Create a copy of ProviderDefaultModelSetParamsDto +/// Create a copy of ProviderConnectNoneParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,Object? modelId = null,}) { - return _then(_ProviderDefaultModelSetParamsDto( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable -as String, +@override @pragma('vm:prefer-inline') $Res call({Object? definitionId = null,Object? makeDefault = null,}) { + return _then(_ProviderConnectNoneParamsDto( +definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable +as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool, )); } @@ -3501,82 +3510,3036 @@ as String, /// @nodoc -mixin _$ProviderCustomCreateParamsDto { +mixin _$ProviderConnectionIdParamsDto { - String get id; CustomProviderConfigDto get config; bool get makeDefault; String? get apiKey; -/// Create a copy of ProviderCustomCreateParamsDto + String get connectionId; +/// Create a copy of ProviderConnectionIdParamsDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderCustomCreateParamsDtoCopyWith get copyWith => _$ProviderCustomCreateParamsDtoCopyWithImpl(this as ProviderCustomCreateParamsDto, _$identity); +$ProviderConnectionIdParamsDtoCopyWith get copyWith => _$ProviderConnectionIdParamsDtoCopyWithImpl(this as ProviderConnectionIdParamsDto, _$identity); - /// Serializes this ProviderCustomCreateParamsDto to a JSON map. + /// Serializes this ProviderConnectionIdParamsDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderCustomCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.config, config) || other.config == config)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderConnectionIdParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,config,makeDefault,apiKey); +int get hashCode => Object.hash(runtimeType,connectionId); @override String toString() { - return 'ProviderCustomCreateParamsDto(id: $id, config: $config, makeDefault: $makeDefault, apiKey: $apiKey)'; + return 'ProviderConnectionIdParamsDto(connectionId: $connectionId)'; } } /// @nodoc -abstract mixin class $ProviderCustomCreateParamsDtoCopyWith<$Res> { - factory $ProviderCustomCreateParamsDtoCopyWith(ProviderCustomCreateParamsDto value, $Res Function(ProviderCustomCreateParamsDto) _then) = _$ProviderCustomCreateParamsDtoCopyWithImpl; +abstract mixin class $ProviderConnectionIdParamsDtoCopyWith<$Res> { + factory $ProviderConnectionIdParamsDtoCopyWith(ProviderConnectionIdParamsDto value, $Res Function(ProviderConnectionIdParamsDto) _then) = _$ProviderConnectionIdParamsDtoCopyWithImpl; @useResult $Res call({ - String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey + String connectionId }); -$CustomProviderConfigDtoCopyWith<$Res> get config; + } /// @nodoc -class _$ProviderCustomCreateParamsDtoCopyWithImpl<$Res> - implements $ProviderCustomCreateParamsDtoCopyWith<$Res> { - _$ProviderCustomCreateParamsDtoCopyWithImpl(this._self, this._then); +class _$ProviderConnectionIdParamsDtoCopyWithImpl<$Res> + implements $ProviderConnectionIdParamsDtoCopyWith<$Res> { + _$ProviderConnectionIdParamsDtoCopyWithImpl(this._self, this._then); - final ProviderCustomCreateParamsDto _self; - final $Res Function(ProviderCustomCreateParamsDto) _then; + final ProviderConnectionIdParamsDto _self; + final $Res Function(ProviderConnectionIdParamsDto) _then; -/// Create a copy of ProviderCustomCreateParamsDto +/// Create a copy of ProviderConnectionIdParamsDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? config = null,Object? makeDefault = null,Object? apiKey = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,}) { return _then(_self.copyWith( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable -as CustomProviderConfigDto,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable -as String?, +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String, )); } -/// Create a copy of ProviderCustomCreateParamsDto + +} + + +/// Adds pattern-matching-related methods to [ProviderConnectionIdParamsDto]. +extension ProviderConnectionIdParamsDtoPatterns on ProviderConnectionIdParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderConnectionIdParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderConnectionIdParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderConnectionIdParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderConnectionIdParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderConnectionIdParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderConnectionIdParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderConnectionIdParamsDto() when $default != null: +return $default(_that.connectionId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String connectionId) $default,) {final _that = this; +switch (_that) { +case _ProviderConnectionIdParamsDto(): +return $default(_that.connectionId);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId)? $default,) {final _that = this; +switch (_that) { +case _ProviderConnectionIdParamsDto() when $default != null: +return $default(_that.connectionId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderConnectionIdParamsDto implements ProviderConnectionIdParamsDto { + const _ProviderConnectionIdParamsDto({required this.connectionId}); + factory _ProviderConnectionIdParamsDto.fromJson(Map json) => _$ProviderConnectionIdParamsDtoFromJson(json); + +@override final String connectionId; + +/// Create a copy of ProviderConnectionIdParamsDto /// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderConnectionIdParamsDtoCopyWith<_ProviderConnectionIdParamsDto> get copyWith => __$ProviderConnectionIdParamsDtoCopyWithImpl<_ProviderConnectionIdParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderConnectionIdParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderConnectionIdParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId); + @override +String toString() { + return 'ProviderConnectionIdParamsDto(connectionId: $connectionId)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderConnectionIdParamsDtoCopyWith<$Res> implements $ProviderConnectionIdParamsDtoCopyWith<$Res> { + factory _$ProviderConnectionIdParamsDtoCopyWith(_ProviderConnectionIdParamsDto value, $Res Function(_ProviderConnectionIdParamsDto) _then) = __$ProviderConnectionIdParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String connectionId +}); + + + + +} +/// @nodoc +class __$ProviderConnectionIdParamsDtoCopyWithImpl<$Res> + implements _$ProviderConnectionIdParamsDtoCopyWith<$Res> { + __$ProviderConnectionIdParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderConnectionIdParamsDto _self; + final $Res Function(_ProviderConnectionIdParamsDto) _then; + +/// Create a copy of ProviderConnectionIdParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,}) { + return _then(_ProviderConnectionIdParamsDto( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$ProviderModelParamsDto { + + String get connectionId; String get modelId; +/// Create a copy of ProviderModelParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$CustomProviderConfigDtoCopyWith<$Res> get config { - - return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { - return _then(_self.copyWith(config: value)); - }); +$ProviderModelParamsDtoCopyWith get copyWith => _$ProviderModelParamsDtoCopyWithImpl(this as ProviderModelParamsDto, _$identity); + + /// Serializes this ProviderModelParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderModelParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId,modelId); + +@override +String toString() { + return 'ProviderModelParamsDto(connectionId: $connectionId, modelId: $modelId)'; +} + + +} + +/// @nodoc +abstract mixin class $ProviderModelParamsDtoCopyWith<$Res> { + factory $ProviderModelParamsDtoCopyWith(ProviderModelParamsDto value, $Res Function(ProviderModelParamsDto) _then) = _$ProviderModelParamsDtoCopyWithImpl; +@useResult +$Res call({ + String connectionId, String modelId +}); + + + + +} +/// @nodoc +class _$ProviderModelParamsDtoCopyWithImpl<$Res> + implements $ProviderModelParamsDtoCopyWith<$Res> { + _$ProviderModelParamsDtoCopyWithImpl(this._self, this._then); + + final ProviderModelParamsDto _self; + final $Res Function(ProviderModelParamsDto) _then; + +/// Create a copy of ProviderModelParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,Object? modelId = null,}) { + return _then(_self.copyWith( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ProviderModelParamsDto]. +extension ProviderModelParamsDtoPatterns on ProviderModelParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderModelParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderModelParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderModelParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderModelParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderModelParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderModelParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId, String modelId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderModelParamsDto() when $default != null: +return $default(_that.connectionId,_that.modelId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String connectionId, String modelId) $default,) {final _that = this; +switch (_that) { +case _ProviderModelParamsDto(): +return $default(_that.connectionId,_that.modelId);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId, String modelId)? $default,) {final _that = this; +switch (_that) { +case _ProviderModelParamsDto() when $default != null: +return $default(_that.connectionId,_that.modelId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderModelParamsDto implements ProviderModelParamsDto { + const _ProviderModelParamsDto({required this.connectionId, required this.modelId}); + factory _ProviderModelParamsDto.fromJson(Map json) => _$ProviderModelParamsDtoFromJson(json); + +@override final String connectionId; +@override final String modelId; + +/// Create a copy of ProviderModelParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderModelParamsDtoCopyWith<_ProviderModelParamsDto> get copyWith => __$ProviderModelParamsDtoCopyWithImpl<_ProviderModelParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderModelParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderModelParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId,modelId); + +@override +String toString() { + return 'ProviderModelParamsDto(connectionId: $connectionId, modelId: $modelId)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderModelParamsDtoCopyWith<$Res> implements $ProviderModelParamsDtoCopyWith<$Res> { + factory _$ProviderModelParamsDtoCopyWith(_ProviderModelParamsDto value, $Res Function(_ProviderModelParamsDto) _then) = __$ProviderModelParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String connectionId, String modelId +}); + + + + +} +/// @nodoc +class __$ProviderModelParamsDtoCopyWithImpl<$Res> + implements _$ProviderModelParamsDtoCopyWith<$Res> { + __$ProviderModelParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderModelParamsDto _self; + final $Res Function(_ProviderModelParamsDto) _then; + +/// Create a copy of ProviderModelParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,Object? modelId = null,}) { + return _then(_ProviderModelParamsDto( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$ProviderAuthStartParamsDto { + + String get definitionId; String get methodId; bool get makeDefault; +/// Create a copy of ProviderAuthStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ProviderAuthStartParamsDtoCopyWith get copyWith => _$ProviderAuthStartParamsDtoCopyWithImpl(this as ProviderAuthStartParamsDto, _$identity); + + /// Serializes this ProviderAuthStartParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderAuthStartParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.methodId, methodId) || other.methodId == methodId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,definitionId,methodId,makeDefault); + +@override +String toString() { + return 'ProviderAuthStartParamsDto(definitionId: $definitionId, methodId: $methodId, makeDefault: $makeDefault)'; +} + + +} + +/// @nodoc +abstract mixin class $ProviderAuthStartParamsDtoCopyWith<$Res> { + factory $ProviderAuthStartParamsDtoCopyWith(ProviderAuthStartParamsDto value, $Res Function(ProviderAuthStartParamsDto) _then) = _$ProviderAuthStartParamsDtoCopyWithImpl; +@useResult +$Res call({ + String definitionId, String methodId, bool makeDefault +}); + + + + +} +/// @nodoc +class _$ProviderAuthStartParamsDtoCopyWithImpl<$Res> + implements $ProviderAuthStartParamsDtoCopyWith<$Res> { + _$ProviderAuthStartParamsDtoCopyWithImpl(this._self, this._then); + + final ProviderAuthStartParamsDto _self; + final $Res Function(ProviderAuthStartParamsDto) _then; + +/// Create a copy of ProviderAuthStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? definitionId = null,Object? methodId = null,Object? makeDefault = null,}) { + return _then(_self.copyWith( +definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable +as String,methodId: null == methodId ? _self.methodId : methodId // ignore: cast_nullable_to_non_nullable +as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ProviderAuthStartParamsDto]. +extension ProviderAuthStartParamsDtoPatterns on ProviderAuthStartParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderAuthStartParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderAuthStartParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderAuthStartParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderAuthStartParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderAuthStartParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderAuthStartParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String definitionId, String methodId, bool makeDefault)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderAuthStartParamsDto() when $default != null: +return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String definitionId, String methodId, bool makeDefault) $default,) {final _that = this; +switch (_that) { +case _ProviderAuthStartParamsDto(): +return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String definitionId, String methodId, bool makeDefault)? $default,) {final _that = this; +switch (_that) { +case _ProviderAuthStartParamsDto() when $default != null: +return $default(_that.definitionId,_that.methodId,_that.makeDefault);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderAuthStartParamsDto implements ProviderAuthStartParamsDto { + const _ProviderAuthStartParamsDto({required this.definitionId, required this.methodId, required this.makeDefault}); + factory _ProviderAuthStartParamsDto.fromJson(Map json) => _$ProviderAuthStartParamsDtoFromJson(json); + +@override final String definitionId; +@override final String methodId; +@override final bool makeDefault; + +/// Create a copy of ProviderAuthStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderAuthStartParamsDtoCopyWith<_ProviderAuthStartParamsDto> get copyWith => __$ProviderAuthStartParamsDtoCopyWithImpl<_ProviderAuthStartParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderAuthStartParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderAuthStartParamsDto&&(identical(other.definitionId, definitionId) || other.definitionId == definitionId)&&(identical(other.methodId, methodId) || other.methodId == methodId)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,definitionId,methodId,makeDefault); + +@override +String toString() { + return 'ProviderAuthStartParamsDto(definitionId: $definitionId, methodId: $methodId, makeDefault: $makeDefault)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderAuthStartParamsDtoCopyWith<$Res> implements $ProviderAuthStartParamsDtoCopyWith<$Res> { + factory _$ProviderAuthStartParamsDtoCopyWith(_ProviderAuthStartParamsDto value, $Res Function(_ProviderAuthStartParamsDto) _then) = __$ProviderAuthStartParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String definitionId, String methodId, bool makeDefault +}); + + + + +} +/// @nodoc +class __$ProviderAuthStartParamsDtoCopyWithImpl<$Res> + implements _$ProviderAuthStartParamsDtoCopyWith<$Res> { + __$ProviderAuthStartParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderAuthStartParamsDto _self; + final $Res Function(_ProviderAuthStartParamsDto) _then; + +/// Create a copy of ProviderAuthStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? definitionId = null,Object? methodId = null,Object? makeDefault = null,}) { + return _then(_ProviderAuthStartParamsDto( +definitionId: null == definitionId ? _self.definitionId : definitionId // ignore: cast_nullable_to_non_nullable +as String,methodId: null == methodId ? _self.methodId : methodId // ignore: cast_nullable_to_non_nullable +as String,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + + +/// @nodoc +mixin _$ProviderAuthAttemptParamsDto { + + String get attemptId; +/// Create a copy of ProviderAuthAttemptParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ProviderAuthAttemptParamsDtoCopyWith get copyWith => _$ProviderAuthAttemptParamsDtoCopyWithImpl(this as ProviderAuthAttemptParamsDto, _$identity); + + /// Serializes this ProviderAuthAttemptParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderAuthAttemptParamsDto&&(identical(other.attemptId, attemptId) || other.attemptId == attemptId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,attemptId); + +@override +String toString() { + return 'ProviderAuthAttemptParamsDto(attemptId: $attemptId)'; +} + + +} + +/// @nodoc +abstract mixin class $ProviderAuthAttemptParamsDtoCopyWith<$Res> { + factory $ProviderAuthAttemptParamsDtoCopyWith(ProviderAuthAttemptParamsDto value, $Res Function(ProviderAuthAttemptParamsDto) _then) = _$ProviderAuthAttemptParamsDtoCopyWithImpl; +@useResult +$Res call({ + String attemptId +}); + + + + +} +/// @nodoc +class _$ProviderAuthAttemptParamsDtoCopyWithImpl<$Res> + implements $ProviderAuthAttemptParamsDtoCopyWith<$Res> { + _$ProviderAuthAttemptParamsDtoCopyWithImpl(this._self, this._then); + + final ProviderAuthAttemptParamsDto _self; + final $Res Function(ProviderAuthAttemptParamsDto) _then; + +/// Create a copy of ProviderAuthAttemptParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? attemptId = null,}) { + return _then(_self.copyWith( +attemptId: null == attemptId ? _self.attemptId : attemptId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ProviderAuthAttemptParamsDto]. +extension ProviderAuthAttemptParamsDtoPatterns on ProviderAuthAttemptParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderAuthAttemptParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderAuthAttemptParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderAuthAttemptParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderAuthAttemptParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderAuthAttemptParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderAuthAttemptParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String attemptId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderAuthAttemptParamsDto() when $default != null: +return $default(_that.attemptId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String attemptId) $default,) {final _that = this; +switch (_that) { +case _ProviderAuthAttemptParamsDto(): +return $default(_that.attemptId);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String attemptId)? $default,) {final _that = this; +switch (_that) { +case _ProviderAuthAttemptParamsDto() when $default != null: +return $default(_that.attemptId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderAuthAttemptParamsDto implements ProviderAuthAttemptParamsDto { + const _ProviderAuthAttemptParamsDto({required this.attemptId}); + factory _ProviderAuthAttemptParamsDto.fromJson(Map json) => _$ProviderAuthAttemptParamsDtoFromJson(json); + +@override final String attemptId; + +/// Create a copy of ProviderAuthAttemptParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderAuthAttemptParamsDtoCopyWith<_ProviderAuthAttemptParamsDto> get copyWith => __$ProviderAuthAttemptParamsDtoCopyWithImpl<_ProviderAuthAttemptParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderAuthAttemptParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderAuthAttemptParamsDto&&(identical(other.attemptId, attemptId) || other.attemptId == attemptId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,attemptId); + +@override +String toString() { + return 'ProviderAuthAttemptParamsDto(attemptId: $attemptId)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderAuthAttemptParamsDtoCopyWith<$Res> implements $ProviderAuthAttemptParamsDtoCopyWith<$Res> { + factory _$ProviderAuthAttemptParamsDtoCopyWith(_ProviderAuthAttemptParamsDto value, $Res Function(_ProviderAuthAttemptParamsDto) _then) = __$ProviderAuthAttemptParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String attemptId +}); + + + + +} +/// @nodoc +class __$ProviderAuthAttemptParamsDtoCopyWithImpl<$Res> + implements _$ProviderAuthAttemptParamsDtoCopyWith<$Res> { + __$ProviderAuthAttemptParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderAuthAttemptParamsDto _self; + final $Res Function(_ProviderAuthAttemptParamsDto) _then; + +/// Create a copy of ProviderAuthAttemptParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? attemptId = null,}) { + return _then(_ProviderAuthAttemptParamsDto( +attemptId: null == attemptId ? _self.attemptId : attemptId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$ProviderDefaultSetParamsDto { + + String get connectionId; +/// Create a copy of ProviderDefaultSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ProviderDefaultSetParamsDtoCopyWith get copyWith => _$ProviderDefaultSetParamsDtoCopyWithImpl(this as ProviderDefaultSetParamsDto, _$identity); + + /// Serializes this ProviderDefaultSetParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderDefaultSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId); + +@override +String toString() { + return 'ProviderDefaultSetParamsDto(connectionId: $connectionId)'; +} + + +} + +/// @nodoc +abstract mixin class $ProviderDefaultSetParamsDtoCopyWith<$Res> { + factory $ProviderDefaultSetParamsDtoCopyWith(ProviderDefaultSetParamsDto value, $Res Function(ProviderDefaultSetParamsDto) _then) = _$ProviderDefaultSetParamsDtoCopyWithImpl; +@useResult +$Res call({ + String connectionId +}); + + + + +} +/// @nodoc +class _$ProviderDefaultSetParamsDtoCopyWithImpl<$Res> + implements $ProviderDefaultSetParamsDtoCopyWith<$Res> { + _$ProviderDefaultSetParamsDtoCopyWithImpl(this._self, this._then); + + final ProviderDefaultSetParamsDto _self; + final $Res Function(ProviderDefaultSetParamsDto) _then; + +/// Create a copy of ProviderDefaultSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,}) { + return _then(_self.copyWith( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ProviderDefaultSetParamsDto]. +extension ProviderDefaultSetParamsDtoPatterns on ProviderDefaultSetParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderDefaultSetParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderDefaultSetParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderDefaultSetParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderDefaultSetParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderDefaultSetParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderDefaultSetParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderDefaultSetParamsDto() when $default != null: +return $default(_that.connectionId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String connectionId) $default,) {final _that = this; +switch (_that) { +case _ProviderDefaultSetParamsDto(): +return $default(_that.connectionId);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId)? $default,) {final _that = this; +switch (_that) { +case _ProviderDefaultSetParamsDto() when $default != null: +return $default(_that.connectionId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderDefaultSetParamsDto implements ProviderDefaultSetParamsDto { + const _ProviderDefaultSetParamsDto({required this.connectionId}); + factory _ProviderDefaultSetParamsDto.fromJson(Map json) => _$ProviderDefaultSetParamsDtoFromJson(json); + +@override final String connectionId; + +/// Create a copy of ProviderDefaultSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderDefaultSetParamsDtoCopyWith<_ProviderDefaultSetParamsDto> get copyWith => __$ProviderDefaultSetParamsDtoCopyWithImpl<_ProviderDefaultSetParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderDefaultSetParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderDefaultSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId); + +@override +String toString() { + return 'ProviderDefaultSetParamsDto(connectionId: $connectionId)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderDefaultSetParamsDtoCopyWith<$Res> implements $ProviderDefaultSetParamsDtoCopyWith<$Res> { + factory _$ProviderDefaultSetParamsDtoCopyWith(_ProviderDefaultSetParamsDto value, $Res Function(_ProviderDefaultSetParamsDto) _then) = __$ProviderDefaultSetParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String connectionId +}); + + + + +} +/// @nodoc +class __$ProviderDefaultSetParamsDtoCopyWithImpl<$Res> + implements _$ProviderDefaultSetParamsDtoCopyWith<$Res> { + __$ProviderDefaultSetParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderDefaultSetParamsDto _self; + final $Res Function(_ProviderDefaultSetParamsDto) _then; + +/// Create a copy of ProviderDefaultSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,}) { + return _then(_ProviderDefaultSetParamsDto( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$ProviderDefaultModelSetParamsDto { + + String get connectionId; String get modelId; +/// Create a copy of ProviderDefaultModelSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ProviderDefaultModelSetParamsDtoCopyWith get copyWith => _$ProviderDefaultModelSetParamsDtoCopyWithImpl(this as ProviderDefaultModelSetParamsDto, _$identity); + + /// Serializes this ProviderDefaultModelSetParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderDefaultModelSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId,modelId); + +@override +String toString() { + return 'ProviderDefaultModelSetParamsDto(connectionId: $connectionId, modelId: $modelId)'; +} + + +} + +/// @nodoc +abstract mixin class $ProviderDefaultModelSetParamsDtoCopyWith<$Res> { + factory $ProviderDefaultModelSetParamsDtoCopyWith(ProviderDefaultModelSetParamsDto value, $Res Function(ProviderDefaultModelSetParamsDto) _then) = _$ProviderDefaultModelSetParamsDtoCopyWithImpl; +@useResult +$Res call({ + String connectionId, String modelId +}); + + + + +} +/// @nodoc +class _$ProviderDefaultModelSetParamsDtoCopyWithImpl<$Res> + implements $ProviderDefaultModelSetParamsDtoCopyWith<$Res> { + _$ProviderDefaultModelSetParamsDtoCopyWithImpl(this._self, this._then); + + final ProviderDefaultModelSetParamsDto _self; + final $Res Function(ProviderDefaultModelSetParamsDto) _then; + +/// Create a copy of ProviderDefaultModelSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,Object? modelId = null,}) { + return _then(_self.copyWith( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ProviderDefaultModelSetParamsDto]. +extension ProviderDefaultModelSetParamsDtoPatterns on ProviderDefaultModelSetParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderDefaultModelSetParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderDefaultModelSetParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderDefaultModelSetParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderDefaultModelSetParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderDefaultModelSetParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderDefaultModelSetParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId, String modelId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderDefaultModelSetParamsDto() when $default != null: +return $default(_that.connectionId,_that.modelId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String connectionId, String modelId) $default,) {final _that = this; +switch (_that) { +case _ProviderDefaultModelSetParamsDto(): +return $default(_that.connectionId,_that.modelId);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId, String modelId)? $default,) {final _that = this; +switch (_that) { +case _ProviderDefaultModelSetParamsDto() when $default != null: +return $default(_that.connectionId,_that.modelId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderDefaultModelSetParamsDto implements ProviderDefaultModelSetParamsDto { + const _ProviderDefaultModelSetParamsDto({required this.connectionId, required this.modelId}); + factory _ProviderDefaultModelSetParamsDto.fromJson(Map json) => _$ProviderDefaultModelSetParamsDtoFromJson(json); + +@override final String connectionId; +@override final String modelId; + +/// Create a copy of ProviderDefaultModelSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderDefaultModelSetParamsDtoCopyWith<_ProviderDefaultModelSetParamsDto> get copyWith => __$ProviderDefaultModelSetParamsDtoCopyWithImpl<_ProviderDefaultModelSetParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderDefaultModelSetParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderDefaultModelSetParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.modelId, modelId) || other.modelId == modelId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId,modelId); + +@override +String toString() { + return 'ProviderDefaultModelSetParamsDto(connectionId: $connectionId, modelId: $modelId)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderDefaultModelSetParamsDtoCopyWith<$Res> implements $ProviderDefaultModelSetParamsDtoCopyWith<$Res> { + factory _$ProviderDefaultModelSetParamsDtoCopyWith(_ProviderDefaultModelSetParamsDto value, $Res Function(_ProviderDefaultModelSetParamsDto) _then) = __$ProviderDefaultModelSetParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String connectionId, String modelId +}); + + + + +} +/// @nodoc +class __$ProviderDefaultModelSetParamsDtoCopyWithImpl<$Res> + implements _$ProviderDefaultModelSetParamsDtoCopyWith<$Res> { + __$ProviderDefaultModelSetParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderDefaultModelSetParamsDto _self; + final $Res Function(_ProviderDefaultModelSetParamsDto) _then; + +/// Create a copy of ProviderDefaultModelSetParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,Object? modelId = null,}) { + return _then(_ProviderDefaultModelSetParamsDto( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String,modelId: null == modelId ? _self.modelId : modelId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$ProviderCustomCreateParamsDto { + + String get id; CustomProviderConfigDto get config; bool get makeDefault; String? get apiKey; +/// Create a copy of ProviderCustomCreateParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ProviderCustomCreateParamsDtoCopyWith get copyWith => _$ProviderCustomCreateParamsDtoCopyWithImpl(this as ProviderCustomCreateParamsDto, _$identity); + + /// Serializes this ProviderCustomCreateParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderCustomCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.config, config) || other.config == config)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,config,makeDefault,apiKey); + +@override +String toString() { + return 'ProviderCustomCreateParamsDto(id: $id, config: $config, makeDefault: $makeDefault, apiKey: $apiKey)'; +} + + +} + +/// @nodoc +abstract mixin class $ProviderCustomCreateParamsDtoCopyWith<$Res> { + factory $ProviderCustomCreateParamsDtoCopyWith(ProviderCustomCreateParamsDto value, $Res Function(ProviderCustomCreateParamsDto) _then) = _$ProviderCustomCreateParamsDtoCopyWithImpl; +@useResult +$Res call({ + String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey +}); + + +$CustomProviderConfigDtoCopyWith<$Res> get config; + +} +/// @nodoc +class _$ProviderCustomCreateParamsDtoCopyWithImpl<$Res> + implements $ProviderCustomCreateParamsDtoCopyWith<$Res> { + _$ProviderCustomCreateParamsDtoCopyWithImpl(this._self, this._then); + + final ProviderCustomCreateParamsDto _self; + final $Res Function(ProviderCustomCreateParamsDto) _then; + +/// Create a copy of ProviderCustomCreateParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? config = null,Object? makeDefault = null,Object? apiKey = freezed,}) { + return _then(_self.copyWith( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable +as CustomProviderConfigDto,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable +as String?, + )); +} +/// Create a copy of ProviderCustomCreateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CustomProviderConfigDtoCopyWith<$Res> get config { + + return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { + return _then(_self.copyWith(config: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [ProviderCustomCreateParamsDto]. +extension ProviderCustomCreateParamsDtoPatterns on ProviderCustomCreateParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderCustomCreateParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderCustomCreateParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderCustomCreateParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderCustomCreateParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderCustomCreateParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderCustomCreateParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderCustomCreateParamsDto() when $default != null: +return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey) $default,) {final _that = this; +switch (_that) { +case _ProviderCustomCreateParamsDto(): +return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey)? $default,) {final _that = this; +switch (_that) { +case _ProviderCustomCreateParamsDto() when $default != null: +return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderCustomCreateParamsDto implements ProviderCustomCreateParamsDto { + const _ProviderCustomCreateParamsDto({required this.id, required this.config, required this.makeDefault, this.apiKey}); + factory _ProviderCustomCreateParamsDto.fromJson(Map json) => _$ProviderCustomCreateParamsDtoFromJson(json); + +@override final String id; +@override final CustomProviderConfigDto config; +@override final bool makeDefault; +@override final String? apiKey; + +/// Create a copy of ProviderCustomCreateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderCustomCreateParamsDtoCopyWith<_ProviderCustomCreateParamsDto> get copyWith => __$ProviderCustomCreateParamsDtoCopyWithImpl<_ProviderCustomCreateParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderCustomCreateParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderCustomCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.config, config) || other.config == config)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,config,makeDefault,apiKey); + +@override +String toString() { + return 'ProviderCustomCreateParamsDto(id: $id, config: $config, makeDefault: $makeDefault, apiKey: $apiKey)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderCustomCreateParamsDtoCopyWith<$Res> implements $ProviderCustomCreateParamsDtoCopyWith<$Res> { + factory _$ProviderCustomCreateParamsDtoCopyWith(_ProviderCustomCreateParamsDto value, $Res Function(_ProviderCustomCreateParamsDto) _then) = __$ProviderCustomCreateParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey +}); + + +@override $CustomProviderConfigDtoCopyWith<$Res> get config; + +} +/// @nodoc +class __$ProviderCustomCreateParamsDtoCopyWithImpl<$Res> + implements _$ProviderCustomCreateParamsDtoCopyWith<$Res> { + __$ProviderCustomCreateParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderCustomCreateParamsDto _self; + final $Res Function(_ProviderCustomCreateParamsDto) _then; + +/// Create a copy of ProviderCustomCreateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? config = null,Object? makeDefault = null,Object? apiKey = freezed,}) { + return _then(_ProviderCustomCreateParamsDto( +id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable +as CustomProviderConfigDto,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable +as bool,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +/// Create a copy of ProviderCustomCreateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CustomProviderConfigDtoCopyWith<$Res> get config { + + return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { + return _then(_self.copyWith(config: value)); + }); +} +} + + +/// @nodoc +mixin _$ProviderCustomUpdateParamsDto { + + String get connectionId; CustomProviderConfigDto get config; String? get apiKey; +/// Create a copy of ProviderCustomUpdateParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ProviderCustomUpdateParamsDtoCopyWith get copyWith => _$ProviderCustomUpdateParamsDtoCopyWithImpl(this as ProviderCustomUpdateParamsDto, _$identity); + + /// Serializes this ProviderCustomUpdateParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderCustomUpdateParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.config, config) || other.config == config)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId,config,apiKey); + +@override +String toString() { + return 'ProviderCustomUpdateParamsDto(connectionId: $connectionId, config: $config, apiKey: $apiKey)'; +} + + +} + +/// @nodoc +abstract mixin class $ProviderCustomUpdateParamsDtoCopyWith<$Res> { + factory $ProviderCustomUpdateParamsDtoCopyWith(ProviderCustomUpdateParamsDto value, $Res Function(ProviderCustomUpdateParamsDto) _then) = _$ProviderCustomUpdateParamsDtoCopyWithImpl; +@useResult +$Res call({ + String connectionId, CustomProviderConfigDto config, String? apiKey +}); + + +$CustomProviderConfigDtoCopyWith<$Res> get config; + +} +/// @nodoc +class _$ProviderCustomUpdateParamsDtoCopyWithImpl<$Res> + implements $ProviderCustomUpdateParamsDtoCopyWith<$Res> { + _$ProviderCustomUpdateParamsDtoCopyWithImpl(this._self, this._then); + + final ProviderCustomUpdateParamsDto _self; + final $Res Function(ProviderCustomUpdateParamsDto) _then; + +/// Create a copy of ProviderCustomUpdateParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,Object? config = null,Object? apiKey = freezed,}) { + return _then(_self.copyWith( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable +as CustomProviderConfigDto,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable +as String?, + )); +} +/// Create a copy of ProviderCustomUpdateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CustomProviderConfigDtoCopyWith<$Res> get config { + + return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { + return _then(_self.copyWith(config: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [ProviderCustomUpdateParamsDto]. +extension ProviderCustomUpdateParamsDtoPatterns on ProviderCustomUpdateParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderCustomUpdateParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ProviderCustomUpdateParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ProviderCustomUpdateParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ProviderCustomUpdateParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderCustomUpdateParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ProviderCustomUpdateParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId, CustomProviderConfigDto config, String? apiKey)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ProviderCustomUpdateParamsDto() when $default != null: +return $default(_that.connectionId,_that.config,_that.apiKey);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String connectionId, CustomProviderConfigDto config, String? apiKey) $default,) {final _that = this; +switch (_that) { +case _ProviderCustomUpdateParamsDto(): +return $default(_that.connectionId,_that.config,_that.apiKey);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId, CustomProviderConfigDto config, String? apiKey)? $default,) {final _that = this; +switch (_that) { +case _ProviderCustomUpdateParamsDto() when $default != null: +return $default(_that.connectionId,_that.config,_that.apiKey);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ProviderCustomUpdateParamsDto implements ProviderCustomUpdateParamsDto { + const _ProviderCustomUpdateParamsDto({required this.connectionId, required this.config, this.apiKey}); + factory _ProviderCustomUpdateParamsDto.fromJson(Map json) => _$ProviderCustomUpdateParamsDtoFromJson(json); + +@override final String connectionId; +@override final CustomProviderConfigDto config; +@override final String? apiKey; + +/// Create a copy of ProviderCustomUpdateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ProviderCustomUpdateParamsDtoCopyWith<_ProviderCustomUpdateParamsDto> get copyWith => __$ProviderCustomUpdateParamsDtoCopyWithImpl<_ProviderCustomUpdateParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ProviderCustomUpdateParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderCustomUpdateParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.config, config) || other.config == config)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,connectionId,config,apiKey); + +@override +String toString() { + return 'ProviderCustomUpdateParamsDto(connectionId: $connectionId, config: $config, apiKey: $apiKey)'; +} + + +} + +/// @nodoc +abstract mixin class _$ProviderCustomUpdateParamsDtoCopyWith<$Res> implements $ProviderCustomUpdateParamsDtoCopyWith<$Res> { + factory _$ProviderCustomUpdateParamsDtoCopyWith(_ProviderCustomUpdateParamsDto value, $Res Function(_ProviderCustomUpdateParamsDto) _then) = __$ProviderCustomUpdateParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String connectionId, CustomProviderConfigDto config, String? apiKey +}); + + +@override $CustomProviderConfigDtoCopyWith<$Res> get config; + +} +/// @nodoc +class __$ProviderCustomUpdateParamsDtoCopyWithImpl<$Res> + implements _$ProviderCustomUpdateParamsDtoCopyWith<$Res> { + __$ProviderCustomUpdateParamsDtoCopyWithImpl(this._self, this._then); + + final _ProviderCustomUpdateParamsDto _self; + final $Res Function(_ProviderCustomUpdateParamsDto) _then; + +/// Create a copy of ProviderCustomUpdateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,Object? config = null,Object? apiKey = freezed,}) { + return _then(_ProviderCustomUpdateParamsDto( +connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable +as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable +as CustomProviderConfigDto,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable +as String?, + )); +} + +/// Create a copy of ProviderCustomUpdateParamsDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$CustomProviderConfigDtoCopyWith<$Res> get config { + + return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { + return _then(_self.copyWith(config: value)); + }); +} +} + + +/// @nodoc +mixin _$TurnStartParamsDto { + + String get agentId; String get turnId; String get prompt; +/// Create a copy of TurnStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$TurnStartParamsDtoCopyWith get copyWith => _$TurnStartParamsDtoCopyWithImpl(this as TurnStartParamsDto, _$identity); + + /// Serializes this TurnStartParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is TurnStartParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.turnId, turnId) || other.turnId == turnId)&&(identical(other.prompt, prompt) || other.prompt == prompt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,agentId,turnId,prompt); + +@override +String toString() { + return 'TurnStartParamsDto(agentId: $agentId, turnId: $turnId, prompt: $prompt)'; +} + + +} + +/// @nodoc +abstract mixin class $TurnStartParamsDtoCopyWith<$Res> { + factory $TurnStartParamsDtoCopyWith(TurnStartParamsDto value, $Res Function(TurnStartParamsDto) _then) = _$TurnStartParamsDtoCopyWithImpl; +@useResult +$Res call({ + String agentId, String turnId, String prompt +}); + + + + +} +/// @nodoc +class _$TurnStartParamsDtoCopyWithImpl<$Res> + implements $TurnStartParamsDtoCopyWith<$Res> { + _$TurnStartParamsDtoCopyWithImpl(this._self, this._then); + + final TurnStartParamsDto _self; + final $Res Function(TurnStartParamsDto) _then; + +/// Create a copy of TurnStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,Object? turnId = null,Object? prompt = null,}) { + return _then(_self.copyWith( +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String,turnId: null == turnId ? _self.turnId : turnId // ignore: cast_nullable_to_non_nullable +as String,prompt: null == prompt ? _self.prompt : prompt // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [TurnStartParamsDto]. +extension TurnStartParamsDtoPatterns on TurnStartParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _TurnStartParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _TurnStartParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _TurnStartParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _TurnStartParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _TurnStartParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _TurnStartParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId, String turnId, String prompt)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _TurnStartParamsDto() when $default != null: +return $default(_that.agentId,_that.turnId,_that.prompt);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String agentId, String turnId, String prompt) $default,) {final _that = this; +switch (_that) { +case _TurnStartParamsDto(): +return $default(_that.agentId,_that.turnId,_that.prompt);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId, String turnId, String prompt)? $default,) {final _that = this; +switch (_that) { +case _TurnStartParamsDto() when $default != null: +return $default(_that.agentId,_that.turnId,_that.prompt);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _TurnStartParamsDto implements TurnStartParamsDto { + const _TurnStartParamsDto({required this.agentId, required this.turnId, required this.prompt}); + factory _TurnStartParamsDto.fromJson(Map json) => _$TurnStartParamsDtoFromJson(json); + +@override final String agentId; +@override final String turnId; +@override final String prompt; + +/// Create a copy of TurnStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$TurnStartParamsDtoCopyWith<_TurnStartParamsDto> get copyWith => __$TurnStartParamsDtoCopyWithImpl<_TurnStartParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$TurnStartParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _TurnStartParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.turnId, turnId) || other.turnId == turnId)&&(identical(other.prompt, prompt) || other.prompt == prompt)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,agentId,turnId,prompt); + +@override +String toString() { + return 'TurnStartParamsDto(agentId: $agentId, turnId: $turnId, prompt: $prompt)'; +} + + +} + +/// @nodoc +abstract mixin class _$TurnStartParamsDtoCopyWith<$Res> implements $TurnStartParamsDtoCopyWith<$Res> { + factory _$TurnStartParamsDtoCopyWith(_TurnStartParamsDto value, $Res Function(_TurnStartParamsDto) _then) = __$TurnStartParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String agentId, String turnId, String prompt +}); + + + + +} +/// @nodoc +class __$TurnStartParamsDtoCopyWithImpl<$Res> + implements _$TurnStartParamsDtoCopyWith<$Res> { + __$TurnStartParamsDtoCopyWithImpl(this._self, this._then); + + final _TurnStartParamsDto _self; + final $Res Function(_TurnStartParamsDto) _then; + +/// Create a copy of TurnStartParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,Object? turnId = null,Object? prompt = null,}) { + return _then(_TurnStartParamsDto( +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String,turnId: null == turnId ? _self.turnId : turnId // ignore: cast_nullable_to_non_nullable +as String,prompt: null == prompt ? _self.prompt : prompt // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$AgentIdParamsDto { + + String get agentId; +/// Create a copy of AgentIdParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$AgentIdParamsDtoCopyWith get copyWith => _$AgentIdParamsDtoCopyWithImpl(this as AgentIdParamsDto, _$identity); + + /// Serializes this AgentIdParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentIdParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,agentId); + +@override +String toString() { + return 'AgentIdParamsDto(agentId: $agentId)'; +} + + +} + +/// @nodoc +abstract mixin class $AgentIdParamsDtoCopyWith<$Res> { + factory $AgentIdParamsDtoCopyWith(AgentIdParamsDto value, $Res Function(AgentIdParamsDto) _then) = _$AgentIdParamsDtoCopyWithImpl; +@useResult +$Res call({ + String agentId +}); + + + + +} +/// @nodoc +class _$AgentIdParamsDtoCopyWithImpl<$Res> + implements $AgentIdParamsDtoCopyWith<$Res> { + _$AgentIdParamsDtoCopyWithImpl(this._self, this._then); + + final AgentIdParamsDto _self; + final $Res Function(AgentIdParamsDto) _then; + +/// Create a copy of AgentIdParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,}) { + return _then(_self.copyWith( +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + +} + + +/// Adds pattern-matching-related methods to [AgentIdParamsDto]. +extension AgentIdParamsDtoPatterns on AgentIdParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _AgentIdParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _AgentIdParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _AgentIdParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _AgentIdParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentIdParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _AgentIdParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _AgentIdParamsDto() when $default != null: +return $default(_that.agentId);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String agentId) $default,) {final _that = this; +switch (_that) { +case _AgentIdParamsDto(): +return $default(_that.agentId);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId)? $default,) {final _that = this; +switch (_that) { +case _AgentIdParamsDto() when $default != null: +return $default(_that.agentId);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _AgentIdParamsDto implements AgentIdParamsDto { + const _AgentIdParamsDto({required this.agentId}); + factory _AgentIdParamsDto.fromJson(Map json) => _$AgentIdParamsDtoFromJson(json); + +@override final String agentId; + +/// Create a copy of AgentIdParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$AgentIdParamsDtoCopyWith<_AgentIdParamsDto> get copyWith => __$AgentIdParamsDtoCopyWithImpl<_AgentIdParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$AgentIdParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentIdParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,agentId); + +@override +String toString() { + return 'AgentIdParamsDto(agentId: $agentId)'; +} + + +} + +/// @nodoc +abstract mixin class _$AgentIdParamsDtoCopyWith<$Res> implements $AgentIdParamsDtoCopyWith<$Res> { + factory _$AgentIdParamsDtoCopyWith(_AgentIdParamsDto value, $Res Function(_AgentIdParamsDto) _then) = __$AgentIdParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String agentId +}); + + + + +} +/// @nodoc +class __$AgentIdParamsDtoCopyWithImpl<$Res> + implements _$AgentIdParamsDtoCopyWith<$Res> { + __$AgentIdParamsDtoCopyWithImpl(this._self, this._then); + + final _AgentIdParamsDto _self; + final $Res Function(_AgentIdParamsDto) _then; + +/// Create a copy of AgentIdParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,}) { + return _then(_AgentIdParamsDto( +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + + +/// @nodoc +mixin _$ApprovalResolveParamsDto { + + String get approvalId; bool get approved; +/// Create a copy of ApprovalResolveParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ApprovalResolveParamsDtoCopyWith get copyWith => _$ApprovalResolveParamsDtoCopyWithImpl(this as ApprovalResolveParamsDto, _$identity); + + /// Serializes this ApprovalResolveParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ApprovalResolveParamsDto&&(identical(other.approvalId, approvalId) || other.approvalId == approvalId)&&(identical(other.approved, approved) || other.approved == approved)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,approvalId,approved); + +@override +String toString() { + return 'ApprovalResolveParamsDto(approvalId: $approvalId, approved: $approved)'; +} + + +} + +/// @nodoc +abstract mixin class $ApprovalResolveParamsDtoCopyWith<$Res> { + factory $ApprovalResolveParamsDtoCopyWith(ApprovalResolveParamsDto value, $Res Function(ApprovalResolveParamsDto) _then) = _$ApprovalResolveParamsDtoCopyWithImpl; +@useResult +$Res call({ + String approvalId, bool approved +}); + + + + +} +/// @nodoc +class _$ApprovalResolveParamsDtoCopyWithImpl<$Res> + implements $ApprovalResolveParamsDtoCopyWith<$Res> { + _$ApprovalResolveParamsDtoCopyWithImpl(this._self, this._then); + + final ApprovalResolveParamsDto _self; + final $Res Function(ApprovalResolveParamsDto) _then; + +/// Create a copy of ApprovalResolveParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? approvalId = null,Object? approved = null,}) { + return _then(_self.copyWith( +approvalId: null == approvalId ? _self.approvalId : approvalId // ignore: cast_nullable_to_non_nullable +as String,approved: null == approved ? _self.approved : approved // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + +} + + +/// Adds pattern-matching-related methods to [ApprovalResolveParamsDto]. +extension ApprovalResolveParamsDtoPatterns on ApprovalResolveParamsDto { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _ApprovalResolveParamsDto value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _ApprovalResolveParamsDto() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _ApprovalResolveParamsDto value) $default,){ +final _that = this; +switch (_that) { +case _ApprovalResolveParamsDto(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ApprovalResolveParamsDto value)? $default,){ +final _that = this; +switch (_that) { +case _ApprovalResolveParamsDto() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String approvalId, bool approved)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _ApprovalResolveParamsDto() when $default != null: +return $default(_that.approvalId,_that.approved);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String approvalId, bool approved) $default,) {final _that = this; +switch (_that) { +case _ApprovalResolveParamsDto(): +return $default(_that.approvalId,_that.approved);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String approvalId, bool approved)? $default,) {final _that = this; +switch (_that) { +case _ApprovalResolveParamsDto() when $default != null: +return $default(_that.approvalId,_that.approved);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _ApprovalResolveParamsDto implements ApprovalResolveParamsDto { + const _ApprovalResolveParamsDto({required this.approvalId, required this.approved}); + factory _ApprovalResolveParamsDto.fromJson(Map json) => _$ApprovalResolveParamsDtoFromJson(json); + +@override final String approvalId; +@override final bool approved; + +/// Create a copy of ApprovalResolveParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$ApprovalResolveParamsDtoCopyWith<_ApprovalResolveParamsDto> get copyWith => __$ApprovalResolveParamsDtoCopyWithImpl<_ApprovalResolveParamsDto>(this, _$identity); + +@override +Map toJson() { + return _$ApprovalResolveParamsDtoToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _ApprovalResolveParamsDto&&(identical(other.approvalId, approvalId) || other.approvalId == approvalId)&&(identical(other.approved, approved) || other.approved == approved)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,approvalId,approved); + +@override +String toString() { + return 'ApprovalResolveParamsDto(approvalId: $approvalId, approved: $approved)'; +} + + +} + +/// @nodoc +abstract mixin class _$ApprovalResolveParamsDtoCopyWith<$Res> implements $ApprovalResolveParamsDtoCopyWith<$Res> { + factory _$ApprovalResolveParamsDtoCopyWith(_ApprovalResolveParamsDto value, $Res Function(_ApprovalResolveParamsDto) _then) = __$ApprovalResolveParamsDtoCopyWithImpl; +@override @useResult +$Res call({ + String approvalId, bool approved +}); + + + + +} +/// @nodoc +class __$ApprovalResolveParamsDtoCopyWithImpl<$Res> + implements _$ApprovalResolveParamsDtoCopyWith<$Res> { + __$ApprovalResolveParamsDtoCopyWithImpl(this._self, this._then); + + final _ApprovalResolveParamsDto _self; + final $Res Function(_ApprovalResolveParamsDto) _then; + +/// Create a copy of ApprovalResolveParamsDto +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? approvalId = null,Object? approved = null,}) { + return _then(_ApprovalResolveParamsDto( +approvalId: null == approvalId ? _self.approvalId : approvalId // ignore: cast_nullable_to_non_nullable +as String,approved: null == approved ? _self.approved : approved // ignore: cast_nullable_to_non_nullable +as bool, + )); +} + + +} + + +/// @nodoc +mixin _$TimelineSubscribeParamsDto { + + String get agentId; int get afterSequence; +/// Create a copy of TimelineSubscribeParamsDto +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$TimelineSubscribeParamsDtoCopyWith get copyWith => _$TimelineSubscribeParamsDtoCopyWithImpl(this as TimelineSubscribeParamsDto, _$identity); + + /// Serializes this TimelineSubscribeParamsDto to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is TimelineSubscribeParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.afterSequence, afterSequence) || other.afterSequence == afterSequence)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,agentId,afterSequence); + +@override +String toString() { + return 'TimelineSubscribeParamsDto(agentId: $agentId, afterSequence: $afterSequence)'; +} + + +} + +/// @nodoc +abstract mixin class $TimelineSubscribeParamsDtoCopyWith<$Res> { + factory $TimelineSubscribeParamsDtoCopyWith(TimelineSubscribeParamsDto value, $Res Function(TimelineSubscribeParamsDto) _then) = _$TimelineSubscribeParamsDtoCopyWithImpl; +@useResult +$Res call({ + String agentId, int afterSequence +}); + + + + +} +/// @nodoc +class _$TimelineSubscribeParamsDtoCopyWithImpl<$Res> + implements $TimelineSubscribeParamsDtoCopyWith<$Res> { + _$TimelineSubscribeParamsDtoCopyWithImpl(this._self, this._then); + + final TimelineSubscribeParamsDto _self; + final $Res Function(TimelineSubscribeParamsDto) _then; + +/// Create a copy of TimelineSubscribeParamsDto +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,Object? afterSequence = null,}) { + return _then(_self.copyWith( +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String,afterSequence: null == afterSequence ? _self.afterSequence : afterSequence // ignore: cast_nullable_to_non_nullable +as int, + )); } + } -/// Adds pattern-matching-related methods to [ProviderCustomCreateParamsDto]. -extension ProviderCustomCreateParamsDtoPatterns on ProviderCustomCreateParamsDto { +/// Adds pattern-matching-related methods to [TimelineSubscribeParamsDto]. +extension TimelineSubscribeParamsDtoPatterns on TimelineSubscribeParamsDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -3589,10 +6552,10 @@ extension ProviderCustomCreateParamsDtoPatterns on ProviderCustomCreateParamsDto /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderCustomCreateParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _TimelineSubscribeParamsDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderCustomCreateParamsDto() when $default != null: +case _TimelineSubscribeParamsDto() when $default != null: return $default(_that);case _: return orElse(); @@ -3611,10 +6574,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderCustomCreateParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _TimelineSubscribeParamsDto value) $default,){ final _that = this; switch (_that) { -case _ProviderCustomCreateParamsDto(): +case _TimelineSubscribeParamsDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -3632,10 +6595,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderCustomCreateParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _TimelineSubscribeParamsDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderCustomCreateParamsDto() when $default != null: +case _TimelineSubscribeParamsDto() when $default != null: return $default(_that);case _: return null; @@ -3653,10 +6616,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId, int afterSequence)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderCustomCreateParamsDto() when $default != null: -return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: +case _TimelineSubscribeParamsDto() when $default != null: +return $default(_that.agentId,_that.afterSequence);case _: return orElse(); } @@ -3674,10 +6637,10 @@ return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String agentId, int afterSequence) $default,) {final _that = this; switch (_that) { -case _ProviderCustomCreateParamsDto(): -return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: +case _TimelineSubscribeParamsDto(): +return $default(_that.agentId,_that.afterSequence);case _: throw StateError('Unexpected subclass'); } @@ -3694,10 +6657,10 @@ return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId, int afterSequence)? $default,) {final _that = this; switch (_that) { -case _ProviderCustomCreateParamsDto() when $default != null: -return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: +case _TimelineSubscribeParamsDto() when $default != null: +return $default(_that.agentId,_that.afterSequence);case _: return null; } @@ -3708,164 +6671,149 @@ return $default(_that.id,_that.config,_that.makeDefault,_that.apiKey);case _: /// @nodoc @JsonSerializable() -class _ProviderCustomCreateParamsDto implements ProviderCustomCreateParamsDto { - const _ProviderCustomCreateParamsDto({required this.id, required this.config, required this.makeDefault, this.apiKey}); - factory _ProviderCustomCreateParamsDto.fromJson(Map json) => _$ProviderCustomCreateParamsDtoFromJson(json); +class _TimelineSubscribeParamsDto implements TimelineSubscribeParamsDto { + const _TimelineSubscribeParamsDto({required this.agentId, required this.afterSequence}); + factory _TimelineSubscribeParamsDto.fromJson(Map json) => _$TimelineSubscribeParamsDtoFromJson(json); -@override final String id; -@override final CustomProviderConfigDto config; -@override final bool makeDefault; -@override final String? apiKey; +@override final String agentId; +@override final int afterSequence; -/// Create a copy of ProviderCustomCreateParamsDto +/// Create a copy of TimelineSubscribeParamsDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderCustomCreateParamsDtoCopyWith<_ProviderCustomCreateParamsDto> get copyWith => __$ProviderCustomCreateParamsDtoCopyWithImpl<_ProviderCustomCreateParamsDto>(this, _$identity); +_$TimelineSubscribeParamsDtoCopyWith<_TimelineSubscribeParamsDto> get copyWith => __$TimelineSubscribeParamsDtoCopyWithImpl<_TimelineSubscribeParamsDto>(this, _$identity); @override Map toJson() { - return _$ProviderCustomCreateParamsDtoToJson(this, ); + return _$TimelineSubscribeParamsDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderCustomCreateParamsDto&&(identical(other.id, id) || other.id == id)&&(identical(other.config, config) || other.config == config)&&(identical(other.makeDefault, makeDefault) || other.makeDefault == makeDefault)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _TimelineSubscribeParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.afterSequence, afterSequence) || other.afterSequence == afterSequence)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,id,config,makeDefault,apiKey); +int get hashCode => Object.hash(runtimeType,agentId,afterSequence); @override String toString() { - return 'ProviderCustomCreateParamsDto(id: $id, config: $config, makeDefault: $makeDefault, apiKey: $apiKey)'; + return 'TimelineSubscribeParamsDto(agentId: $agentId, afterSequence: $afterSequence)'; } } /// @nodoc -abstract mixin class _$ProviderCustomCreateParamsDtoCopyWith<$Res> implements $ProviderCustomCreateParamsDtoCopyWith<$Res> { - factory _$ProviderCustomCreateParamsDtoCopyWith(_ProviderCustomCreateParamsDto value, $Res Function(_ProviderCustomCreateParamsDto) _then) = __$ProviderCustomCreateParamsDtoCopyWithImpl; +abstract mixin class _$TimelineSubscribeParamsDtoCopyWith<$Res> implements $TimelineSubscribeParamsDtoCopyWith<$Res> { + factory _$TimelineSubscribeParamsDtoCopyWith(_TimelineSubscribeParamsDto value, $Res Function(_TimelineSubscribeParamsDto) _then) = __$TimelineSubscribeParamsDtoCopyWithImpl; @override @useResult $Res call({ - String id, CustomProviderConfigDto config, bool makeDefault, String? apiKey + String agentId, int afterSequence }); -@override $CustomProviderConfigDtoCopyWith<$Res> get config; + } /// @nodoc -class __$ProviderCustomCreateParamsDtoCopyWithImpl<$Res> - implements _$ProviderCustomCreateParamsDtoCopyWith<$Res> { - __$ProviderCustomCreateParamsDtoCopyWithImpl(this._self, this._then); +class __$TimelineSubscribeParamsDtoCopyWithImpl<$Res> + implements _$TimelineSubscribeParamsDtoCopyWith<$Res> { + __$TimelineSubscribeParamsDtoCopyWithImpl(this._self, this._then); - final _ProviderCustomCreateParamsDto _self; - final $Res Function(_ProviderCustomCreateParamsDto) _then; + final _TimelineSubscribeParamsDto _self; + final $Res Function(_TimelineSubscribeParamsDto) _then; -/// Create a copy of ProviderCustomCreateParamsDto +/// Create a copy of TimelineSubscribeParamsDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? config = null,Object? makeDefault = null,Object? apiKey = freezed,}) { - return _then(_ProviderCustomCreateParamsDto( -id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable -as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable -as CustomProviderConfigDto,makeDefault: null == makeDefault ? _self.makeDefault : makeDefault // ignore: cast_nullable_to_non_nullable -as bool,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable -as String?, +@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,Object? afterSequence = null,}) { + return _then(_TimelineSubscribeParamsDto( +agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable +as String,afterSequence: null == afterSequence ? _self.afterSequence : afterSequence // ignore: cast_nullable_to_non_nullable +as int, )); } -/// Create a copy of ProviderCustomCreateParamsDto -/// with the given fields replaced by the non-null parameter values. -@override -@pragma('vm:prefer-inline') -$CustomProviderConfigDtoCopyWith<$Res> get config { - - return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { - return _then(_self.copyWith(config: value)); - }); -} + } /// @nodoc -mixin _$ProviderCustomUpdateParamsDto { +mixin _$WorkspaceCatalogResultDto { - String get connectionId; CustomProviderConfigDto get config; String? get apiKey; -/// Create a copy of ProviderCustomUpdateParamsDto + WorkspaceCatalogDto get catalog; +/// Create a copy of WorkspaceCatalogResultDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ProviderCustomUpdateParamsDtoCopyWith get copyWith => _$ProviderCustomUpdateParamsDtoCopyWithImpl(this as ProviderCustomUpdateParamsDto, _$identity); +$WorkspaceCatalogResultDtoCopyWith get copyWith => _$WorkspaceCatalogResultDtoCopyWithImpl(this as WorkspaceCatalogResultDto, _$identity); - /// Serializes this ProviderCustomUpdateParamsDto to a JSON map. + /// Serializes this WorkspaceCatalogResultDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ProviderCustomUpdateParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.config, config) || other.config == config)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceCatalogResultDto&&(identical(other.catalog, catalog) || other.catalog == catalog)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId,config,apiKey); +int get hashCode => Object.hash(runtimeType,catalog); @override String toString() { - return 'ProviderCustomUpdateParamsDto(connectionId: $connectionId, config: $config, apiKey: $apiKey)'; + return 'WorkspaceCatalogResultDto(catalog: $catalog)'; } } /// @nodoc -abstract mixin class $ProviderCustomUpdateParamsDtoCopyWith<$Res> { - factory $ProviderCustomUpdateParamsDtoCopyWith(ProviderCustomUpdateParamsDto value, $Res Function(ProviderCustomUpdateParamsDto) _then) = _$ProviderCustomUpdateParamsDtoCopyWithImpl; +abstract mixin class $WorkspaceCatalogResultDtoCopyWith<$Res> { + factory $WorkspaceCatalogResultDtoCopyWith(WorkspaceCatalogResultDto value, $Res Function(WorkspaceCatalogResultDto) _then) = _$WorkspaceCatalogResultDtoCopyWithImpl; @useResult $Res call({ - String connectionId, CustomProviderConfigDto config, String? apiKey + WorkspaceCatalogDto catalog }); -$CustomProviderConfigDtoCopyWith<$Res> get config; +$WorkspaceCatalogDtoCopyWith<$Res> get catalog; } /// @nodoc -class _$ProviderCustomUpdateParamsDtoCopyWithImpl<$Res> - implements $ProviderCustomUpdateParamsDtoCopyWith<$Res> { - _$ProviderCustomUpdateParamsDtoCopyWithImpl(this._self, this._then); +class _$WorkspaceCatalogResultDtoCopyWithImpl<$Res> + implements $WorkspaceCatalogResultDtoCopyWith<$Res> { + _$WorkspaceCatalogResultDtoCopyWithImpl(this._self, this._then); - final ProviderCustomUpdateParamsDto _self; - final $Res Function(ProviderCustomUpdateParamsDto) _then; + final WorkspaceCatalogResultDto _self; + final $Res Function(WorkspaceCatalogResultDto) _then; -/// Create a copy of ProviderCustomUpdateParamsDto +/// Create a copy of WorkspaceCatalogResultDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? connectionId = null,Object? config = null,Object? apiKey = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? catalog = null,}) { return _then(_self.copyWith( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable -as CustomProviderConfigDto,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable -as String?, +catalog: null == catalog ? _self.catalog : catalog // ignore: cast_nullable_to_non_nullable +as WorkspaceCatalogDto, )); } -/// Create a copy of ProviderCustomUpdateParamsDto +/// Create a copy of WorkspaceCatalogResultDto /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$CustomProviderConfigDtoCopyWith<$Res> get config { +$WorkspaceCatalogDtoCopyWith<$Res> get catalog { - return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { - return _then(_self.copyWith(config: value)); + return $WorkspaceCatalogDtoCopyWith<$Res>(_self.catalog, (value) { + return _then(_self.copyWith(catalog: value)); }); } } -/// Adds pattern-matching-related methods to [ProviderCustomUpdateParamsDto]. -extension ProviderCustomUpdateParamsDtoPatterns on ProviderCustomUpdateParamsDto { +/// Adds pattern-matching-related methods to [WorkspaceCatalogResultDto]. +extension WorkspaceCatalogResultDtoPatterns on WorkspaceCatalogResultDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -3878,10 +6826,10 @@ extension ProviderCustomUpdateParamsDtoPatterns on ProviderCustomUpdateParamsDto /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ProviderCustomUpdateParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceCatalogResultDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ProviderCustomUpdateParamsDto() when $default != null: +case _WorkspaceCatalogResultDto() when $default != null: return $default(_that);case _: return orElse(); @@ -3900,10 +6848,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ProviderCustomUpdateParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorkspaceCatalogResultDto value) $default,){ final _that = this; switch (_that) { -case _ProviderCustomUpdateParamsDto(): +case _WorkspaceCatalogResultDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -3921,10 +6869,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ProviderCustomUpdateParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceCatalogResultDto value)? $default,){ final _that = this; switch (_that) { -case _ProviderCustomUpdateParamsDto() when $default != null: +case _WorkspaceCatalogResultDto() when $default != null: return $default(_that);case _: return null; @@ -3942,10 +6890,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String connectionId, CustomProviderConfigDto config, String? apiKey)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( WorkspaceCatalogDto catalog)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ProviderCustomUpdateParamsDto() when $default != null: -return $default(_that.connectionId,_that.config,_that.apiKey);case _: +case _WorkspaceCatalogResultDto() when $default != null: +return $default(_that.catalog);case _: return orElse(); } @@ -3963,10 +6911,10 @@ return $default(_that.connectionId,_that.config,_that.apiKey);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String connectionId, CustomProviderConfigDto config, String? apiKey) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( WorkspaceCatalogDto catalog) $default,) {final _that = this; switch (_that) { -case _ProviderCustomUpdateParamsDto(): -return $default(_that.connectionId,_that.config,_that.apiKey);case _: +case _WorkspaceCatalogResultDto(): +return $default(_that.catalog);case _: throw StateError('Unexpected subclass'); } @@ -3983,10 +6931,10 @@ return $default(_that.connectionId,_that.config,_that.apiKey);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String connectionId, CustomProviderConfigDto config, String? apiKey)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( WorkspaceCatalogDto catalog)? $default,) {final _that = this; switch (_that) { -case _ProviderCustomUpdateParamsDto() when $default != null: -return $default(_that.connectionId,_that.config,_that.apiKey);case _: +case _WorkspaceCatalogResultDto() when $default != null: +return $default(_that.catalog);case _: return null; } @@ -3997,153 +6945,157 @@ return $default(_that.connectionId,_that.config,_that.apiKey);case _: /// @nodoc @JsonSerializable() -class _ProviderCustomUpdateParamsDto implements ProviderCustomUpdateParamsDto { - const _ProviderCustomUpdateParamsDto({required this.connectionId, required this.config, this.apiKey}); - factory _ProviderCustomUpdateParamsDto.fromJson(Map json) => _$ProviderCustomUpdateParamsDtoFromJson(json); +class _WorkspaceCatalogResultDto implements WorkspaceCatalogResultDto { + const _WorkspaceCatalogResultDto({required this.catalog}); + factory _WorkspaceCatalogResultDto.fromJson(Map json) => _$WorkspaceCatalogResultDtoFromJson(json); -@override final String connectionId; -@override final CustomProviderConfigDto config; -@override final String? apiKey; +@override final WorkspaceCatalogDto catalog; -/// Create a copy of ProviderCustomUpdateParamsDto +/// Create a copy of WorkspaceCatalogResultDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ProviderCustomUpdateParamsDtoCopyWith<_ProviderCustomUpdateParamsDto> get copyWith => __$ProviderCustomUpdateParamsDtoCopyWithImpl<_ProviderCustomUpdateParamsDto>(this, _$identity); +_$WorkspaceCatalogResultDtoCopyWith<_WorkspaceCatalogResultDto> get copyWith => __$WorkspaceCatalogResultDtoCopyWithImpl<_WorkspaceCatalogResultDto>(this, _$identity); @override Map toJson() { - return _$ProviderCustomUpdateParamsDtoToJson(this, ); + return _$WorkspaceCatalogResultDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProviderCustomUpdateParamsDto&&(identical(other.connectionId, connectionId) || other.connectionId == connectionId)&&(identical(other.config, config) || other.config == config)&&(identical(other.apiKey, apiKey) || other.apiKey == apiKey)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceCatalogResultDto&&(identical(other.catalog, catalog) || other.catalog == catalog)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,connectionId,config,apiKey); +int get hashCode => Object.hash(runtimeType,catalog); @override String toString() { - return 'ProviderCustomUpdateParamsDto(connectionId: $connectionId, config: $config, apiKey: $apiKey)'; + return 'WorkspaceCatalogResultDto(catalog: $catalog)'; } } /// @nodoc -abstract mixin class _$ProviderCustomUpdateParamsDtoCopyWith<$Res> implements $ProviderCustomUpdateParamsDtoCopyWith<$Res> { - factory _$ProviderCustomUpdateParamsDtoCopyWith(_ProviderCustomUpdateParamsDto value, $Res Function(_ProviderCustomUpdateParamsDto) _then) = __$ProviderCustomUpdateParamsDtoCopyWithImpl; +abstract mixin class _$WorkspaceCatalogResultDtoCopyWith<$Res> implements $WorkspaceCatalogResultDtoCopyWith<$Res> { + factory _$WorkspaceCatalogResultDtoCopyWith(_WorkspaceCatalogResultDto value, $Res Function(_WorkspaceCatalogResultDto) _then) = __$WorkspaceCatalogResultDtoCopyWithImpl; @override @useResult $Res call({ - String connectionId, CustomProviderConfigDto config, String? apiKey + WorkspaceCatalogDto catalog }); -@override $CustomProviderConfigDtoCopyWith<$Res> get config; +@override $WorkspaceCatalogDtoCopyWith<$Res> get catalog; } /// @nodoc -class __$ProviderCustomUpdateParamsDtoCopyWithImpl<$Res> - implements _$ProviderCustomUpdateParamsDtoCopyWith<$Res> { - __$ProviderCustomUpdateParamsDtoCopyWithImpl(this._self, this._then); +class __$WorkspaceCatalogResultDtoCopyWithImpl<$Res> + implements _$WorkspaceCatalogResultDtoCopyWith<$Res> { + __$WorkspaceCatalogResultDtoCopyWithImpl(this._self, this._then); - final _ProviderCustomUpdateParamsDto _self; - final $Res Function(_ProviderCustomUpdateParamsDto) _then; + final _WorkspaceCatalogResultDto _self; + final $Res Function(_WorkspaceCatalogResultDto) _then; -/// Create a copy of ProviderCustomUpdateParamsDto +/// Create a copy of WorkspaceCatalogResultDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? connectionId = null,Object? config = null,Object? apiKey = freezed,}) { - return _then(_ProviderCustomUpdateParamsDto( -connectionId: null == connectionId ? _self.connectionId : connectionId // ignore: cast_nullable_to_non_nullable -as String,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable -as CustomProviderConfigDto,apiKey: freezed == apiKey ? _self.apiKey : apiKey // ignore: cast_nullable_to_non_nullable -as String?, +@override @pragma('vm:prefer-inline') $Res call({Object? catalog = null,}) { + return _then(_WorkspaceCatalogResultDto( +catalog: null == catalog ? _self.catalog : catalog // ignore: cast_nullable_to_non_nullable +as WorkspaceCatalogDto, )); } -/// Create a copy of ProviderCustomUpdateParamsDto +/// Create a copy of WorkspaceCatalogResultDto /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$CustomProviderConfigDtoCopyWith<$Res> get config { +$WorkspaceCatalogDtoCopyWith<$Res> get catalog { - return $CustomProviderConfigDtoCopyWith<$Res>(_self.config, (value) { - return _then(_self.copyWith(config: value)); + return $WorkspaceCatalogDtoCopyWith<$Res>(_self.catalog, (value) { + return _then(_self.copyWith(catalog: value)); }); } } /// @nodoc -mixin _$TurnStartParamsDto { +mixin _$WorkspaceRegisterResultDto { - String get agentId; String get turnId; String get prompt; -/// Create a copy of TurnStartParamsDto + WorkspaceDto get workspace; List get worktrees; +/// Create a copy of WorkspaceRegisterResultDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$TurnStartParamsDtoCopyWith get copyWith => _$TurnStartParamsDtoCopyWithImpl(this as TurnStartParamsDto, _$identity); +$WorkspaceRegisterResultDtoCopyWith get copyWith => _$WorkspaceRegisterResultDtoCopyWithImpl(this as WorkspaceRegisterResultDto, _$identity); - /// Serializes this TurnStartParamsDto to a JSON map. + /// Serializes this WorkspaceRegisterResultDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is TurnStartParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.turnId, turnId) || other.turnId == turnId)&&(identical(other.prompt, prompt) || other.prompt == prompt)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceRegisterResultDto&&(identical(other.workspace, workspace) || other.workspace == workspace)&&const DeepCollectionEquality().equals(other.worktrees, worktrees)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId,turnId,prompt); +int get hashCode => Object.hash(runtimeType,workspace,const DeepCollectionEquality().hash(worktrees)); @override String toString() { - return 'TurnStartParamsDto(agentId: $agentId, turnId: $turnId, prompt: $prompt)'; + return 'WorkspaceRegisterResultDto(workspace: $workspace, worktrees: $worktrees)'; } } /// @nodoc -abstract mixin class $TurnStartParamsDtoCopyWith<$Res> { - factory $TurnStartParamsDtoCopyWith(TurnStartParamsDto value, $Res Function(TurnStartParamsDto) _then) = _$TurnStartParamsDtoCopyWithImpl; +abstract mixin class $WorkspaceRegisterResultDtoCopyWith<$Res> { + factory $WorkspaceRegisterResultDtoCopyWith(WorkspaceRegisterResultDto value, $Res Function(WorkspaceRegisterResultDto) _then) = _$WorkspaceRegisterResultDtoCopyWithImpl; @useResult $Res call({ - String agentId, String turnId, String prompt + WorkspaceDto workspace, List worktrees }); - +$WorkspaceDtoCopyWith<$Res> get workspace; } /// @nodoc -class _$TurnStartParamsDtoCopyWithImpl<$Res> - implements $TurnStartParamsDtoCopyWith<$Res> { - _$TurnStartParamsDtoCopyWithImpl(this._self, this._then); +class _$WorkspaceRegisterResultDtoCopyWithImpl<$Res> + implements $WorkspaceRegisterResultDtoCopyWith<$Res> { + _$WorkspaceRegisterResultDtoCopyWithImpl(this._self, this._then); - final TurnStartParamsDto _self; - final $Res Function(TurnStartParamsDto) _then; + final WorkspaceRegisterResultDto _self; + final $Res Function(WorkspaceRegisterResultDto) _then; -/// Create a copy of TurnStartParamsDto +/// Create a copy of WorkspaceRegisterResultDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,Object? turnId = null,Object? prompt = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? workspace = null,Object? worktrees = null,}) { return _then(_self.copyWith( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String,turnId: null == turnId ? _self.turnId : turnId // ignore: cast_nullable_to_non_nullable -as String,prompt: null == prompt ? _self.prompt : prompt // ignore: cast_nullable_to_non_nullable -as String, +workspace: null == workspace ? _self.workspace : workspace // ignore: cast_nullable_to_non_nullable +as WorkspaceDto,worktrees: null == worktrees ? _self.worktrees : worktrees // ignore: cast_nullable_to_non_nullable +as List, )); } - +/// Create a copy of WorkspaceRegisterResultDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$WorkspaceDtoCopyWith<$Res> get workspace { + + return $WorkspaceDtoCopyWith<$Res>(_self.workspace, (value) { + return _then(_self.copyWith(workspace: value)); + }); +} } -/// Adds pattern-matching-related methods to [TurnStartParamsDto]. -extension TurnStartParamsDtoPatterns on TurnStartParamsDto { +/// Adds pattern-matching-related methods to [WorkspaceRegisterResultDto]. +extension WorkspaceRegisterResultDtoPatterns on WorkspaceRegisterResultDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -4156,10 +7108,10 @@ extension TurnStartParamsDtoPatterns on TurnStartParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _TurnStartParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceRegisterResultDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _TurnStartParamsDto() when $default != null: +case _WorkspaceRegisterResultDto() when $default != null: return $default(_that);case _: return orElse(); @@ -4178,10 +7130,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _TurnStartParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorkspaceRegisterResultDto value) $default,){ final _that = this; switch (_that) { -case _TurnStartParamsDto(): +case _WorkspaceRegisterResultDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -4199,10 +7151,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _TurnStartParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceRegisterResultDto value)? $default,){ final _that = this; switch (_that) { -case _TurnStartParamsDto() when $default != null: +case _WorkspaceRegisterResultDto() when $default != null: return $default(_that);case _: return null; @@ -4220,10 +7172,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId, String turnId, String prompt)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( WorkspaceDto workspace, List worktrees)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _TurnStartParamsDto() when $default != null: -return $default(_that.agentId,_that.turnId,_that.prompt);case _: +case _WorkspaceRegisterResultDto() when $default != null: +return $default(_that.workspace,_that.worktrees);case _: return orElse(); } @@ -4241,10 +7193,10 @@ return $default(_that.agentId,_that.turnId,_that.prompt);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String agentId, String turnId, String prompt) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( WorkspaceDto workspace, List worktrees) $default,) {final _that = this; switch (_that) { -case _TurnStartParamsDto(): -return $default(_that.agentId,_that.turnId,_that.prompt);case _: +case _WorkspaceRegisterResultDto(): +return $default(_that.workspace,_that.worktrees);case _: throw StateError('Unexpected subclass'); } @@ -4261,10 +7213,10 @@ return $default(_that.agentId,_that.turnId,_that.prompt);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId, String turnId, String prompt)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( WorkspaceDto workspace, List worktrees)? $default,) {final _that = this; switch (_that) { -case _TurnStartParamsDto() when $default != null: -return $default(_that.agentId,_that.turnId,_that.prompt);case _: +case _WorkspaceRegisterResultDto() when $default != null: +return $default(_that.workspace,_that.worktrees);case _: return null; } @@ -4275,114 +7227,127 @@ return $default(_that.agentId,_that.turnId,_that.prompt);case _: /// @nodoc @JsonSerializable() -class _TurnStartParamsDto implements TurnStartParamsDto { - const _TurnStartParamsDto({required this.agentId, required this.turnId, required this.prompt}); - factory _TurnStartParamsDto.fromJson(Map json) => _$TurnStartParamsDtoFromJson(json); +class _WorkspaceRegisterResultDto implements WorkspaceRegisterResultDto { + const _WorkspaceRegisterResultDto({required this.workspace, required final List worktrees}): _worktrees = worktrees; + factory _WorkspaceRegisterResultDto.fromJson(Map json) => _$WorkspaceRegisterResultDtoFromJson(json); -@override final String agentId; -@override final String turnId; -@override final String prompt; +@override final WorkspaceDto workspace; + final List _worktrees; +@override List get worktrees { + if (_worktrees is EqualUnmodifiableListView) return _worktrees; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_worktrees); +} -/// Create a copy of TurnStartParamsDto + +/// Create a copy of WorkspaceRegisterResultDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$TurnStartParamsDtoCopyWith<_TurnStartParamsDto> get copyWith => __$TurnStartParamsDtoCopyWithImpl<_TurnStartParamsDto>(this, _$identity); +_$WorkspaceRegisterResultDtoCopyWith<_WorkspaceRegisterResultDto> get copyWith => __$WorkspaceRegisterResultDtoCopyWithImpl<_WorkspaceRegisterResultDto>(this, _$identity); @override Map toJson() { - return _$TurnStartParamsDtoToJson(this, ); + return _$WorkspaceRegisterResultDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _TurnStartParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.turnId, turnId) || other.turnId == turnId)&&(identical(other.prompt, prompt) || other.prompt == prompt)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceRegisterResultDto&&(identical(other.workspace, workspace) || other.workspace == workspace)&&const DeepCollectionEquality().equals(other._worktrees, _worktrees)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId,turnId,prompt); +int get hashCode => Object.hash(runtimeType,workspace,const DeepCollectionEquality().hash(_worktrees)); @override String toString() { - return 'TurnStartParamsDto(agentId: $agentId, turnId: $turnId, prompt: $prompt)'; + return 'WorkspaceRegisterResultDto(workspace: $workspace, worktrees: $worktrees)'; } } /// @nodoc -abstract mixin class _$TurnStartParamsDtoCopyWith<$Res> implements $TurnStartParamsDtoCopyWith<$Res> { - factory _$TurnStartParamsDtoCopyWith(_TurnStartParamsDto value, $Res Function(_TurnStartParamsDto) _then) = __$TurnStartParamsDtoCopyWithImpl; +abstract mixin class _$WorkspaceRegisterResultDtoCopyWith<$Res> implements $WorkspaceRegisterResultDtoCopyWith<$Res> { + factory _$WorkspaceRegisterResultDtoCopyWith(_WorkspaceRegisterResultDto value, $Res Function(_WorkspaceRegisterResultDto) _then) = __$WorkspaceRegisterResultDtoCopyWithImpl; @override @useResult $Res call({ - String agentId, String turnId, String prompt + WorkspaceDto workspace, List worktrees }); - +@override $WorkspaceDtoCopyWith<$Res> get workspace; } /// @nodoc -class __$TurnStartParamsDtoCopyWithImpl<$Res> - implements _$TurnStartParamsDtoCopyWith<$Res> { - __$TurnStartParamsDtoCopyWithImpl(this._self, this._then); +class __$WorkspaceRegisterResultDtoCopyWithImpl<$Res> + implements _$WorkspaceRegisterResultDtoCopyWith<$Res> { + __$WorkspaceRegisterResultDtoCopyWithImpl(this._self, this._then); - final _TurnStartParamsDto _self; - final $Res Function(_TurnStartParamsDto) _then; + final _WorkspaceRegisterResultDto _self; + final $Res Function(_WorkspaceRegisterResultDto) _then; -/// Create a copy of TurnStartParamsDto +/// Create a copy of WorkspaceRegisterResultDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,Object? turnId = null,Object? prompt = null,}) { - return _then(_TurnStartParamsDto( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String,turnId: null == turnId ? _self.turnId : turnId // ignore: cast_nullable_to_non_nullable -as String,prompt: null == prompt ? _self.prompt : prompt // ignore: cast_nullable_to_non_nullable -as String, +@override @pragma('vm:prefer-inline') $Res call({Object? workspace = null,Object? worktrees = null,}) { + return _then(_WorkspaceRegisterResultDto( +workspace: null == workspace ? _self.workspace : workspace // ignore: cast_nullable_to_non_nullable +as WorkspaceDto,worktrees: null == worktrees ? _self._worktrees : worktrees // ignore: cast_nullable_to_non_nullable +as List, )); } - +/// Create a copy of WorkspaceRegisterResultDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$WorkspaceDtoCopyWith<$Res> get workspace { + + return $WorkspaceDtoCopyWith<$Res>(_self.workspace, (value) { + return _then(_self.copyWith(workspace: value)); + }); +} } /// @nodoc -mixin _$AgentIdParamsDto { +mixin _$WorkspaceUnregisterResultDto { - String get agentId; -/// Create a copy of AgentIdParamsDto + bool get unregistered; +/// Create a copy of WorkspaceUnregisterResultDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$AgentIdParamsDtoCopyWith get copyWith => _$AgentIdParamsDtoCopyWithImpl(this as AgentIdParamsDto, _$identity); +$WorkspaceUnregisterResultDtoCopyWith get copyWith => _$WorkspaceUnregisterResultDtoCopyWithImpl(this as WorkspaceUnregisterResultDto, _$identity); - /// Serializes this AgentIdParamsDto to a JSON map. + /// Serializes this WorkspaceUnregisterResultDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is AgentIdParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceUnregisterResultDto&&(identical(other.unregistered, unregistered) || other.unregistered == unregistered)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId); +int get hashCode => Object.hash(runtimeType,unregistered); @override String toString() { - return 'AgentIdParamsDto(agentId: $agentId)'; + return 'WorkspaceUnregisterResultDto(unregistered: $unregistered)'; } } /// @nodoc -abstract mixin class $AgentIdParamsDtoCopyWith<$Res> { - factory $AgentIdParamsDtoCopyWith(AgentIdParamsDto value, $Res Function(AgentIdParamsDto) _then) = _$AgentIdParamsDtoCopyWithImpl; +abstract mixin class $WorkspaceUnregisterResultDtoCopyWith<$Res> { + factory $WorkspaceUnregisterResultDtoCopyWith(WorkspaceUnregisterResultDto value, $Res Function(WorkspaceUnregisterResultDto) _then) = _$WorkspaceUnregisterResultDtoCopyWithImpl; @useResult $Res call({ - String agentId + bool unregistered }); @@ -4390,27 +7355,27 @@ $Res call({ } /// @nodoc -class _$AgentIdParamsDtoCopyWithImpl<$Res> - implements $AgentIdParamsDtoCopyWith<$Res> { - _$AgentIdParamsDtoCopyWithImpl(this._self, this._then); +class _$WorkspaceUnregisterResultDtoCopyWithImpl<$Res> + implements $WorkspaceUnregisterResultDtoCopyWith<$Res> { + _$WorkspaceUnregisterResultDtoCopyWithImpl(this._self, this._then); - final AgentIdParamsDto _self; - final $Res Function(AgentIdParamsDto) _then; + final WorkspaceUnregisterResultDto _self; + final $Res Function(WorkspaceUnregisterResultDto) _then; -/// Create a copy of AgentIdParamsDto +/// Create a copy of WorkspaceUnregisterResultDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? unregistered = null,}) { return _then(_self.copyWith( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String, +unregistered: null == unregistered ? _self.unregistered : unregistered // ignore: cast_nullable_to_non_nullable +as bool, )); } } -/// Adds pattern-matching-related methods to [AgentIdParamsDto]. -extension AgentIdParamsDtoPatterns on AgentIdParamsDto { +/// Adds pattern-matching-related methods to [WorkspaceUnregisterResultDto]. +extension WorkspaceUnregisterResultDtoPatterns on WorkspaceUnregisterResultDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -4423,10 +7388,10 @@ extension AgentIdParamsDtoPatterns on AgentIdParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _AgentIdParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceUnregisterResultDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _AgentIdParamsDto() when $default != null: +case _WorkspaceUnregisterResultDto() when $default != null: return $default(_that);case _: return orElse(); @@ -4445,10 +7410,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _AgentIdParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorkspaceUnregisterResultDto value) $default,){ final _that = this; switch (_that) { -case _AgentIdParamsDto(): +case _WorkspaceUnregisterResultDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -4466,10 +7431,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _AgentIdParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceUnregisterResultDto value)? $default,){ final _that = this; switch (_that) { -case _AgentIdParamsDto() when $default != null: +case _WorkspaceUnregisterResultDto() when $default != null: return $default(_that);case _: return null; @@ -4487,10 +7452,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( bool unregistered)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _AgentIdParamsDto() when $default != null: -return $default(_that.agentId);case _: +case _WorkspaceUnregisterResultDto() when $default != null: +return $default(_that.unregistered);case _: return orElse(); } @@ -4508,10 +7473,10 @@ return $default(_that.agentId);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String agentId) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( bool unregistered) $default,) {final _that = this; switch (_that) { -case _AgentIdParamsDto(): -return $default(_that.agentId);case _: +case _WorkspaceUnregisterResultDto(): +return $default(_that.unregistered);case _: throw StateError('Unexpected subclass'); } @@ -4528,10 +7493,10 @@ return $default(_that.agentId);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool unregistered)? $default,) {final _that = this; switch (_that) { -case _AgentIdParamsDto() when $default != null: -return $default(_that.agentId);case _: +case _WorkspaceUnregisterResultDto() when $default != null: +return $default(_that.unregistered);case _: return null; } @@ -4542,46 +7507,46 @@ return $default(_that.agentId);case _: /// @nodoc @JsonSerializable() -class _AgentIdParamsDto implements AgentIdParamsDto { - const _AgentIdParamsDto({required this.agentId}); - factory _AgentIdParamsDto.fromJson(Map json) => _$AgentIdParamsDtoFromJson(json); +class _WorkspaceUnregisterResultDto implements WorkspaceUnregisterResultDto { + const _WorkspaceUnregisterResultDto({required this.unregistered}); + factory _WorkspaceUnregisterResultDto.fromJson(Map json) => _$WorkspaceUnregisterResultDtoFromJson(json); -@override final String agentId; +@override final bool unregistered; -/// Create a copy of AgentIdParamsDto +/// Create a copy of WorkspaceUnregisterResultDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$AgentIdParamsDtoCopyWith<_AgentIdParamsDto> get copyWith => __$AgentIdParamsDtoCopyWithImpl<_AgentIdParamsDto>(this, _$identity); +_$WorkspaceUnregisterResultDtoCopyWith<_WorkspaceUnregisterResultDto> get copyWith => __$WorkspaceUnregisterResultDtoCopyWithImpl<_WorkspaceUnregisterResultDto>(this, _$identity); @override Map toJson() { - return _$AgentIdParamsDtoToJson(this, ); + return _$WorkspaceUnregisterResultDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _AgentIdParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceUnregisterResultDto&&(identical(other.unregistered, unregistered) || other.unregistered == unregistered)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId); +int get hashCode => Object.hash(runtimeType,unregistered); @override String toString() { - return 'AgentIdParamsDto(agentId: $agentId)'; + return 'WorkspaceUnregisterResultDto(unregistered: $unregistered)'; } } /// @nodoc -abstract mixin class _$AgentIdParamsDtoCopyWith<$Res> implements $AgentIdParamsDtoCopyWith<$Res> { - factory _$AgentIdParamsDtoCopyWith(_AgentIdParamsDto value, $Res Function(_AgentIdParamsDto) _then) = __$AgentIdParamsDtoCopyWithImpl; +abstract mixin class _$WorkspaceUnregisterResultDtoCopyWith<$Res> implements $WorkspaceUnregisterResultDtoCopyWith<$Res> { + factory _$WorkspaceUnregisterResultDtoCopyWith(_WorkspaceUnregisterResultDto value, $Res Function(_WorkspaceUnregisterResultDto) _then) = __$WorkspaceUnregisterResultDtoCopyWithImpl; @override @useResult $Res call({ - String agentId + bool unregistered }); @@ -4589,19 +7554,19 @@ $Res call({ } /// @nodoc -class __$AgentIdParamsDtoCopyWithImpl<$Res> - implements _$AgentIdParamsDtoCopyWith<$Res> { - __$AgentIdParamsDtoCopyWithImpl(this._self, this._then); +class __$WorkspaceUnregisterResultDtoCopyWithImpl<$Res> + implements _$WorkspaceUnregisterResultDtoCopyWith<$Res> { + __$WorkspaceUnregisterResultDtoCopyWithImpl(this._self, this._then); - final _AgentIdParamsDto _self; - final $Res Function(_AgentIdParamsDto) _then; + final _WorkspaceUnregisterResultDto _self; + final $Res Function(_WorkspaceUnregisterResultDto) _then; -/// Create a copy of AgentIdParamsDto +/// Create a copy of WorkspaceUnregisterResultDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,}) { - return _then(_AgentIdParamsDto( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String, +@override @pragma('vm:prefer-inline') $Res call({Object? unregistered = null,}) { + return _then(_WorkspaceUnregisterResultDto( +unregistered: null == unregistered ? _self.unregistered : unregistered // ignore: cast_nullable_to_non_nullable +as bool, )); } @@ -4610,42 +7575,42 @@ as String, /// @nodoc -mixin _$ApprovalResolveParamsDto { +mixin _$DirectorySuggestResultDto { - String get approvalId; bool get approved; -/// Create a copy of ApprovalResolveParamsDto + List get suggestions; +/// Create a copy of DirectorySuggestResultDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$ApprovalResolveParamsDtoCopyWith get copyWith => _$ApprovalResolveParamsDtoCopyWithImpl(this as ApprovalResolveParamsDto, _$identity); +$DirectorySuggestResultDtoCopyWith get copyWith => _$DirectorySuggestResultDtoCopyWithImpl(this as DirectorySuggestResultDto, _$identity); - /// Serializes this ApprovalResolveParamsDto to a JSON map. + /// Serializes this DirectorySuggestResultDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is ApprovalResolveParamsDto&&(identical(other.approvalId, approvalId) || other.approvalId == approvalId)&&(identical(other.approved, approved) || other.approved == approved)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is DirectorySuggestResultDto&&const DeepCollectionEquality().equals(other.suggestions, suggestions)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,approvalId,approved); +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(suggestions)); @override String toString() { - return 'ApprovalResolveParamsDto(approvalId: $approvalId, approved: $approved)'; + return 'DirectorySuggestResultDto(suggestions: $suggestions)'; } } /// @nodoc -abstract mixin class $ApprovalResolveParamsDtoCopyWith<$Res> { - factory $ApprovalResolveParamsDtoCopyWith(ApprovalResolveParamsDto value, $Res Function(ApprovalResolveParamsDto) _then) = _$ApprovalResolveParamsDtoCopyWithImpl; +abstract mixin class $DirectorySuggestResultDtoCopyWith<$Res> { + factory $DirectorySuggestResultDtoCopyWith(DirectorySuggestResultDto value, $Res Function(DirectorySuggestResultDto) _then) = _$DirectorySuggestResultDtoCopyWithImpl; @useResult $Res call({ - String approvalId, bool approved + List suggestions }); @@ -4653,28 +7618,27 @@ $Res call({ } /// @nodoc -class _$ApprovalResolveParamsDtoCopyWithImpl<$Res> - implements $ApprovalResolveParamsDtoCopyWith<$Res> { - _$ApprovalResolveParamsDtoCopyWithImpl(this._self, this._then); +class _$DirectorySuggestResultDtoCopyWithImpl<$Res> + implements $DirectorySuggestResultDtoCopyWith<$Res> { + _$DirectorySuggestResultDtoCopyWithImpl(this._self, this._then); - final ApprovalResolveParamsDto _self; - final $Res Function(ApprovalResolveParamsDto) _then; + final DirectorySuggestResultDto _self; + final $Res Function(DirectorySuggestResultDto) _then; -/// Create a copy of ApprovalResolveParamsDto +/// Create a copy of DirectorySuggestResultDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? approvalId = null,Object? approved = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? suggestions = null,}) { return _then(_self.copyWith( -approvalId: null == approvalId ? _self.approvalId : approvalId // ignore: cast_nullable_to_non_nullable -as String,approved: null == approved ? _self.approved : approved // ignore: cast_nullable_to_non_nullable -as bool, +suggestions: null == suggestions ? _self.suggestions : suggestions // ignore: cast_nullable_to_non_nullable +as List, )); } } -/// Adds pattern-matching-related methods to [ApprovalResolveParamsDto]. -extension ApprovalResolveParamsDtoPatterns on ApprovalResolveParamsDto { +/// Adds pattern-matching-related methods to [DirectorySuggestResultDto]. +extension DirectorySuggestResultDtoPatterns on DirectorySuggestResultDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -4687,10 +7651,10 @@ extension ApprovalResolveParamsDtoPatterns on ApprovalResolveParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _ApprovalResolveParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _DirectorySuggestResultDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _ApprovalResolveParamsDto() when $default != null: +case _DirectorySuggestResultDto() when $default != null: return $default(_that);case _: return orElse(); @@ -4709,10 +7673,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _ApprovalResolveParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _DirectorySuggestResultDto value) $default,){ final _that = this; switch (_that) { -case _ApprovalResolveParamsDto(): +case _DirectorySuggestResultDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -4730,10 +7694,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _ApprovalResolveParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _DirectorySuggestResultDto value)? $default,){ final _that = this; switch (_that) { -case _ApprovalResolveParamsDto() when $default != null: +case _DirectorySuggestResultDto() when $default != null: return $default(_that);case _: return null; @@ -4751,10 +7715,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String approvalId, bool approved)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( List suggestions)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _ApprovalResolveParamsDto() when $default != null: -return $default(_that.approvalId,_that.approved);case _: +case _DirectorySuggestResultDto() when $default != null: +return $default(_that.suggestions);case _: return orElse(); } @@ -4772,10 +7736,10 @@ return $default(_that.approvalId,_that.approved);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String approvalId, bool approved) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( List suggestions) $default,) {final _that = this; switch (_that) { -case _ApprovalResolveParamsDto(): -return $default(_that.approvalId,_that.approved);case _: +case _DirectorySuggestResultDto(): +return $default(_that.suggestions);case _: throw StateError('Unexpected subclass'); } @@ -4792,10 +7756,10 @@ return $default(_that.approvalId,_that.approved);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String approvalId, bool approved)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List suggestions)? $default,) {final _that = this; switch (_that) { -case _ApprovalResolveParamsDto() when $default != null: -return $default(_that.approvalId,_that.approved);case _: +case _DirectorySuggestResultDto() when $default != null: +return $default(_that.suggestions);case _: return null; } @@ -4806,47 +7770,52 @@ return $default(_that.approvalId,_that.approved);case _: /// @nodoc @JsonSerializable() -class _ApprovalResolveParamsDto implements ApprovalResolveParamsDto { - const _ApprovalResolveParamsDto({required this.approvalId, required this.approved}); - factory _ApprovalResolveParamsDto.fromJson(Map json) => _$ApprovalResolveParamsDtoFromJson(json); +class _DirectorySuggestResultDto implements DirectorySuggestResultDto { + const _DirectorySuggestResultDto({required final List suggestions}): _suggestions = suggestions; + factory _DirectorySuggestResultDto.fromJson(Map json) => _$DirectorySuggestResultDtoFromJson(json); -@override final String approvalId; -@override final bool approved; + final List _suggestions; +@override List get suggestions { + if (_suggestions is EqualUnmodifiableListView) return _suggestions; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_suggestions); +} -/// Create a copy of ApprovalResolveParamsDto + +/// Create a copy of DirectorySuggestResultDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$ApprovalResolveParamsDtoCopyWith<_ApprovalResolveParamsDto> get copyWith => __$ApprovalResolveParamsDtoCopyWithImpl<_ApprovalResolveParamsDto>(this, _$identity); +_$DirectorySuggestResultDtoCopyWith<_DirectorySuggestResultDto> get copyWith => __$DirectorySuggestResultDtoCopyWithImpl<_DirectorySuggestResultDto>(this, _$identity); @override Map toJson() { - return _$ApprovalResolveParamsDtoToJson(this, ); + return _$DirectorySuggestResultDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _ApprovalResolveParamsDto&&(identical(other.approvalId, approvalId) || other.approvalId == approvalId)&&(identical(other.approved, approved) || other.approved == approved)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _DirectorySuggestResultDto&&const DeepCollectionEquality().equals(other._suggestions, _suggestions)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,approvalId,approved); +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_suggestions)); @override String toString() { - return 'ApprovalResolveParamsDto(approvalId: $approvalId, approved: $approved)'; + return 'DirectorySuggestResultDto(suggestions: $suggestions)'; } } /// @nodoc -abstract mixin class _$ApprovalResolveParamsDtoCopyWith<$Res> implements $ApprovalResolveParamsDtoCopyWith<$Res> { - factory _$ApprovalResolveParamsDtoCopyWith(_ApprovalResolveParamsDto value, $Res Function(_ApprovalResolveParamsDto) _then) = __$ApprovalResolveParamsDtoCopyWithImpl; +abstract mixin class _$DirectorySuggestResultDtoCopyWith<$Res> implements $DirectorySuggestResultDtoCopyWith<$Res> { + factory _$DirectorySuggestResultDtoCopyWith(_DirectorySuggestResultDto value, $Res Function(_DirectorySuggestResultDto) _then) = __$DirectorySuggestResultDtoCopyWithImpl; @override @useResult $Res call({ - String approvalId, bool approved + List suggestions }); @@ -4854,20 +7823,19 @@ $Res call({ } /// @nodoc -class __$ApprovalResolveParamsDtoCopyWithImpl<$Res> - implements _$ApprovalResolveParamsDtoCopyWith<$Res> { - __$ApprovalResolveParamsDtoCopyWithImpl(this._self, this._then); +class __$DirectorySuggestResultDtoCopyWithImpl<$Res> + implements _$DirectorySuggestResultDtoCopyWith<$Res> { + __$DirectorySuggestResultDtoCopyWithImpl(this._self, this._then); - final _ApprovalResolveParamsDto _self; - final $Res Function(_ApprovalResolveParamsDto) _then; + final _DirectorySuggestResultDto _self; + final $Res Function(_DirectorySuggestResultDto) _then; -/// Create a copy of ApprovalResolveParamsDto +/// Create a copy of DirectorySuggestResultDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? approvalId = null,Object? approved = null,}) { - return _then(_ApprovalResolveParamsDto( -approvalId: null == approvalId ? _self.approvalId : approvalId // ignore: cast_nullable_to_non_nullable -as String,approved: null == approved ? _self.approved : approved // ignore: cast_nullable_to_non_nullable -as bool, +@override @pragma('vm:prefer-inline') $Res call({Object? suggestions = null,}) { + return _then(_DirectorySuggestResultDto( +suggestions: null == suggestions ? _self._suggestions : suggestions // ignore: cast_nullable_to_non_nullable +as List, )); } @@ -4876,42 +7844,42 @@ as bool, /// @nodoc -mixin _$TimelineSubscribeParamsDto { +mixin _$GitBranchesListResultDto { - String get agentId; int get afterSequence; -/// Create a copy of TimelineSubscribeParamsDto + List get branches; +/// Create a copy of GitBranchesListResultDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$TimelineSubscribeParamsDtoCopyWith get copyWith => _$TimelineSubscribeParamsDtoCopyWithImpl(this as TimelineSubscribeParamsDto, _$identity); +$GitBranchesListResultDtoCopyWith get copyWith => _$GitBranchesListResultDtoCopyWithImpl(this as GitBranchesListResultDto, _$identity); - /// Serializes this TimelineSubscribeParamsDto to a JSON map. + /// Serializes this GitBranchesListResultDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is TimelineSubscribeParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.afterSequence, afterSequence) || other.afterSequence == afterSequence)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is GitBranchesListResultDto&&const DeepCollectionEquality().equals(other.branches, branches)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId,afterSequence); +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(branches)); @override String toString() { - return 'TimelineSubscribeParamsDto(agentId: $agentId, afterSequence: $afterSequence)'; + return 'GitBranchesListResultDto(branches: $branches)'; } } /// @nodoc -abstract mixin class $TimelineSubscribeParamsDtoCopyWith<$Res> { - factory $TimelineSubscribeParamsDtoCopyWith(TimelineSubscribeParamsDto value, $Res Function(TimelineSubscribeParamsDto) _then) = _$TimelineSubscribeParamsDtoCopyWithImpl; +abstract mixin class $GitBranchesListResultDtoCopyWith<$Res> { + factory $GitBranchesListResultDtoCopyWith(GitBranchesListResultDto value, $Res Function(GitBranchesListResultDto) _then) = _$GitBranchesListResultDtoCopyWithImpl; @useResult $Res call({ - String agentId, int afterSequence + List branches }); @@ -4919,28 +7887,27 @@ $Res call({ } /// @nodoc -class _$TimelineSubscribeParamsDtoCopyWithImpl<$Res> - implements $TimelineSubscribeParamsDtoCopyWith<$Res> { - _$TimelineSubscribeParamsDtoCopyWithImpl(this._self, this._then); +class _$GitBranchesListResultDtoCopyWithImpl<$Res> + implements $GitBranchesListResultDtoCopyWith<$Res> { + _$GitBranchesListResultDtoCopyWithImpl(this._self, this._then); - final TimelineSubscribeParamsDto _self; - final $Res Function(TimelineSubscribeParamsDto) _then; + final GitBranchesListResultDto _self; + final $Res Function(GitBranchesListResultDto) _then; -/// Create a copy of TimelineSubscribeParamsDto +/// Create a copy of GitBranchesListResultDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? agentId = null,Object? afterSequence = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? branches = null,}) { return _then(_self.copyWith( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String,afterSequence: null == afterSequence ? _self.afterSequence : afterSequence // ignore: cast_nullable_to_non_nullable -as int, +branches: null == branches ? _self.branches : branches // ignore: cast_nullable_to_non_nullable +as List, )); } } -/// Adds pattern-matching-related methods to [TimelineSubscribeParamsDto]. -extension TimelineSubscribeParamsDtoPatterns on TimelineSubscribeParamsDto { +/// Adds pattern-matching-related methods to [GitBranchesListResultDto]. +extension GitBranchesListResultDtoPatterns on GitBranchesListResultDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -4953,10 +7920,10 @@ extension TimelineSubscribeParamsDtoPatterns on TimelineSubscribeParamsDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _TimelineSubscribeParamsDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _GitBranchesListResultDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _TimelineSubscribeParamsDto() when $default != null: +case _GitBranchesListResultDto() when $default != null: return $default(_that);case _: return orElse(); @@ -4975,10 +7942,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _TimelineSubscribeParamsDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _GitBranchesListResultDto value) $default,){ final _that = this; switch (_that) { -case _TimelineSubscribeParamsDto(): +case _GitBranchesListResultDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -4996,10 +7963,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _TimelineSubscribeParamsDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _GitBranchesListResultDto value)? $default,){ final _that = this; switch (_that) { -case _TimelineSubscribeParamsDto() when $default != null: +case _GitBranchesListResultDto() when $default != null: return $default(_that);case _: return null; @@ -5017,10 +7984,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String agentId, int afterSequence)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( List branches)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _TimelineSubscribeParamsDto() when $default != null: -return $default(_that.agentId,_that.afterSequence);case _: +case _GitBranchesListResultDto() when $default != null: +return $default(_that.branches);case _: return orElse(); } @@ -5038,10 +8005,10 @@ return $default(_that.agentId,_that.afterSequence);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String agentId, int afterSequence) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( List branches) $default,) {final _that = this; switch (_that) { -case _TimelineSubscribeParamsDto(): -return $default(_that.agentId,_that.afterSequence);case _: +case _GitBranchesListResultDto(): +return $default(_that.branches);case _: throw StateError('Unexpected subclass'); } @@ -5058,10 +8025,10 @@ return $default(_that.agentId,_that.afterSequence);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String agentId, int afterSequence)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List branches)? $default,) {final _that = this; switch (_that) { -case _TimelineSubscribeParamsDto() when $default != null: -return $default(_that.agentId,_that.afterSequence);case _: +case _GitBranchesListResultDto() when $default != null: +return $default(_that.branches);case _: return null; } @@ -5072,47 +8039,52 @@ return $default(_that.agentId,_that.afterSequence);case _: /// @nodoc @JsonSerializable() -class _TimelineSubscribeParamsDto implements TimelineSubscribeParamsDto { - const _TimelineSubscribeParamsDto({required this.agentId, required this.afterSequence}); - factory _TimelineSubscribeParamsDto.fromJson(Map json) => _$TimelineSubscribeParamsDtoFromJson(json); +class _GitBranchesListResultDto implements GitBranchesListResultDto { + const _GitBranchesListResultDto({required final List branches}): _branches = branches; + factory _GitBranchesListResultDto.fromJson(Map json) => _$GitBranchesListResultDtoFromJson(json); -@override final String agentId; -@override final int afterSequence; + final List _branches; +@override List get branches { + if (_branches is EqualUnmodifiableListView) return _branches; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_branches); +} -/// Create a copy of TimelineSubscribeParamsDto + +/// Create a copy of GitBranchesListResultDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$TimelineSubscribeParamsDtoCopyWith<_TimelineSubscribeParamsDto> get copyWith => __$TimelineSubscribeParamsDtoCopyWithImpl<_TimelineSubscribeParamsDto>(this, _$identity); +_$GitBranchesListResultDtoCopyWith<_GitBranchesListResultDto> get copyWith => __$GitBranchesListResultDtoCopyWithImpl<_GitBranchesListResultDto>(this, _$identity); @override Map toJson() { - return _$TimelineSubscribeParamsDtoToJson(this, ); + return _$GitBranchesListResultDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _TimelineSubscribeParamsDto&&(identical(other.agentId, agentId) || other.agentId == agentId)&&(identical(other.afterSequence, afterSequence) || other.afterSequence == afterSequence)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _GitBranchesListResultDto&&const DeepCollectionEquality().equals(other._branches, _branches)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,agentId,afterSequence); +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_branches)); @override String toString() { - return 'TimelineSubscribeParamsDto(agentId: $agentId, afterSequence: $afterSequence)'; + return 'GitBranchesListResultDto(branches: $branches)'; } } /// @nodoc -abstract mixin class _$TimelineSubscribeParamsDtoCopyWith<$Res> implements $TimelineSubscribeParamsDtoCopyWith<$Res> { - factory _$TimelineSubscribeParamsDtoCopyWith(_TimelineSubscribeParamsDto value, $Res Function(_TimelineSubscribeParamsDto) _then) = __$TimelineSubscribeParamsDtoCopyWithImpl; +abstract mixin class _$GitBranchesListResultDtoCopyWith<$Res> implements $GitBranchesListResultDtoCopyWith<$Res> { + factory _$GitBranchesListResultDtoCopyWith(_GitBranchesListResultDto value, $Res Function(_GitBranchesListResultDto) _then) = __$GitBranchesListResultDtoCopyWithImpl; @override @useResult $Res call({ - String agentId, int afterSequence + List branches }); @@ -5120,20 +8092,19 @@ $Res call({ } /// @nodoc -class __$TimelineSubscribeParamsDtoCopyWithImpl<$Res> - implements _$TimelineSubscribeParamsDtoCopyWith<$Res> { - __$TimelineSubscribeParamsDtoCopyWithImpl(this._self, this._then); +class __$GitBranchesListResultDtoCopyWithImpl<$Res> + implements _$GitBranchesListResultDtoCopyWith<$Res> { + __$GitBranchesListResultDtoCopyWithImpl(this._self, this._then); - final _TimelineSubscribeParamsDto _self; - final $Res Function(_TimelineSubscribeParamsDto) _then; + final _GitBranchesListResultDto _self; + final $Res Function(_GitBranchesListResultDto) _then; -/// Create a copy of TimelineSubscribeParamsDto +/// Create a copy of GitBranchesListResultDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? agentId = null,Object? afterSequence = null,}) { - return _then(_TimelineSubscribeParamsDto( -agentId: null == agentId ? _self.agentId : agentId // ignore: cast_nullable_to_non_nullable -as String,afterSequence: null == afterSequence ? _self.afterSequence : afterSequence // ignore: cast_nullable_to_non_nullable -as int, +@override @pragma('vm:prefer-inline') $Res call({Object? branches = null,}) { + return _then(_GitBranchesListResultDto( +branches: null == branches ? _self._branches : branches // ignore: cast_nullable_to_non_nullable +as List, )); } @@ -5142,70 +8113,79 @@ as int, /// @nodoc -mixin _$WorkspaceListResultDto { +mixin _$WorktreeResultDto { - List get workspaces; -/// Create a copy of WorkspaceListResultDto + WorktreeDto get worktree; +/// Create a copy of WorktreeResultDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$WorkspaceListResultDtoCopyWith get copyWith => _$WorkspaceListResultDtoCopyWithImpl(this as WorkspaceListResultDto, _$identity); +$WorktreeResultDtoCopyWith get copyWith => _$WorktreeResultDtoCopyWithImpl(this as WorktreeResultDto, _$identity); - /// Serializes this WorkspaceListResultDto to a JSON map. + /// Serializes this WorktreeResultDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceListResultDto&&const DeepCollectionEquality().equals(other.workspaces, workspaces)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorktreeResultDto&&(identical(other.worktree, worktree) || other.worktree == worktree)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(workspaces)); +int get hashCode => Object.hash(runtimeType,worktree); @override String toString() { - return 'WorkspaceListResultDto(workspaces: $workspaces)'; + return 'WorktreeResultDto(worktree: $worktree)'; } } /// @nodoc -abstract mixin class $WorkspaceListResultDtoCopyWith<$Res> { - factory $WorkspaceListResultDtoCopyWith(WorkspaceListResultDto value, $Res Function(WorkspaceListResultDto) _then) = _$WorkspaceListResultDtoCopyWithImpl; +abstract mixin class $WorktreeResultDtoCopyWith<$Res> { + factory $WorktreeResultDtoCopyWith(WorktreeResultDto value, $Res Function(WorktreeResultDto) _then) = _$WorktreeResultDtoCopyWithImpl; @useResult $Res call({ - List workspaces + WorktreeDto worktree }); - +$WorktreeDtoCopyWith<$Res> get worktree; } /// @nodoc -class _$WorkspaceListResultDtoCopyWithImpl<$Res> - implements $WorkspaceListResultDtoCopyWith<$Res> { - _$WorkspaceListResultDtoCopyWithImpl(this._self, this._then); +class _$WorktreeResultDtoCopyWithImpl<$Res> + implements $WorktreeResultDtoCopyWith<$Res> { + _$WorktreeResultDtoCopyWithImpl(this._self, this._then); - final WorkspaceListResultDto _self; - final $Res Function(WorkspaceListResultDto) _then; + final WorktreeResultDto _self; + final $Res Function(WorktreeResultDto) _then; -/// Create a copy of WorkspaceListResultDto +/// Create a copy of WorktreeResultDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? workspaces = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? worktree = null,}) { return _then(_self.copyWith( -workspaces: null == workspaces ? _self.workspaces : workspaces // ignore: cast_nullable_to_non_nullable -as List, +worktree: null == worktree ? _self.worktree : worktree // ignore: cast_nullable_to_non_nullable +as WorktreeDto, )); } - +/// Create a copy of WorktreeResultDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$WorktreeDtoCopyWith<$Res> get worktree { + + return $WorktreeDtoCopyWith<$Res>(_self.worktree, (value) { + return _then(_self.copyWith(worktree: value)); + }); +} } -/// Adds pattern-matching-related methods to [WorkspaceListResultDto]. -extension WorkspaceListResultDtoPatterns on WorkspaceListResultDto { +/// Adds pattern-matching-related methods to [WorktreeResultDto]. +extension WorktreeResultDtoPatterns on WorktreeResultDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -5218,10 +8198,10 @@ extension WorkspaceListResultDtoPatterns on WorkspaceListResultDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceListResultDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorktreeResultDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _WorkspaceListResultDto() when $default != null: +case _WorktreeResultDto() when $default != null: return $default(_that);case _: return orElse(); @@ -5240,10 +8220,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _WorkspaceListResultDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorktreeResultDto value) $default,){ final _that = this; switch (_that) { -case _WorkspaceListResultDto(): +case _WorktreeResultDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -5261,10 +8241,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceListResultDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorktreeResultDto value)? $default,){ final _that = this; switch (_that) { -case _WorkspaceListResultDto() when $default != null: +case _WorktreeResultDto() when $default != null: return $default(_that);case _: return null; @@ -5282,10 +8262,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( List workspaces)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( WorktreeDto worktree)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _WorkspaceListResultDto() when $default != null: -return $default(_that.workspaces);case _: +case _WorktreeResultDto() when $default != null: +return $default(_that.worktree);case _: return orElse(); } @@ -5303,10 +8283,10 @@ return $default(_that.workspaces);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( List workspaces) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( WorktreeDto worktree) $default,) {final _that = this; switch (_that) { -case _WorkspaceListResultDto(): -return $default(_that.workspaces);case _: +case _WorktreeResultDto(): +return $default(_that.worktree);case _: throw StateError('Unexpected subclass'); } @@ -5323,10 +8303,10 @@ return $default(_that.workspaces);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( List workspaces)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( WorktreeDto worktree)? $default,) {final _that = this; switch (_that) { -case _WorkspaceListResultDto() when $default != null: -return $default(_that.workspaces);case _: +case _WorktreeResultDto() when $default != null: +return $default(_that.worktree);case _: return null; } @@ -5337,153 +8317,156 @@ return $default(_that.workspaces);case _: /// @nodoc @JsonSerializable() -class _WorkspaceListResultDto implements WorkspaceListResultDto { - const _WorkspaceListResultDto({required final List workspaces}): _workspaces = workspaces; - factory _WorkspaceListResultDto.fromJson(Map json) => _$WorkspaceListResultDtoFromJson(json); - - final List _workspaces; -@override List get workspaces { - if (_workspaces is EqualUnmodifiableListView) return _workspaces; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_workspaces); -} +class _WorktreeResultDto implements WorktreeResultDto { + const _WorktreeResultDto({required this.worktree}); + factory _WorktreeResultDto.fromJson(Map json) => _$WorktreeResultDtoFromJson(json); +@override final WorktreeDto worktree; -/// Create a copy of WorkspaceListResultDto +/// Create a copy of WorktreeResultDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$WorkspaceListResultDtoCopyWith<_WorkspaceListResultDto> get copyWith => __$WorkspaceListResultDtoCopyWithImpl<_WorkspaceListResultDto>(this, _$identity); +_$WorktreeResultDtoCopyWith<_WorktreeResultDto> get copyWith => __$WorktreeResultDtoCopyWithImpl<_WorktreeResultDto>(this, _$identity); @override Map toJson() { - return _$WorkspaceListResultDtoToJson(this, ); + return _$WorktreeResultDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceListResultDto&&const DeepCollectionEquality().equals(other._workspaces, _workspaces)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorktreeResultDto&&(identical(other.worktree, worktree) || other.worktree == worktree)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_workspaces)); +int get hashCode => Object.hash(runtimeType,worktree); @override String toString() { - return 'WorkspaceListResultDto(workspaces: $workspaces)'; + return 'WorktreeResultDto(worktree: $worktree)'; } } /// @nodoc -abstract mixin class _$WorkspaceListResultDtoCopyWith<$Res> implements $WorkspaceListResultDtoCopyWith<$Res> { - factory _$WorkspaceListResultDtoCopyWith(_WorkspaceListResultDto value, $Res Function(_WorkspaceListResultDto) _then) = __$WorkspaceListResultDtoCopyWithImpl; +abstract mixin class _$WorktreeResultDtoCopyWith<$Res> implements $WorktreeResultDtoCopyWith<$Res> { + factory _$WorktreeResultDtoCopyWith(_WorktreeResultDto value, $Res Function(_WorktreeResultDto) _then) = __$WorktreeResultDtoCopyWithImpl; @override @useResult $Res call({ - List workspaces + WorktreeDto worktree }); - +@override $WorktreeDtoCopyWith<$Res> get worktree; } /// @nodoc -class __$WorkspaceListResultDtoCopyWithImpl<$Res> - implements _$WorkspaceListResultDtoCopyWith<$Res> { - __$WorkspaceListResultDtoCopyWithImpl(this._self, this._then); +class __$WorktreeResultDtoCopyWithImpl<$Res> + implements _$WorktreeResultDtoCopyWith<$Res> { + __$WorktreeResultDtoCopyWithImpl(this._self, this._then); - final _WorkspaceListResultDto _self; - final $Res Function(_WorkspaceListResultDto) _then; + final _WorktreeResultDto _self; + final $Res Function(_WorktreeResultDto) _then; -/// Create a copy of WorkspaceListResultDto +/// Create a copy of WorktreeResultDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? workspaces = null,}) { - return _then(_WorkspaceListResultDto( -workspaces: null == workspaces ? _self._workspaces : workspaces // ignore: cast_nullable_to_non_nullable -as List, +@override @pragma('vm:prefer-inline') $Res call({Object? worktree = null,}) { + return _then(_WorktreeResultDto( +worktree: null == worktree ? _self.worktree : worktree // ignore: cast_nullable_to_non_nullable +as WorktreeDto, )); } - +/// Create a copy of WorktreeResultDto +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$WorktreeDtoCopyWith<$Res> get worktree { + + return $WorktreeDtoCopyWith<$Res>(_self.worktree, (value) { + return _then(_self.copyWith(worktree: value)); + }); +} } /// @nodoc -mixin _$WorkspaceResultDto { +mixin _$WorktreeArchivePreviewResultDto { - WorkspaceDto get workspace; -/// Create a copy of WorkspaceResultDto + WorktreeArchivePreviewDto get preview; +/// Create a copy of WorktreeArchivePreviewResultDto /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -$WorkspaceResultDtoCopyWith get copyWith => _$WorkspaceResultDtoCopyWithImpl(this as WorkspaceResultDto, _$identity); +$WorktreeArchivePreviewResultDtoCopyWith get copyWith => _$WorktreeArchivePreviewResultDtoCopyWithImpl(this as WorktreeArchivePreviewResultDto, _$identity); - /// Serializes this WorkspaceResultDto to a JSON map. + /// Serializes this WorktreeArchivePreviewResultDto to a JSON map. Map toJson(); @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is WorkspaceResultDto&&(identical(other.workspace, workspace) || other.workspace == workspace)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is WorktreeArchivePreviewResultDto&&(identical(other.preview, preview) || other.preview == preview)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,workspace); +int get hashCode => Object.hash(runtimeType,preview); @override String toString() { - return 'WorkspaceResultDto(workspace: $workspace)'; + return 'WorktreeArchivePreviewResultDto(preview: $preview)'; } } /// @nodoc -abstract mixin class $WorkspaceResultDtoCopyWith<$Res> { - factory $WorkspaceResultDtoCopyWith(WorkspaceResultDto value, $Res Function(WorkspaceResultDto) _then) = _$WorkspaceResultDtoCopyWithImpl; +abstract mixin class $WorktreeArchivePreviewResultDtoCopyWith<$Res> { + factory $WorktreeArchivePreviewResultDtoCopyWith(WorktreeArchivePreviewResultDto value, $Res Function(WorktreeArchivePreviewResultDto) _then) = _$WorktreeArchivePreviewResultDtoCopyWithImpl; @useResult $Res call({ - WorkspaceDto workspace + WorktreeArchivePreviewDto preview }); -$WorkspaceDtoCopyWith<$Res> get workspace; +$WorktreeArchivePreviewDtoCopyWith<$Res> get preview; } /// @nodoc -class _$WorkspaceResultDtoCopyWithImpl<$Res> - implements $WorkspaceResultDtoCopyWith<$Res> { - _$WorkspaceResultDtoCopyWithImpl(this._self, this._then); +class _$WorktreeArchivePreviewResultDtoCopyWithImpl<$Res> + implements $WorktreeArchivePreviewResultDtoCopyWith<$Res> { + _$WorktreeArchivePreviewResultDtoCopyWithImpl(this._self, this._then); - final WorkspaceResultDto _self; - final $Res Function(WorkspaceResultDto) _then; + final WorktreeArchivePreviewResultDto _self; + final $Res Function(WorktreeArchivePreviewResultDto) _then; -/// Create a copy of WorkspaceResultDto +/// Create a copy of WorktreeArchivePreviewResultDto /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? workspace = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? preview = null,}) { return _then(_self.copyWith( -workspace: null == workspace ? _self.workspace : workspace // ignore: cast_nullable_to_non_nullable -as WorkspaceDto, +preview: null == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable +as WorktreeArchivePreviewDto, )); } -/// Create a copy of WorkspaceResultDto +/// Create a copy of WorktreeArchivePreviewResultDto /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$WorkspaceDtoCopyWith<$Res> get workspace { +$WorktreeArchivePreviewDtoCopyWith<$Res> get preview { - return $WorkspaceDtoCopyWith<$Res>(_self.workspace, (value) { - return _then(_self.copyWith(workspace: value)); + return $WorktreeArchivePreviewDtoCopyWith<$Res>(_self.preview, (value) { + return _then(_self.copyWith(preview: value)); }); } } -/// Adds pattern-matching-related methods to [WorkspaceResultDto]. -extension WorkspaceResultDtoPatterns on WorkspaceResultDto { +/// Adds pattern-matching-related methods to [WorktreeArchivePreviewResultDto]. +extension WorktreeArchivePreviewResultDtoPatterns on WorktreeArchivePreviewResultDto { /// A variant of `map` that fallback to returning `orElse`. /// /// It is equivalent to doing: @@ -5496,10 +8479,10 @@ extension WorkspaceResultDtoPatterns on WorkspaceResultDto { /// } /// ``` -@optionalTypeArgs TResult maybeMap(TResult Function( _WorkspaceResultDto value)? $default,{required TResult orElse(),}){ +@optionalTypeArgs TResult maybeMap(TResult Function( _WorktreeArchivePreviewResultDto value)? $default,{required TResult orElse(),}){ final _that = this; switch (_that) { -case _WorkspaceResultDto() when $default != null: +case _WorktreeArchivePreviewResultDto() when $default != null: return $default(_that);case _: return orElse(); @@ -5518,10 +8501,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult map(TResult Function( _WorkspaceResultDto value) $default,){ +@optionalTypeArgs TResult map(TResult Function( _WorktreeArchivePreviewResultDto value) $default,){ final _that = this; switch (_that) { -case _WorkspaceResultDto(): +case _WorktreeArchivePreviewResultDto(): return $default(_that);case _: throw StateError('Unexpected subclass'); @@ -5539,10 +8522,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorkspaceResultDto value)? $default,){ +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _WorktreeArchivePreviewResultDto value)? $default,){ final _that = this; switch (_that) { -case _WorkspaceResultDto() when $default != null: +case _WorktreeArchivePreviewResultDto() when $default != null: return $default(_that);case _: return null; @@ -5560,10 +8543,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( WorkspaceDto workspace)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( WorktreeArchivePreviewDto preview)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { -case _WorkspaceResultDto() when $default != null: -return $default(_that.workspace);case _: +case _WorktreeArchivePreviewResultDto() when $default != null: +return $default(_that.preview);case _: return orElse(); } @@ -5581,10 +8564,10 @@ return $default(_that.workspace);case _: /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( WorkspaceDto workspace) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( WorktreeArchivePreviewDto preview) $default,) {final _that = this; switch (_that) { -case _WorkspaceResultDto(): -return $default(_that.workspace);case _: +case _WorktreeArchivePreviewResultDto(): +return $default(_that.preview);case _: throw StateError('Unexpected subclass'); } @@ -5601,10 +8584,10 @@ return $default(_that.workspace);case _: /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( WorkspaceDto workspace)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( WorktreeArchivePreviewDto preview)? $default,) {final _that = this; switch (_that) { -case _WorkspaceResultDto() when $default != null: -return $default(_that.workspace);case _: +case _WorktreeArchivePreviewResultDto() when $default != null: +return $default(_that.preview);case _: return null; } @@ -5615,77 +8598,77 @@ return $default(_that.workspace);case _: /// @nodoc @JsonSerializable() -class _WorkspaceResultDto implements WorkspaceResultDto { - const _WorkspaceResultDto({required this.workspace}); - factory _WorkspaceResultDto.fromJson(Map json) => _$WorkspaceResultDtoFromJson(json); +class _WorktreeArchivePreviewResultDto implements WorktreeArchivePreviewResultDto { + const _WorktreeArchivePreviewResultDto({required this.preview}); + factory _WorktreeArchivePreviewResultDto.fromJson(Map json) => _$WorktreeArchivePreviewResultDtoFromJson(json); -@override final WorkspaceDto workspace; +@override final WorktreeArchivePreviewDto preview; -/// Create a copy of WorkspaceResultDto +/// Create a copy of WorktreeArchivePreviewResultDto /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) @pragma('vm:prefer-inline') -_$WorkspaceResultDtoCopyWith<_WorkspaceResultDto> get copyWith => __$WorkspaceResultDtoCopyWithImpl<_WorkspaceResultDto>(this, _$identity); +_$WorktreeArchivePreviewResultDtoCopyWith<_WorktreeArchivePreviewResultDto> get copyWith => __$WorktreeArchivePreviewResultDtoCopyWithImpl<_WorktreeArchivePreviewResultDto>(this, _$identity); @override Map toJson() { - return _$WorkspaceResultDtoToJson(this, ); + return _$WorktreeArchivePreviewResultDtoToJson(this, ); } @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorkspaceResultDto&&(identical(other.workspace, workspace) || other.workspace == workspace)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _WorktreeArchivePreviewResultDto&&(identical(other.preview, preview) || other.preview == preview)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,workspace); +int get hashCode => Object.hash(runtimeType,preview); @override String toString() { - return 'WorkspaceResultDto(workspace: $workspace)'; + return 'WorktreeArchivePreviewResultDto(preview: $preview)'; } } /// @nodoc -abstract mixin class _$WorkspaceResultDtoCopyWith<$Res> implements $WorkspaceResultDtoCopyWith<$Res> { - factory _$WorkspaceResultDtoCopyWith(_WorkspaceResultDto value, $Res Function(_WorkspaceResultDto) _then) = __$WorkspaceResultDtoCopyWithImpl; +abstract mixin class _$WorktreeArchivePreviewResultDtoCopyWith<$Res> implements $WorktreeArchivePreviewResultDtoCopyWith<$Res> { + factory _$WorktreeArchivePreviewResultDtoCopyWith(_WorktreeArchivePreviewResultDto value, $Res Function(_WorktreeArchivePreviewResultDto) _then) = __$WorktreeArchivePreviewResultDtoCopyWithImpl; @override @useResult $Res call({ - WorkspaceDto workspace + WorktreeArchivePreviewDto preview }); -@override $WorkspaceDtoCopyWith<$Res> get workspace; +@override $WorktreeArchivePreviewDtoCopyWith<$Res> get preview; } /// @nodoc -class __$WorkspaceResultDtoCopyWithImpl<$Res> - implements _$WorkspaceResultDtoCopyWith<$Res> { - __$WorkspaceResultDtoCopyWithImpl(this._self, this._then); +class __$WorktreeArchivePreviewResultDtoCopyWithImpl<$Res> + implements _$WorktreeArchivePreviewResultDtoCopyWith<$Res> { + __$WorktreeArchivePreviewResultDtoCopyWithImpl(this._self, this._then); - final _WorkspaceResultDto _self; - final $Res Function(_WorkspaceResultDto) _then; + final _WorktreeArchivePreviewResultDto _self; + final $Res Function(_WorktreeArchivePreviewResultDto) _then; -/// Create a copy of WorkspaceResultDto +/// Create a copy of WorktreeArchivePreviewResultDto /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? workspace = null,}) { - return _then(_WorkspaceResultDto( -workspace: null == workspace ? _self.workspace : workspace // ignore: cast_nullable_to_non_nullable -as WorkspaceDto, +@override @pragma('vm:prefer-inline') $Res call({Object? preview = null,}) { + return _then(_WorktreeArchivePreviewResultDto( +preview: null == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable +as WorktreeArchivePreviewDto, )); } -/// Create a copy of WorkspaceResultDto +/// Create a copy of WorktreeArchivePreviewResultDto /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') -$WorkspaceDtoCopyWith<$Res> get workspace { +$WorktreeArchivePreviewDtoCopyWith<$Res> get preview { - return $WorkspaceDtoCopyWith<$Res>(_self.workspace, (value) { - return _then(_self.copyWith(workspace: value)); + return $WorktreeArchivePreviewDtoCopyWith<$Res>(_self.preview, (value) { + return _then(_self.copyWith(preview: value)); }); } } diff --git a/packages/coder_protocol/lib/src/rpc_models.g.dart b/packages/coder_protocol/lib/src/rpc_models.g.dart index 6746f0c..558df7b 100644 --- a/packages/coder_protocol/lib/src/rpc_models.g.dart +++ b/packages/coder_protocol/lib/src/rpc_models.g.dart @@ -25,7 +25,8 @@ Map _$HelloParamsDtoToJson(_HelloParamsDto instance) => _WorkspaceRegisterParamsDto _$WorkspaceRegisterParamsDtoFromJson( Map json, ) => _WorkspaceRegisterParamsDto( - id: json['id'] as String, + workspaceId: json['workspaceId'] as String, + checkoutId: json['checkoutId'] as String, rootPath: json['rootPath'] as String, name: json['name'] as String, ); @@ -33,22 +34,96 @@ _WorkspaceRegisterParamsDto _$WorkspaceRegisterParamsDtoFromJson( Map _$WorkspaceRegisterParamsDtoToJson( _WorkspaceRegisterParamsDto instance, ) => { - 'id': instance.id, + 'workspaceId': instance.workspaceId, + 'checkoutId': instance.checkoutId, 'rootPath': instance.rootPath, 'name': instance.name, }; +_WorkspaceIdParamsDto _$WorkspaceIdParamsDtoFromJson( + Map json, +) => _WorkspaceIdParamsDto(workspaceId: json['workspaceId'] as String); + +Map _$WorkspaceIdParamsDtoToJson( + _WorkspaceIdParamsDto instance, +) => {'workspaceId': instance.workspaceId}; + +_DirectorySuggestParamsDto _$DirectorySuggestParamsDtoFromJson( + Map json, +) => _DirectorySuggestParamsDto( + query: json['query'] as String, + limit: (json['limit'] as num?)?.toInt() ?? 30, +); + +Map _$DirectorySuggestParamsDtoToJson( + _DirectorySuggestParamsDto instance, +) => {'query': instance.query, 'limit': instance.limit}; + +_GitBranchesListParamsDto _$GitBranchesListParamsDtoFromJson( + Map json, +) => _GitBranchesListParamsDto(workspaceId: json['workspaceId'] as String); + +Map _$GitBranchesListParamsDtoToJson( + _GitBranchesListParamsDto instance, +) => {'workspaceId': instance.workspaceId}; + +_WorktreeCreateParamsDto _$WorktreeCreateParamsDtoFromJson( + Map json, +) => _WorktreeCreateParamsDto( + id: json['id'] as String, + workspaceId: json['workspaceId'] as String, + mode: $enumDecode(_$WorktreeCreateModeEnumMap, json['mode']), + branchName: json['branchName'] as String, + baseBranch: json['baseBranch'] as String?, +); + +Map _$WorktreeCreateParamsDtoToJson( + _WorktreeCreateParamsDto instance, +) => { + 'id': instance.id, + 'workspaceId': instance.workspaceId, + 'mode': _$WorktreeCreateModeEnumMap[instance.mode]!, + 'branchName': instance.branchName, + 'baseBranch': instance.baseBranch, +}; + +const _$WorktreeCreateModeEnumMap = { + WorktreeCreateMode.newBranch: 'newBranch', + WorktreeCreateMode.existingBranch: 'existingBranch', +}; + +_WorktreeIdParamsDto _$WorktreeIdParamsDtoFromJson(Map json) => + _WorktreeIdParamsDto(worktreeId: json['worktreeId'] as String); + +Map _$WorktreeIdParamsDtoToJson( + _WorktreeIdParamsDto instance, +) => {'worktreeId': instance.worktreeId}; + +_WorktreeArchiveParamsDto _$WorktreeArchiveParamsDtoFromJson( + Map json, +) => _WorktreeArchiveParamsDto( + worktreeId: json['worktreeId'] as String, + force: json['force'] as bool, +); + +Map _$WorktreeArchiveParamsDtoToJson( + _WorktreeArchiveParamsDto instance, +) => { + 'worktreeId': instance.worktreeId, + 'force': instance.force, +}; + _AgentListParamsDto _$AgentListParamsDtoFromJson(Map json) => - _AgentListParamsDto(workspaceId: json['workspaceId'] as String?); + _AgentListParamsDto(worktreeId: json['worktreeId'] as String?); Map _$AgentListParamsDtoToJson(_AgentListParamsDto instance) => - {'workspaceId': instance.workspaceId}; + {'worktreeId': instance.worktreeId}; _AgentCreateParamsDto _$AgentCreateParamsDtoFromJson( Map json, ) => _AgentCreateParamsDto( id: json['id'] as String, - workspaceId: json['workspaceId'] as String, + worktreeId: json['worktreeId'] as String, title: json['title'] as String, providerConnectionId: json['providerConnectionId'] as String, model: json['model'] as String, @@ -60,7 +135,7 @@ Map _$AgentCreateParamsDtoToJson( _AgentCreateParamsDto instance, ) => { 'id': instance.id, - 'workspaceId': instance.workspaceId, + 'worktreeId': instance.worktreeId, 'title': instance.title, 'providerConnectionId': instance.providerConnectionId, 'model': instance.model, @@ -278,27 +353,85 @@ Map _$TimelineSubscribeParamsDtoToJson( 'afterSequence': instance.afterSequence, }; -_WorkspaceListResultDto _$WorkspaceListResultDtoFromJson( +_WorkspaceCatalogResultDto _$WorkspaceCatalogResultDtoFromJson( + Map json, +) => _WorkspaceCatalogResultDto( + catalog: WorkspaceCatalogDto.fromJson( + json['catalog'] as Map, + ), +); + +Map _$WorkspaceCatalogResultDtoToJson( + _WorkspaceCatalogResultDto instance, +) => {'catalog': instance.catalog}; + +_WorkspaceRegisterResultDto _$WorkspaceRegisterResultDtoFromJson( Map json, -) => _WorkspaceListResultDto( - workspaces: (json['workspaces'] as List) - .map((e) => WorkspaceDto.fromJson(e as Map)) +) => _WorkspaceRegisterResultDto( + workspace: WorkspaceDto.fromJson(json['workspace'] as Map), + worktrees: (json['worktrees'] as List) + .map((e) => WorktreeDto.fromJson(e as Map)) .toList(), ); -Map _$WorkspaceListResultDtoToJson( - _WorkspaceListResultDto instance, -) => {'workspaces': instance.workspaces}; +Map _$WorkspaceRegisterResultDtoToJson( + _WorkspaceRegisterResultDto instance, +) => { + 'workspace': instance.workspace, + 'worktrees': instance.worktrees, +}; + +_WorkspaceUnregisterResultDto _$WorkspaceUnregisterResultDtoFromJson( + Map json, +) => _WorkspaceUnregisterResultDto(unregistered: json['unregistered'] as bool); + +Map _$WorkspaceUnregisterResultDtoToJson( + _WorkspaceUnregisterResultDto instance, +) => {'unregistered': instance.unregistered}; -_WorkspaceResultDto _$WorkspaceResultDtoFromJson(Map json) => - _WorkspaceResultDto( - workspace: WorkspaceDto.fromJson( - json['workspace'] as Map, - ), +_DirectorySuggestResultDto _$DirectorySuggestResultDtoFromJson( + Map json, +) => _DirectorySuggestResultDto( + suggestions: (json['suggestions'] as List) + .map((e) => DirectorySuggestionDto.fromJson(e as Map)) + .toList(), +); + +Map _$DirectorySuggestResultDtoToJson( + _DirectorySuggestResultDto instance, +) => {'suggestions': instance.suggestions}; + +_GitBranchesListResultDto _$GitBranchesListResultDtoFromJson( + Map json, +) => _GitBranchesListResultDto( + branches: (json['branches'] as List) + .map((e) => GitBranchDto.fromJson(e as Map)) + .toList(), +); + +Map _$GitBranchesListResultDtoToJson( + _GitBranchesListResultDto instance, +) => {'branches': instance.branches}; + +_WorktreeResultDto _$WorktreeResultDtoFromJson(Map json) => + _WorktreeResultDto( + worktree: WorktreeDto.fromJson(json['worktree'] as Map), ); -Map _$WorkspaceResultDtoToJson(_WorkspaceResultDto instance) => - {'workspace': instance.workspace}; +Map _$WorktreeResultDtoToJson(_WorktreeResultDto instance) => + {'worktree': instance.worktree}; + +_WorktreeArchivePreviewResultDto _$WorktreeArchivePreviewResultDtoFromJson( + Map json, +) => _WorktreeArchivePreviewResultDto( + preview: WorktreeArchivePreviewDto.fromJson( + json['preview'] as Map, + ), +); + +Map _$WorktreeArchivePreviewResultDtoToJson( + _WorktreeArchivePreviewResultDto instance, +) => {'preview': instance.preview}; _AgentListResultDto _$AgentListResultDtoFromJson(Map json) => _AgentListResultDto( diff --git a/packages/coder_protocol/test/protocol_test.dart b/packages/coder_protocol/test/protocol_test.dart index 8a81e1b..f2d0c98 100644 --- a/packages/coder_protocol/test/protocol_test.dart +++ b/packages/coder_protocol/test/protocol_test.dart @@ -5,10 +5,96 @@ import 'package:test/test.dart'; void main() { final now = DateTime.utc(2026, 8, 2); + + test('protocol v5 exposes workspace, worktree, and directory RPCs', () { + expect(coderProtocolVersion, 5); + expect(RpcMethod.workspaceCatalog, 'workspace.catalog'); + expect(RpcMethod.workspaceRefresh, 'workspace.refresh'); + expect(RpcMethod.workspaceUnregister, 'workspace.unregister'); + expect(RpcMethod.directorySuggest, 'directory.suggest'); + expect(RpcMethod.gitBranchesList, 'git.branches.list'); + expect(RpcMethod.worktreeCreate, 'worktree.create'); + expect(RpcMethod.worktreeArchivePreview, 'worktree.archive.preview'); + expect(RpcMethod.worktreeArchive, 'worktree.archive'); + }); + + test('workspace and worktree contracts round-trip', () { + final workspace = WorkspaceDto( + id: 'workspace', + name: 'Coder', + rootPath: '/workspace', + kind: WorkspaceKind.git, + createdAt: now, + ); + final worktree = WorktreeDto( + id: 'worktree', + workspaceId: workspace.id, + name: 'feature/settings', + path: '/daemon/worktrees/feature-settings', + kind: WorktreeKind.managed, + branch: 'feature/settings', + head: 'abc123', + isCoderOwned: true, + createdAt: now, + ); + final catalog = WorkspaceCatalogDto( + workspaces: [workspace], + worktrees: [worktree], + ); + + _roundTrip(workspace, (value) => value.toJson(), WorkspaceDto.fromJson); + _roundTrip(worktree, (value) => value.toJson(), WorktreeDto.fromJson); + _roundTrip( + catalog, + (value) => value.toJson(), + WorkspaceCatalogDto.fromJson, + ); + _roundTrip( + const WorktreeArchivePreviewDto( + worktreeId: 'worktree', + dirty: true, + unpushedCommitCount: 2, + runningSessionCount: 0, + removesDirectory: true, + ), + (value) => value.toJson(), + WorktreeArchivePreviewDto.fromJson, + ); + _roundTrip( + const DirectorySuggestionDto( + path: '/workspace', + name: 'workspace', + ), + (value) => value.toJson(), + DirectorySuggestionDto.fromJson, + ); + _roundTrip( + const GitBranchDto( + name: 'main', + current: true, + checkedOut: true, + ), + (value) => value.toJson(), + GitBranchDto.fromJson, + ); + }); + final workspace = WorkspaceDto( id: 'workspace', name: 'Coder', rootPath: '/workspace', + kind: WorkspaceKind.git, + createdAt: now, + ); + final worktree = WorktreeDto( + id: 'worktree', + workspaceId: workspace.id, + name: 'main', + path: workspace.rootPath, + kind: WorktreeKind.checkout, + branch: 'main', + head: 'abc123', + isCoderOwned: false, createdAt: now, ); const capabilities = ModelCapabilitiesDto( @@ -88,7 +174,7 @@ void main() { ); final agent = AgentDto( id: 'agent', - workspaceId: workspace.id, + worktreeId: worktree.id, title: 'Agent', providerConnectionId: connection.id, model: model.id, @@ -131,8 +217,8 @@ void main() { ); test('protocol version and direct JSON-RPC names are stable', () { - expect(coderProtocolVersion, 3); - expect(RpcMethod.workspaceList, 'workspace.list'); + expect(coderProtocolVersion, 5); + expect(RpcMethod.workspaceCatalog, 'workspace.catalog'); expect(RpcMethod.agentCreate, 'agent.create'); expect(RpcMethod.providerCatalog, 'provider.catalog'); expect(RpcMethod.providerAuthStart, 'provider.auth.start'); @@ -231,7 +317,8 @@ void main() { ); _roundTrip( const WorkspaceRegisterParamsDto( - id: 'workspace', + workspaceId: 'workspace', + checkoutId: 'checkout', rootPath: '/workspace', name: 'Workspace', ), @@ -239,14 +326,50 @@ void main() { WorkspaceRegisterParamsDto.fromJson, ); _roundTrip( - const AgentListParamsDto(workspaceId: 'workspace'), + const WorkspaceIdParamsDto(workspaceId: 'workspace'), + (value) => value.toJson(), + WorkspaceIdParamsDto.fromJson, + ); + _roundTrip( + const DirectorySuggestParamsDto(query: '~/Workspaces', limit: 20), + (value) => value.toJson(), + DirectorySuggestParamsDto.fromJson, + ); + _roundTrip( + const GitBranchesListParamsDto(workspaceId: 'workspace'), + (value) => value.toJson(), + GitBranchesListParamsDto.fromJson, + ); + _roundTrip( + const WorktreeCreateParamsDto( + id: 'worktree', + workspaceId: 'workspace', + mode: WorktreeCreateMode.newBranch, + branchName: 'feature/settings', + baseBranch: 'main', + ), + (value) => value.toJson(), + WorktreeCreateParamsDto.fromJson, + ); + _roundTrip( + const WorktreeIdParamsDto(worktreeId: 'worktree'), + (value) => value.toJson(), + WorktreeIdParamsDto.fromJson, + ); + _roundTrip( + const WorktreeArchiveParamsDto(worktreeId: 'worktree', force: true), + (value) => value.toJson(), + WorktreeArchiveParamsDto.fromJson, + ); + _roundTrip( + const AgentListParamsDto(worktreeId: 'worktree'), (value) => value.toJson(), AgentListParamsDto.fromJson, ); _roundTrip( const AgentCreateParamsDto( id: 'agent', - workspaceId: 'workspace', + worktreeId: 'worktree', title: 'Agent', providerConnectionId: 'provider', model: 'model', @@ -376,14 +499,63 @@ void main() { test('all result DTOs round-trip', () { _roundTrip( - WorkspaceListResultDto(workspaces: [workspace]), + WorkspaceCatalogResultDto( + catalog: WorkspaceCatalogDto( + workspaces: [workspace], + worktrees: [worktree], + ), + ), + (value) => value.toJson(), + WorkspaceCatalogResultDto.fromJson, + ); + _roundTrip( + WorkspaceRegisterResultDto( + workspace: workspace, + worktrees: [worktree], + ), + (value) => value.toJson(), + WorkspaceRegisterResultDto.fromJson, + ); + _roundTrip( + const WorkspaceUnregisterResultDto(unregistered: true), + (value) => value.toJson(), + WorkspaceUnregisterResultDto.fromJson, + ); + _roundTrip( + const DirectorySuggestResultDto( + suggestions: [ + DirectorySuggestionDto(path: '/workspace', name: 'workspace'), + ], + ), (value) => value.toJson(), - WorkspaceListResultDto.fromJson, + DirectorySuggestResultDto.fromJson, ); _roundTrip( - WorkspaceResultDto(workspace: workspace), + const GitBranchesListResultDto( + branches: [ + GitBranchDto(name: 'main', current: true, checkedOut: true), + ], + ), + (value) => value.toJson(), + GitBranchesListResultDto.fromJson, + ); + _roundTrip( + WorktreeResultDto(worktree: worktree), + (value) => value.toJson(), + WorktreeResultDto.fromJson, + ); + _roundTrip( + const WorktreeArchivePreviewResultDto( + preview: WorktreeArchivePreviewDto( + worktreeId: 'worktree', + dirty: false, + unpushedCommitCount: 0, + runningSessionCount: 0, + removesDirectory: true, + ), + ), (value) => value.toJson(), - WorkspaceResultDto.fromJson, + WorktreeArchivePreviewResultDto.fromJson, ); _roundTrip( AgentListResultDto(agents: [agent]), @@ -491,6 +663,9 @@ void main() { ...PermissionMode.values, ...ApprovalStatus.values, ...ToolRisk.values, + ...WorkspaceKind.values, + ...WorktreeKind.values, + ...WorktreeCreateMode.values, ...ProviderApiFormat.values, ...ProviderAuthKind.values, ...ProviderAuthFlow.values, diff --git a/pubspec.lock b/pubspec.lock index 4a9795b..746e379 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -943,6 +943,62 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "0634e64bd719f89c012f392938e173521f535d3ecaf66558fa94a056d22b5cc7" + url: "https://pub.dev" + source: hosted + version: "2.4.27" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 287fbaa..ff0c0bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,7 @@ melos: test:golden: run: dart run melos exec --flutter --scope=coder_app -c 1 -- flutter test test/golden --tags=golden --test-randomize-ordering-seed=random test:coverage: - run: dart run melos exec --no-flutter --dir-exists=test -c 1 -- dart test --coverage-path=coverage/lcov.info --coverage-package="\$MELOS_PACKAGE_NAME" --branch-coverage --test-randomize-ordering-seed=random && dart run melos exec --flutter --scope=coder_app -c 1 -- flutter test --coverage --branch-coverage --test-randomize-ordering-seed=random && dart run tool/verify_coverage.dart + run: dart run melos exec --no-flutter --dir-exists=test -c 1 -- dart test --coverage-path=coverage/lcov.info --coverage-package="\$MELOS_PACKAGE_NAME" --branch-coverage --test-randomize-ordering-seed=random && dart run melos exec --flutter --scope=coder_app -c 1 -- flutter test --coverage --branch-coverage --exclude-tags=golden --test-randomize-ordering-seed=random && dart run tool/verify_coverage.dart test:e2e:linux: run: dart run melos exec --flutter --scope=coder_app -c 1 -- flutter test integration_test/debug_e2e_test.dart -d linux verify:fast: From 2a67893f2c037ca7686b7ae9f42d34b5789e3888 Mon Sep 17 00:00:00 2001 From: winetree94 Date: Mon, 3 Aug 2026 09:06:04 +0900 Subject: [PATCH 2/2] Guard disposed event handlers --- apps/coder_app/lib/src/controller.dart | 3 + apps/coder_app/lib/src/controller.g.dart | 6 +- apps/coder_app/test/controller_test.dart | 58 +++++++++++++++++++ .../test/support/fake_coder_api.dart | 6 +- 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/apps/coder_app/lib/src/controller.dart b/apps/coder_app/lib/src/controller.dart index 3fe0d44..784db0d 100644 --- a/apps/coder_app/lib/src/controller.dart +++ b/apps/coder_app/lib/src/controller.dart @@ -268,6 +268,7 @@ class AgentsController extends _$AgentsController { } void _handleEvent(ClientEvent event) { + if (!ref.mounted) return; if (event case AgentUpdatedClientEvent( :final agent, ) when agent.worktreeId == _worktreeId) { @@ -490,6 +491,7 @@ class ConversationController extends _$ConversationController { } void _handleEvent(ClientEvent clientEvent) { + if (!ref.mounted) return; final current = state.asData?.value; if (current == null) return; switch (clientEvent) { @@ -795,6 +797,7 @@ class ProviderSettingsController extends _$ProviderSettingsController { } void _handleEvent(ClientEvent event) { + if (!ref.mounted) return; if (event case ProviderAuthUpdatedClientEvent(:final attempt)) { final current = state.asData?.value; if (current == null) return; diff --git a/apps/coder_app/lib/src/controller.g.dart b/apps/coder_app/lib/src/controller.g.dart index 0d781f0..c5ea004 100644 --- a/apps/coder_app/lib/src/controller.g.dart +++ b/apps/coder_app/lib/src/controller.g.dart @@ -171,7 +171,7 @@ final class AgentsControllerProvider } } -String _$agentsControllerHash() => r'567a999c8471bd70b04ec43eb2f064f838bb5d59'; +String _$agentsControllerHash() => r'07c302f3e58214d248267cd8157baeb603b13c14'; /// AgentsController defines a public contract. @@ -375,7 +375,7 @@ final class ConversationControllerProvider } String _$conversationControllerHash() => - r'81662b5dd7bb8d0129cde3e09f6a5085f33ea1ea'; + r'dd73111cbc37321541072256ce24f11d830a8f33'; /// ConversationController defines a public contract. @@ -483,7 +483,7 @@ final class ProviderSettingsControllerProvider } String _$providerSettingsControllerHash() => - r'd058f4875231242b0c6162cf7576675f71ee62d1'; + r'3acb9b0d8e72bc00c45a657d5c267d1bde0fedf7'; /// ProviderSettingsController defines a public contract. diff --git a/apps/coder_app/test/controller_test.dart b/apps/coder_app/test/controller_test.dart index 38523bf..7424567 100644 --- a/apps/coder_app/test/controller_test.dart +++ b/apps/coder_app/test/controller_test.dart @@ -417,6 +417,42 @@ void main() { }, ); + test( + 'conversation ignores a transport event delivered after disposal', + () async { + final lateEvents = _LateClientEventStream(); + final api = FakeCoderApi( + agents: [agent], + eventStream: lateEvents, + ); + final container = _container(api); + addTearDown(container.dispose); + await container.read(hostRegistryControllerProvider.future); + await Future.delayed(Duration.zero); + final provider = conversationControllerProvider('server', agent.id); + final listener = container.listen(provider, (_, _) {}); + await container.read(provider.future); + + listener.close(); + await Future.delayed(Duration.zero); + + expect( + () => lateEvents.emit( + TimelineClientEvent( + TimelineEventDto( + agentId: agent.id, + sequence: 1, + type: 'assistant.delta', + data: const {'text': 'late'}, + createdAt: now, + ), + ), + ), + returnsNormally, + ); + }, + ); + test( 'provider settings notifier performs every administrative command', () async { @@ -584,3 +620,25 @@ final class _FixedIdGenerator implements AppIdGenerator { @override String generate() => 'generated-id'; } + +final class _LateClientEventStream extends Stream { + void Function(ClientEvent)? _onData; + + @override + StreamSubscription listen( + void Function(ClientEvent)? onData, { + Function? onError, + void Function()? onDone, + bool? cancelOnError, + }) { + _onData = onData; + return const Stream.empty().listen( + null, + onError: onError, + onDone: onDone, + cancelOnError: cancelOnError, + ); + } + + void emit(ClientEvent event) => _onData?.call(event); +} diff --git a/apps/coder_app/test/support/fake_coder_api.dart b/apps/coder_app/test/support/fake_coder_api.dart index 70b3670..7439e5a 100644 --- a/apps/coder_app/test/support/fake_coder_api.dart +++ b/apps/coder_app/test/support/fake_coder_api.dart @@ -18,6 +18,7 @@ final class FakeCoderApi implements CoderApi { List? agents, Map>? timelines, Map>? models, + this.eventStream, }) : _serverInfo = serverInfo ?? _defaultServerInfo, _catalog = catalog ?? _defaultCatalog, _connections = connections ?? [_openAIConnection], @@ -116,6 +117,9 @@ final class FakeCoderApi implements CoderApi { final List _agents; final Map> _timelines; final Map> _models; + + /// Optional event stream that can model transport lifecycle races. + final Stream? eventStream; final StreamController _events = StreamController.broadcast(sync: true); final StreamController _states = @@ -151,7 +155,7 @@ final class FakeCoderApi implements CoderApi { void emitState(ClientConnectionState state) => _states.add(state); @override - Stream get events => _events.stream; + Stream get events => eventStream ?? _events.stream; @override Stream get states => _states.stream;