Merge main HEAD (segment 5) into sandboxed-agent-rebase

Resolve 83 conflicts: 10 backend, 62 frontend, 11 config/lock files.
Preserve sandbox/agent/collaboration features while adopting main's
UI refactorings (Dialog/AlertDialog/Popover), model provider updates,
and enterprise features.

Made-with: Cursor
This commit is contained in:
Novice
2026-03-23 14:20:06 +08:00
1671 changed files with 124822 additions and 22302 deletions

View File

@ -1,9 +1,9 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { base } from './fetch'
vi.mock('@/app/components/base/toast', () => ({
default: {
notify: vi.fn(),
vi.mock('@/app/components/base/ui/toast', () => ({
toast: {
add: vi.fn(),
},
}))

View File

@ -2,7 +2,7 @@ import type { AfterResponseHook, BeforeRequestHook, Hooks } from 'ky'
import type { IOtherOptions } from './base'
import Cookies from 'js-cookie'
import ky, { HTTPError } from 'ky'
import Toast from '@/app/components/base/toast'
import { toast } from '@/app/components/base/ui/toast'
import { API_PREFIX, APP_VERSION, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, IS_MARKETPLACE, MARKETPLACE_API_PREFIX, PASSPORT_HEADER_NAME, PUBLIC_API_PREFIX, WEB_APP_SHARE_CODE_HEADER_NAME } from '@/config'
import { getWebAppAccessToken, getWebAppPassport } from './webapp-auth'
@ -48,7 +48,7 @@ const afterResponseErrorCode = (otherOptions: IOtherOptions): AfterResponseHook
const shouldNotifyError = response.status !== 401 && errorData && !otherOptions.silent
if (shouldNotifyError)
Toast.notify({ type: 'error', message: errorData.message })
toast.error(errorData.message)
if (response.status === 403 && errorData?.code === 'already_setup')
globalThis.location.href = `${globalThis.location.origin}/signin`

View File

@ -1,31 +1,29 @@
import type { QueryKey } from '@tanstack/react-query'
import {
useQueryClient,
} from '@tanstack/react-query'
import { useQueryClient } from '@tanstack/react-query'
import { useCallback } from 'react'
/**
* @deprecated Convenience wrapper scheduled for removal.
* Prefer binding invalidation in `useMutation` callbacks at the service layer.
*/
export const useInvalid = (key?: QueryKey) => {
const queryClient = useQueryClient()
return () => {
return useCallback(() => {
if (!key)
return Promise.resolve()
return queryClient.invalidateQueries(
{
queryKey: key,
},
)
}
return
queryClient.invalidateQueries({ queryKey: key })
}, [queryClient, key])
}
/**
* @deprecated Convenience wrapper scheduled for removal.
* Prefer binding reset in `useMutation` callbacks at the service layer.
*/
export const useReset = (key?: QueryKey) => {
const queryClient = useQueryClient()
return () => {
return useCallback(() => {
if (!key)
return Promise.resolve()
return queryClient.resetQueries(
{
queryKey: key,
},
)
}
return
queryClient.resetQueries({ queryKey: key })
}, [queryClient, key])
}

View File

@ -377,7 +377,7 @@ export const useNotionBinding = (code?: string | null, enabled?: boolean) => {
export const useModelParameterRules = (provider?: string, model?: string, enabled?: boolean) => {
return useQuery<{ data: ModelParameterRule[] }>({
queryKey: commonQueryKeys.modelParameterRules(provider, model),
queryFn: () => get<{ data: ModelParameterRule[] }>(`/workspaces/current/model-providers/${provider}/models/parameter-rules`, { params: { model } }),
queryFn: () => get<{ data: ModelParameterRule[] }>(`/workspaces/current/model-providers/${provider}/models/parameter-rules`, { params: { model }, silent: true }),
enabled: !!provider && !!model && (enabled ?? true),
})
}

View File

@ -10,14 +10,12 @@ import type {
DebugInfo as DebugInfoTypes,
Dependency,
GitHubItemAndMarketPlaceDependency,
InstalledLatestVersionResponse,
InstalledPluginListWithTotalResponse,
InstallPackageResponse,
InstallStatusResponse,
PackageDependency,
Plugin,
PluginDeclaration,
PluginDetail,
PluginInfoFromMarketPlace,
PluginsFromMarketplaceByInfoResponse,
PluginsFromMarketplaceResponse,
@ -42,11 +40,12 @@ import { PluginCategoryEnum, TaskStatus } from '@/app/components/plugins/types'
import { fetchModelProviderModelList } from '@/service/common'
import { fetchPluginInfoFromMarketPlace, uninstallPlugin } from '@/service/plugins'
import { get, getMarketplace, post, postMarketplace } from './base'
import { consoleQuery } from './client'
import { useInvalidateAllBuiltInTools } from './use-tools'
const NAME_SPACE = 'plugins'
const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList']
export const useCheckInstalled = ({
pluginIds,
enabled,
@ -54,16 +53,20 @@ export const useCheckInstalled = ({
pluginIds: string[]
enabled: boolean
}) => {
return useQuery<{ plugins: PluginDetail[] }>({
queryKey: [NAME_SPACE, 'checkInstalled', pluginIds],
queryFn: () => post<{ plugins: PluginDetail[] }>('/workspaces/current/plugin/list/installations/ids', {
body: {
plugin_ids: pluginIds,
},
}),
return useQuery(consoleQuery.plugins.checkInstalled.queryOptions({
input: { body: { plugin_ids: pluginIds } },
enabled,
staleTime: 0, // always fresh
})
staleTime: 0,
}))
}
export const useInvalidateCheckInstalled = () => {
const queryClient = useQueryClient()
return () => {
queryClient.invalidateQueries({
queryKey: consoleQuery.plugins.checkInstalled.key(),
})
}
}
const useRecommendedMarketplacePluginsKey = [NAME_SPACE, 'recommendedMarketplacePlugins']
@ -180,19 +183,6 @@ export const useInstalledPluginList = (disable?: boolean, pageSize = 100) => {
}
}
export const useInstalledLatestVersion = (pluginIds: string[]) => {
return useQuery<InstalledLatestVersionResponse>({
queryKey: [NAME_SPACE, 'installedLatestVersion', pluginIds],
queryFn: () => post<InstalledLatestVersionResponse>('/workspaces/current/plugin/list/latest-versions', {
body: {
plugin_ids: pluginIds,
},
}),
enabled: !!pluginIds.length,
initialData: pluginIds.length ? undefined : { versions: {} },
})
}
export const useInvalidateInstalledPluginList = () => {
const queryClient = useQueryClient()
const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools()
@ -685,7 +675,7 @@ export const useModelInList = (currentProvider?: ModelProvider, modelId?: string
return false
try {
const modelsData = await fetchModelProviderModelList(`/workspaces/current/model-providers/${provider}/models`)
return !!modelId && !!modelsData.data.find(item => item.model === modelId)
return !!modelId && modelsData.data.some(item => item.model === modelId)
}
catch {
return false

View File

@ -113,6 +113,13 @@ export const useDeleteWorkflow = () => {
})
}
export const useRestoreWorkflow = () => {
return useMutation({
mutationKey: [NAME_SPACE, 'restore'],
mutationFn: (url: string) => post<CommonResponse & { updated_at: number, hash: string }>(url, {}, { silent: true }),
})
}
export const usePublishWorkflow = () => {
return useMutation({
mutationKey: [NAME_SPACE, 'publish'],