@agent-relay/dashboard 2.0.82 → 2.0.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/out/404.html +1 -1
- package/out/_next/static/chunks/1028-da5d75e35d1420f1.js +1 -0
- package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
- package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
- package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
- package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
- package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
- package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
- package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
- package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
- package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
- package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
- package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
- package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
- package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
- package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
- package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
- package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
- package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
- package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
- package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
- package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
- package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
- package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
- package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
- package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
- package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
- package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
- package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
- package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
- package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
- package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
- package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
- package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
- package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
- package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
- package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
- package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
- package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
- package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
- package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
- package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
- package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
- package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
- package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
- package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
- package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
- package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
- package/out/about.html +2 -2
- package/out/about.txt +2 -2
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +2 -2
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +2 -2
- package/out/changelog.html +2 -2
- package/out/changelog.txt +2 -2
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +2 -2
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +2 -2
- package/out/contact.html +2 -2
- package/out/contact.txt +2 -2
- package/out/dev/cli-tools.html +1 -0
- package/out/dev/cli-tools.txt +7 -0
- package/out/dev/log-viewer.html +23 -0
- package/out/dev/log-viewer.txt +7 -0
- package/out/docs.html +2 -2
- package/out/docs.txt +2 -2
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +2 -2
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +2 -2
- package/out/privacy.html +2 -2
- package/out/privacy.txt +2 -2
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +2 -2
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +2 -2
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +2 -2
- package/out/providers.html +1 -1
- package/out/providers.txt +2 -2
- package/out/security.html +2 -2
- package/out/security.txt +2 -2
- package/out/signup.html +2 -2
- package/out/signup.txt +2 -2
- package/out/terms.html +2 -2
- package/out/terms.txt +2 -2
- package/package.json +5 -1
- package/src/adapters/DashboardConfigProvider.tsx +56 -0
- package/src/adapters/cloudFetchAdapter.ts +278 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/types.ts +508 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
- package/src/app/app/onboarding/page.tsx +870 -170
- package/src/app/cloud/link/page.tsx +14 -6
- package/src/app/connect-repos/page.tsx +9 -3
- package/src/app/dev/cli-tools/page.tsx +130 -0
- package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
- package/src/app/dev/log-viewer/fixtures.ts +110 -0
- package/src/app/dev/log-viewer/page.tsx +288 -0
- package/src/app/history/page.tsx +28 -12
- package/src/app/page.tsx +1 -1
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
- package/src/components/AgentCard.tsx +4 -4
- package/src/components/AgentLogPreview.tsx +2 -38
- package/src/components/App.tsx +441 -2624
- package/src/components/CliToolHarness.test.tsx +83 -0
- package/src/components/CliToolHarness.tsx +292 -0
- package/src/components/CoordinatorPanel.tsx +13 -6
- package/src/components/LogViewer.tsx +2 -42
- package/src/components/ProviderAuthFlow.tsx +201 -81
- package/src/components/ProvisioningProgress.tsx +1 -1
- package/src/components/ReactionChips.tsx +2 -1
- package/src/components/SpawnModal.test.tsx +51 -18
- package/src/components/SpawnModal.tsx +175 -207
- package/src/components/TerminalProviderSetup.tsx +1 -1
- package/src/components/ThreadPanel.tsx +2 -0
- package/src/components/WorkspaceContext.tsx +7 -19
- package/src/components/XTermLogViewer.tsx +190 -27
- package/src/components/channels/ChannelMessageList.tsx +94 -4
- package/src/components/channels/ChannelViewV1.tsx +35 -11
- package/src/components/channels/api.ts +21 -20
- package/src/components/channels/types.ts +16 -0
- package/src/components/hooks/index.ts +0 -19
- package/src/components/hooks/useMessages.test.ts +80 -0
- package/src/components/hooks/useMessages.ts +13 -4
- package/src/components/hooks/useOrchestrator.ts +1 -1
- package/src/components/hooks/usePresence.ts +45 -6
- package/src/components/hooks/useThread.ts +83 -46
- package/src/components/hooks/useTrajectory.ts +62 -5
- package/src/components/hooks/useWebSocket.test.ts +358 -0
- package/src/components/hooks/useWebSocket.ts +243 -5
- package/src/components/index.ts +2 -14
- package/src/components/layout/Header.tsx +9 -15
- package/src/components/layout/Sidebar.tsx +1 -8
- package/src/components/settings/SettingsPage.tsx +108 -47
- package/src/components/settings/index.ts +0 -3
- package/src/landing/blogData.ts +1 -1
- package/src/lib/agent-merge.test.ts +2 -2
- package/src/lib/api.ts +8 -38
- package/src/lib/identity.test.ts +139 -0
- package/src/lib/identity.ts +48 -0
- package/src/lib/relaycastMessageAdapters.test.ts +182 -0
- package/src/lib/relaycastMessageAdapters.ts +105 -0
- package/src/lib/sanitize-logs.test.ts +227 -0
- package/src/lib/sanitize-logs.ts +202 -0
- package/src/providers/AgentProvider.tsx +799 -0
- package/src/providers/ChannelProvider.tsx +528 -0
- package/src/providers/CloudWorkspaceProvider.tsx +402 -0
- package/src/providers/MessageProvider.tsx +875 -0
- package/src/providers/RelayConfigProvider.tsx +94 -0
- package/src/providers/SendProvider.tsx +497 -0
- package/src/providers/SettingsProvider.tsx +247 -0
- package/src/providers/index.ts +26 -0
- package/src/types/index.ts +10 -10
- package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
- package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
- package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
- package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
- package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
- package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
- package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
- package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
- package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
- package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
- package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
- package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
- package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
- package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
- package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
- package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
- package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
- package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
- package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
- package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
- package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
- package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
- package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
- package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
- package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
- package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
- package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
- package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
- package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
- package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
- package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
- package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
- package/src/components/BillingResult.tsx +0 -447
- package/src/components/CloudSessionProvider.tsx +0 -130
- package/src/components/SessionExpiredModal.tsx +0 -128
- package/src/components/WorkspaceStatusIndicator.tsx +0 -396
- package/src/components/hooks/useSession.ts +0 -209
- package/src/components/hooks/useWorkspaceMembers.ts +0 -132
- package/src/components/hooks/useWorkspaceStatus.ts +0 -237
- package/src/components/settings/BillingSettingsPanel.tsx +0 -564
- package/src/components/settings/TeamSettingsPanel.tsx +0 -560
- package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
- package/src/lib/cloudApi.ts +0 -893
- /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
|
@@ -2,18 +2,10 @@
|
|
|
2
2
|
* Provider Auth Flow Component
|
|
3
3
|
*
|
|
4
4
|
* Shared component for AI provider authentication via SSH tunnel.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Flow:
|
|
8
|
-
* 1. Calls /api/auth/ssh/init to get a CLI command with one-time token
|
|
9
|
-
* 2. User copies and runs the command in their local terminal
|
|
10
|
-
* 3. CLI establishes SSH to workspace and runs the provider's auth command
|
|
11
|
-
* 4. User completes interactive auth (OAuth in browser, etc.)
|
|
12
|
-
* 5. CLI calls /api/auth/ssh/complete to mark provider as connected
|
|
13
|
-
* 6. Dashboard polls /api/auth/ssh/status/:workspaceId to detect completion
|
|
5
|
+
* Supports both workspace mode and onboarding mode.
|
|
14
6
|
*/
|
|
15
7
|
|
|
16
|
-
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
8
|
+
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
|
17
9
|
|
|
18
10
|
export interface ProviderInfo {
|
|
19
11
|
id: string;
|
|
@@ -27,19 +19,38 @@ export interface ProviderInfo {
|
|
|
27
19
|
|
|
28
20
|
export interface ProviderAuthFlowProps {
|
|
29
21
|
provider: ProviderInfo;
|
|
30
|
-
workspaceId
|
|
22
|
+
workspaceId?: string;
|
|
23
|
+
mode?: 'workspace' | 'onboarding';
|
|
31
24
|
csrfToken?: string;
|
|
25
|
+
showManualDone?: boolean;
|
|
32
26
|
onSuccess: () => void;
|
|
33
27
|
onCancel: () => void;
|
|
34
28
|
onError: (error: string) => void;
|
|
35
29
|
}
|
|
36
30
|
|
|
31
|
+
interface OnboardingNextStepResponse {
|
|
32
|
+
nextStep?: string;
|
|
33
|
+
connectedProviders?: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function getOnboardingNextStep(): Promise<OnboardingNextStepResponse> {
|
|
37
|
+
const response = await fetch('/api/onboarding/next-step', {
|
|
38
|
+
credentials: 'include',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!response.ok) {
|
|
42
|
+
throw new Error('Failed to fetch onboarding next step');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (await response.json()) as OnboardingNextStepResponse;
|
|
46
|
+
}
|
|
47
|
+
|
|
37
48
|
type AuthStatus = 'idle' | 'starting' | 'waiting' | 'success' | 'error';
|
|
38
49
|
|
|
39
50
|
/**
|
|
40
51
|
* Map dashboard provider IDs to SSH backend provider names.
|
|
41
52
|
* The SSH status endpoint uses PROVIDER_COMMANDS keys (anthropic, openai, google, etc.)
|
|
42
|
-
* while the dashboard
|
|
53
|
+
* while the dashboard may use codex for OpenAI.
|
|
43
54
|
*/
|
|
44
55
|
const PROVIDER_STATUS_MAP: Record<string, string> = {
|
|
45
56
|
codex: 'openai',
|
|
@@ -49,10 +60,21 @@ function getStatusProviderName(providerId: string): string {
|
|
|
49
60
|
return PROVIDER_STATUS_MAP[providerId] || providerId;
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
function getStatusProviderNames(providerId: string): string[] {
|
|
64
|
+
const mapped = getStatusProviderName(providerId);
|
|
65
|
+
return Array.from(new Set([mapped, providerId]));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function sleep(ms: number): Promise<void> {
|
|
69
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
70
|
+
}
|
|
71
|
+
|
|
52
72
|
export function ProviderAuthFlow({
|
|
53
73
|
provider,
|
|
54
74
|
workspaceId,
|
|
75
|
+
mode,
|
|
55
76
|
csrfToken,
|
|
77
|
+
showManualDone,
|
|
56
78
|
onSuccess,
|
|
57
79
|
onCancel,
|
|
58
80
|
onError,
|
|
@@ -61,11 +83,124 @@ export function ProviderAuthFlow({
|
|
|
61
83
|
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
|
62
84
|
const [cliCommand, setCliCommand] = useState<string | null>(null);
|
|
63
85
|
const [copied, setCopied] = useState(false);
|
|
86
|
+
const [statusWorkspaceId, setStatusWorkspaceId] = useState<string | null>(workspaceId || null);
|
|
87
|
+
|
|
64
88
|
const pollingRef = useRef(false);
|
|
65
89
|
const completingRef = useRef(false);
|
|
66
90
|
|
|
91
|
+
const effectiveMode = mode ?? (workspaceId ? 'workspace' : 'onboarding');
|
|
92
|
+
const shouldShowManualDone = showManualDone ?? effectiveMode === 'onboarding';
|
|
93
|
+
|
|
67
94
|
const backendProviderId = provider.id;
|
|
68
|
-
const
|
|
95
|
+
const statusProviderNames = useMemo(
|
|
96
|
+
() => getStatusProviderNames(backendProviderId),
|
|
97
|
+
[backendProviderId]
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const completeSuccess = useCallback(() => {
|
|
101
|
+
if (completingRef.current) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
completingRef.current = true;
|
|
106
|
+
setStatus('success');
|
|
107
|
+
setTimeout(() => onSuccess(), 1200);
|
|
108
|
+
}, [onSuccess]);
|
|
109
|
+
|
|
110
|
+
const isProviderConnectedFromStatus = useCallback(
|
|
111
|
+
(providers: Array<{ name: string; status: string }> | undefined): boolean => {
|
|
112
|
+
if (!providers || !Array.isArray(providers)) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return statusProviderNames.some((providerName) =>
|
|
117
|
+
providers.some((providerStatus) => providerStatus.name === providerName && providerStatus.status === 'connected')
|
|
118
|
+
);
|
|
119
|
+
},
|
|
120
|
+
[statusProviderNames]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const isProviderConnectedFromOnboarding = useCallback(
|
|
124
|
+
(data: OnboardingNextStepResponse): boolean => {
|
|
125
|
+
if (data.nextStep && data.nextStep !== 'connect_ai_provider') {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const connectedProviders = Array.isArray(data.connectedProviders)
|
|
130
|
+
? data.connectedProviders
|
|
131
|
+
: [];
|
|
132
|
+
|
|
133
|
+
return statusProviderNames.some((providerName) => connectedProviders.includes(providerName));
|
|
134
|
+
},
|
|
135
|
+
[statusProviderNames]
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const checkConnected = useCallback(
|
|
139
|
+
async (currentWorkspaceId?: string): Promise<boolean> => {
|
|
140
|
+
if (currentWorkspaceId) {
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(`/api/auth/ssh/status/${currentWorkspaceId}`, {
|
|
143
|
+
credentials: 'include',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (res.ok) {
|
|
147
|
+
const data = (await res.json()) as {
|
|
148
|
+
providers?: Array<{ name: string; status: string }>;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (isProviderConnectedFromStatus(data.providers)) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// Fall through to onboarding check
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (effectiveMode !== 'onboarding') {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const stepData = (await getOnboardingNextStep()) as OnboardingNextStepResponse;
|
|
166
|
+
return isProviderConnectedFromOnboarding(stepData);
|
|
167
|
+
} catch {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
[effectiveMode, isProviderConnectedFromOnboarding, isProviderConnectedFromStatus]
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const pollUntilConnected = useCallback(
|
|
175
|
+
async (workspaceForStatus?: string | null): Promise<void> => {
|
|
176
|
+
if (pollingRef.current) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
pollingRef.current = true;
|
|
181
|
+
|
|
182
|
+
const maxAttempts = 120; // 10 minutes at 5s intervals
|
|
183
|
+
let attempts = 0;
|
|
184
|
+
|
|
185
|
+
while (pollingRef.current && attempts < maxAttempts) {
|
|
186
|
+
const connected = await checkConnected(workspaceForStatus || undefined);
|
|
187
|
+
if (connected) {
|
|
188
|
+
pollingRef.current = false;
|
|
189
|
+
completeSuccess();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
attempts += 1;
|
|
194
|
+
await sleep(5000);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
pollingRef.current = false;
|
|
198
|
+
setErrorMessage('Authentication timed out. Please try again.');
|
|
199
|
+
setStatus('error');
|
|
200
|
+
onError('Authentication timed out');
|
|
201
|
+
},
|
|
202
|
+
[checkConnected, completeSuccess, onError]
|
|
203
|
+
);
|
|
69
204
|
|
|
70
205
|
// Start the SSH auth flow
|
|
71
206
|
const startAuth = useCallback(async () => {
|
|
@@ -75,99 +210,75 @@ export function ProviderAuthFlow({
|
|
|
75
210
|
|
|
76
211
|
try {
|
|
77
212
|
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
78
|
-
if (csrfToken)
|
|
213
|
+
if (csrfToken) {
|
|
214
|
+
headers['X-CSRF-Token'] = csrfToken;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const payload: Record<string, unknown> = {
|
|
218
|
+
provider: backendProviderId,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (workspaceId) {
|
|
222
|
+
payload.workspaceId = workspaceId;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (effectiveMode === 'onboarding') {
|
|
226
|
+
payload.mode = 'onboarding';
|
|
227
|
+
}
|
|
79
228
|
|
|
80
229
|
const res = await fetch('/api/auth/ssh/init', {
|
|
81
230
|
method: 'POST',
|
|
82
231
|
credentials: 'include',
|
|
83
232
|
headers,
|
|
84
|
-
body: JSON.stringify(
|
|
85
|
-
provider: backendProviderId,
|
|
86
|
-
workspaceId,
|
|
87
|
-
}),
|
|
233
|
+
body: JSON.stringify(payload),
|
|
88
234
|
});
|
|
89
235
|
|
|
90
|
-
const data = await res.json()
|
|
236
|
+
const data = (await res.json()) as {
|
|
237
|
+
command?: string;
|
|
238
|
+
commandWithUrl?: string;
|
|
239
|
+
workspaceId?: string;
|
|
240
|
+
error?: string;
|
|
241
|
+
};
|
|
91
242
|
|
|
92
243
|
if (!res.ok) {
|
|
93
244
|
throw new Error(data.error || 'Failed to start authentication');
|
|
94
245
|
}
|
|
95
246
|
|
|
96
247
|
const rawCommand = data.commandWithUrl || data.command;
|
|
97
|
-
// Prepend npx if not already present
|
|
98
248
|
const commandWithNpx = rawCommand && !rawCommand.trim().startsWith('npx ')
|
|
99
249
|
? `npx ${rawCommand}`
|
|
100
250
|
: rawCommand;
|
|
251
|
+
|
|
252
|
+
if (!commandWithNpx) {
|
|
253
|
+
throw new Error('Auth command missing in response');
|
|
254
|
+
}
|
|
255
|
+
|
|
101
256
|
setCliCommand(commandWithNpx);
|
|
102
257
|
setStatus('waiting');
|
|
103
258
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
259
|
+
const resolvedWorkspaceId = data.workspaceId || workspaceId || null;
|
|
260
|
+
setStatusWorkspaceId(resolvedWorkspaceId);
|
|
261
|
+
|
|
262
|
+
void pollUntilConnected(resolvedWorkspaceId);
|
|
108
263
|
} catch (err) {
|
|
109
264
|
const msg = err instanceof Error ? err.message : 'Failed to start authentication';
|
|
110
265
|
setErrorMessage(msg);
|
|
111
266
|
setStatus('error');
|
|
112
267
|
onError(msg);
|
|
113
268
|
}
|
|
114
|
-
}, [backendProviderId, workspaceId, csrfToken, onError]);
|
|
115
|
-
|
|
116
|
-
// Poll SSH auth status endpoint to detect when provider is connected
|
|
117
|
-
const startPolling = useCallback((wsId: string) => {
|
|
118
|
-
if (pollingRef.current) return;
|
|
119
|
-
pollingRef.current = true;
|
|
120
|
-
|
|
121
|
-
const maxAttempts = 120; // 10 minutes at 5s intervals
|
|
122
|
-
let attempts = 0;
|
|
123
|
-
|
|
124
|
-
const poll = async () => {
|
|
125
|
-
if (attempts >= maxAttempts || !pollingRef.current) {
|
|
126
|
-
pollingRef.current = false;
|
|
127
|
-
if (attempts >= maxAttempts) {
|
|
128
|
-
setErrorMessage('Authentication timed out. Please try again.');
|
|
129
|
-
setStatus('error');
|
|
130
|
-
onError('Authentication timed out');
|
|
131
|
-
}
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
269
|
+
}, [backendProviderId, workspaceId, effectiveMode, csrfToken, onError, pollUntilConnected]);
|
|
134
270
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const data = await res.json() as {
|
|
142
|
-
providers: Array<{ name: string; status: string }>;
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
const providerStatus = data.providers?.find(
|
|
146
|
-
(p) => p.name === statusProviderId
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
if (providerStatus?.status === 'connected') {
|
|
150
|
-
pollingRef.current = false;
|
|
151
|
-
if (!completingRef.current) {
|
|
152
|
-
completingRef.current = true;
|
|
153
|
-
setStatus('success');
|
|
154
|
-
setTimeout(() => onSuccess(), 1500);
|
|
155
|
-
}
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
attempts++;
|
|
161
|
-
setTimeout(poll, 5000);
|
|
162
|
-
} catch (err) {
|
|
163
|
-
console.error('Poll error:', err);
|
|
164
|
-
attempts++;
|
|
165
|
-
setTimeout(poll, 5000);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
271
|
+
const handleManualDone = useCallback(async () => {
|
|
272
|
+
const connected = await checkConnected(statusWorkspaceId || undefined);
|
|
273
|
+
if (connected) {
|
|
274
|
+
completeSuccess();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
168
277
|
|
|
169
|
-
|
|
170
|
-
|
|
278
|
+
const message = 'Authentication is still in progress. Finish the CLI flow and try again.';
|
|
279
|
+
setErrorMessage(message);
|
|
280
|
+
onError(message);
|
|
281
|
+
}, [checkConnected, statusWorkspaceId, completeSuccess, onError]);
|
|
171
282
|
|
|
172
283
|
// Cancel auth flow
|
|
173
284
|
const handleCancel = useCallback(() => {
|
|
@@ -191,7 +302,7 @@ export function ProviderAuthFlow({
|
|
|
191
302
|
// Start auth when component mounts
|
|
192
303
|
useEffect(() => {
|
|
193
304
|
if (status === 'idle') {
|
|
194
|
-
startAuth();
|
|
305
|
+
void startAuth();
|
|
195
306
|
}
|
|
196
307
|
}, [startAuth, status]);
|
|
197
308
|
|
|
@@ -281,7 +392,7 @@ export function ProviderAuthFlow({
|
|
|
281
392
|
<strong>Step 4:</strong> Wait for the {provider.displayName} input prompt, then type <code className="px-1 py-0.5 bg-bg-deep rounded">exit</code>
|
|
282
393
|
</p>
|
|
283
394
|
<p className="text-xs text-amber-400/80">
|
|
284
|
-
Do
|
|
395
|
+
Do not close the terminal early. After sign-in completes, wait until you see the input screen prompt, then type <code className="px-1 py-0.5 bg-bg-deep rounded">exit</code> and press Enter.
|
|
285
396
|
</p>
|
|
286
397
|
</div>
|
|
287
398
|
|
|
@@ -294,6 +405,15 @@ export function ProviderAuthFlow({
|
|
|
294
405
|
<span>Waiting for authentication to complete...</span>
|
|
295
406
|
</div>
|
|
296
407
|
|
|
408
|
+
{shouldShowManualDone && (
|
|
409
|
+
<button
|
|
410
|
+
onClick={handleManualDone}
|
|
411
|
+
className="w-full py-2 px-4 bg-accent-cyan text-bg-deep font-semibold rounded-lg hover:bg-accent-cyan/90 transition-colors"
|
|
412
|
+
>
|
|
413
|
+
Done
|
|
414
|
+
</button>
|
|
415
|
+
)}
|
|
416
|
+
|
|
297
417
|
{/* Cancel button */}
|
|
298
418
|
<button
|
|
299
419
|
onClick={handleCancel}
|
|
@@ -77,7 +77,7 @@ const TERMINAL_MESSAGES = [
|
|
|
77
77
|
'> Establishing secure connection...',
|
|
78
78
|
'> Allocating compute resources...',
|
|
79
79
|
'> Configuring agent protocols...',
|
|
80
|
-
'> Initializing relay
|
|
80
|
+
'> Initializing relay broker...',
|
|
81
81
|
'> Syncing workspace state...',
|
|
82
82
|
'> Warming up inference engine...',
|
|
83
83
|
'> Connecting to neural mesh...',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useState, useRef } from 'react';
|
|
2
|
+
import { resolveEmoji } from '@relaycast/types';
|
|
2
3
|
import type { Reaction } from '../types';
|
|
3
4
|
import { ReactionPicker } from './ReactionPicker';
|
|
4
5
|
|
|
@@ -40,7 +41,7 @@ export function ReactionChips({
|
|
|
40
41
|
}
|
|
41
42
|
`}
|
|
42
43
|
>
|
|
43
|
-
<span>{reaction.emoji}</span>
|
|
44
|
+
<span>{resolveEmoji(reaction.emoji)}</span>
|
|
44
45
|
<span className="font-medium">{reaction.count}</span>
|
|
45
46
|
</button>
|
|
46
47
|
);
|
|
@@ -10,24 +10,10 @@ import React from 'react';
|
|
|
10
10
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
11
|
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
|
|
12
12
|
import { SpawnModal } from './SpawnModal';
|
|
13
|
+
import { DashboardConfigProvider } from '../adapters';
|
|
13
14
|
|
|
14
15
|
const MOCK_WORKSPACE_ID = '12345678-1234-1234-1234-123456789012';
|
|
15
16
|
|
|
16
|
-
// Mock cloudApi to return connected providers so the button is enabled
|
|
17
|
-
vi.mock('../lib/cloudApi', () => ({
|
|
18
|
-
cloudApi: {
|
|
19
|
-
getProviders: vi.fn().mockResolvedValue({
|
|
20
|
-
success: true,
|
|
21
|
-
data: {
|
|
22
|
-
providers: [
|
|
23
|
-
{ id: 'anthropic', name: 'Claude', displayName: 'Claude', isConnected: true },
|
|
24
|
-
{ id: 'codex', name: 'Codex', displayName: 'Codex', isConnected: true },
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
}),
|
|
28
|
-
},
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
17
|
const mockRepos = [
|
|
32
18
|
{ id: 'repo-1', githubFullName: 'AgentWorkforce/relay' },
|
|
33
19
|
{ id: 'repo-2', githubFullName: 'AgentWorkforce/trajectories' },
|
|
@@ -40,15 +26,27 @@ function getForm(): HTMLFormElement {
|
|
|
40
26
|
return form;
|
|
41
27
|
}
|
|
42
28
|
|
|
43
|
-
|
|
29
|
+
type SpawnModalOverrides = Partial<React.ComponentProps<typeof SpawnModal>> & {
|
|
30
|
+
isCloudMode?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function renderSpawnModal(overrides: SpawnModalOverrides = {}) {
|
|
34
|
+
const { isCloudMode = false, ...spawnModalOverrides } = overrides;
|
|
44
35
|
const defaultProps: React.ComponentProps<typeof SpawnModal> = {
|
|
45
36
|
isOpen: true,
|
|
46
37
|
onClose: vi.fn(),
|
|
47
38
|
onSpawn: vi.fn().mockResolvedValue(true),
|
|
48
39
|
existingAgents: [],
|
|
49
|
-
...
|
|
40
|
+
...spawnModalOverrides,
|
|
41
|
+
};
|
|
42
|
+
return {
|
|
43
|
+
...render(
|
|
44
|
+
<DashboardConfigProvider config={{ features: { workspaces: isCloudMode } }}>
|
|
45
|
+
<SpawnModal {...defaultProps} />
|
|
46
|
+
</DashboardConfigProvider>
|
|
47
|
+
),
|
|
48
|
+
props: defaultProps,
|
|
50
49
|
};
|
|
51
|
-
return { ...render(<SpawnModal {...defaultProps} />), props: defaultProps };
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
describe('SpawnModal', () => {
|
|
@@ -157,6 +155,41 @@ describe('SpawnModal', () => {
|
|
|
157
155
|
});
|
|
158
156
|
});
|
|
159
157
|
|
|
158
|
+
describe('resume previous session', () => {
|
|
159
|
+
it('includes continueFrom when toggle is enabled', async () => {
|
|
160
|
+
const onSpawn = vi.fn().mockResolvedValue(true);
|
|
161
|
+
renderSpawnModal({ onSpawn });
|
|
162
|
+
|
|
163
|
+
// Enable the resume toggle
|
|
164
|
+
const resumeSection = screen.getByText('Resume Previous Session');
|
|
165
|
+
const toggle = resumeSection.closest('div')?.parentElement?.querySelector('button[aria-pressed]');
|
|
166
|
+
if (toggle) fireEvent.click(toggle);
|
|
167
|
+
|
|
168
|
+
fireEvent.submit(getForm());
|
|
169
|
+
|
|
170
|
+
await waitFor(() => {
|
|
171
|
+
expect(onSpawn).toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const config = onSpawn.mock.calls[0][0];
|
|
175
|
+
expect(config.continueFrom).toBe('claude-1'); // default suggested name
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('does not include continueFrom when toggle is disabled', async () => {
|
|
179
|
+
const onSpawn = vi.fn().mockResolvedValue(true);
|
|
180
|
+
renderSpawnModal({ onSpawn });
|
|
181
|
+
|
|
182
|
+
fireEvent.submit(getForm());
|
|
183
|
+
|
|
184
|
+
await waitFor(() => {
|
|
185
|
+
expect(onSpawn).toHaveBeenCalled();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const config = onSpawn.mock.calls[0][0];
|
|
189
|
+
expect(config.continueFrom).toBeUndefined();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
160
193
|
describe('working directory (local mode)', () => {
|
|
161
194
|
it('shows working directory input when not in cloud mode', () => {
|
|
162
195
|
renderSpawnModal({ isCloudMode: false });
|