From b97abe53282744a53d064bbee12dd2271e995802 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 16:19:15 +0800 Subject: [PATCH 01/17] chore: bump nltk from 3.9.4 to 3.10.0 in /api (#39503) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: yunlu.wen --- api/Dockerfile | 4 ++-- .../vdb/vdb-oracle/src/dify_vdb_oracle/oraclevector.py | 4 ++-- api/pyproject.toml | 2 +- api/uv.lock | 9 +++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index 1823a8f6a35..311bc51df15 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -99,9 +99,9 @@ ENV VIRTUAL_ENV=/app/api/.venv COPY --from=packages --chown=dify:dify ${VIRTUAL_ENV} ${VIRTUAL_ENV} ENV PATH="${VIRTUAL_ENV}/bin:${PATH}" -# Download nltk data RUN mkdir -p /usr/local/share/nltk_data \ - && NLTK_DATA=/usr/local/share/nltk_data python -c "import nltk; nltk.download('punkt'); nltk.download('averaged_perceptron_tagger'); nltk.download('stopwords')" \ + && NLTK_DATA=/usr/local/share/nltk_data python -m nltk.downloader punkt_tab averaged_perceptron_tagger_eng stopwords \ + && NLTK_DATA=/usr/local/share/nltk_data python -c "import nltk; nltk.data.find('tokenizers/punkt_tab'); nltk.data.find('taggers/averaged_perceptron_tagger_eng'); nltk.data.find('corpora/stopwords')" \ && chmod -R 755 /usr/local/share/nltk_data ENV TIKTOKEN_CACHE_DIR=/app/api/.tiktoken_cache diff --git a/api/providers/vdb/vdb-oracle/src/dify_vdb_oracle/oraclevector.py b/api/providers/vdb/vdb-oracle/src/dify_vdb_oracle/oraclevector.py index b8639dae619..831ccd32009 100644 --- a/api/providers/vdb/vdb-oracle/src/dify_vdb_oracle/oraclevector.py +++ b/api/providers/vdb/vdb-oracle/src/dify_vdb_oracle/oraclevector.py @@ -316,10 +316,10 @@ class OracleVector(BaseVector): entities.append(current_entity) else: try: - nltk.data.find("tokenizers/punkt") + nltk.data.find("tokenizers/punkt_tab") nltk.data.find("corpora/stopwords") except LookupError: - raise LookupError("Unable to find the required NLTK data package: punkt and stopwords") + raise LookupError("Unable to find the required NLTK data package: punkt_tab and stopwords") e_str = re.sub(r"[^\w ]", "", query) all_tokens = nltk.word_tokenize(e_str) stop_words = stopwords.words("english") diff --git a/api/pyproject.toml b/api/pyproject.toml index 5ebe5d610c4..27070217e63 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -206,7 +206,7 @@ storage = [ ############################################################ # [ Tools ] dependency group ############################################################ -tools = ["cloudscraper>=1.2.71,<2.0.0", "nltk>=3.9.1,<4.0.0"] +tools = ["cloudscraper>=1.2.71,<2.0.0", "nltk>=3.10.0,<4.0.0"] ############################################################ # [ VDB ] workspace plugins — hollow packages under providers/vdb/* diff --git a/api/uv.lock b/api/uv.lock index 676de350ecb..0b1552c4ba9 100644 --- a/api/uv.lock +++ b/api/uv.lock @@ -1738,7 +1738,7 @@ storage = [ ] tools = [ { name = "cloudscraper", specifier = ">=1.2.71,<2.0.0" }, - { name = "nltk", specifier = ">=3.9.1,<4.0.0" }, + { name = "nltk", specifier = ">=3.10.0,<4.0.0" }, ] trace-aliyun = [{ name = "dify-trace-aliyun", editable = "providers/trace/trace-aliyun" }] trace-all = [ @@ -4113,17 +4113,18 @@ wheels = [ [[package]] name = "nltk" -version = "3.9.4" +version = "3.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, + { name = "defusedxml" }, { name = "joblib" }, { name = "regex" }, { name = "tqdm" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/74/a1/b3b4adf15585a5bc4c357adde150c01ebeeb642173ded4d871e89468767c/nltk-3.9.4.tar.gz", hash = "sha256:ed03bc098a40481310320808b2db712d95d13ca65b27372f8a403949c8b523d0", size = 2946864, upload-time = "2026-03-24T06:13:40.641Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/02/df4f105b28a7c16b0e41423bc09cf0f1b8a305df4ef0b10ca74a2e4c648c/nltk-3.10.0.tar.gz", hash = "sha256:4fbac1d98203cbcd1b5d94a2877fb822300072d80604a5e7fae49d2c5f84e8c1", size = 3089244, upload-time = "2026-07-08T02:39:13.562Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/91/04e965f8e717ba0ab4bdca5c112deeab11c9e750d94c4d4602f050295d39/nltk-3.9.4-py3-none-any.whl", hash = "sha256:f2fa301c3a12718ce4a0e9305c5675299da5ad9e26068218b69d692fda84828f", size = 1552087, upload-time = "2026-03-24T06:13:38.47Z" }, + { url = "https://files.pythonhosted.org/packages/6e/89/a0b0f35e2820d6a99d75ea1c11977ee6d5c9e6658eceb45b0c7620881faa/nltk-3.10.0-py3-none-any.whl", hash = "sha256:54ff84d4916d3ef127e8953bee0023f6a6b320b75d634a19e06ef056d3d244bf", size = 1716144, upload-time = "2026-07-08T02:39:09.753Z" }, ] [[package]] From ae0b66311d789dd9341edf6f3e0aaf9fde7249ae Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 28 Jul 2026 16:23:42 +0800 Subject: [PATCH 02/17] feat: add missing plugin installation to agent DSL imports (#39680) --- .../agents/__tests__/layout.spec.tsx | 39 ++++ web/app/(commonLayout)/agents/layout.tsx | 8 +- .../workflow/plugin-dependency/index.tsx | 2 + .../agent-composer/__tests__/store.spec.ts | 34 ++++ .../agent-v2/agent-composer/conversions.ts | 21 ++- .../agent-v2/agent-composer/form-state.ts | 2 + .../agent-composer/store-modules/tools.ts | 5 + .../tools/__tests__/index.spec.tsx | 167 +++++++++++++++++- .../components/orchestrate/tools/index.tsx | 149 ++++++++++++++-- .../orchestrate/tools/provider-tool/item.tsx | 111 +++++++++--- 10 files changed, 495 insertions(+), 43 deletions(-) diff --git a/web/app/(commonLayout)/agents/__tests__/layout.spec.tsx b/web/app/(commonLayout)/agents/__tests__/layout.spec.tsx index 8bcf8a44b5b..edb7035db74 100644 --- a/web/app/(commonLayout)/agents/__tests__/layout.spec.tsx +++ b/web/app/(commonLayout)/agents/__tests__/layout.spec.tsx @@ -1,10 +1,20 @@ import type { ReactNode } from 'react' +import type { Dependency } from '@/app/components/plugins/types' import { render, screen } from '@testing-library/react' +import { useStore as usePluginDependencyStore } from '@/app/components/workflow/plugin-dependency/store' const mocks = vi.hoisted(() => ({ guardAgentV2Route: vi.fn(), })) +vi.mock('@/app/components/plugins/install-plugin/install-bundle', () => ({ + default: ({ fromDSLPayload }: { fromDSLPayload: Dependency[] }) => ( +
+ {`bundle-size:${fromDSLPayload.length}`} +
+ ), +})) + vi.mock('../feature-guard', () => ({ guardAgentV2Route: () => mocks.guardAgentV2Route(), })) @@ -18,6 +28,7 @@ vi.mock('../agents-access-guard', () => ({ describe('RosterLayout', () => { beforeEach(() => { vi.clearAllMocks() + usePluginDependencyStore.setState({ dependencies: [] }) }) it('should render children when Agent v2 is enabled', async () => { @@ -33,6 +44,34 @@ describe('RosterLayout', () => { expect(screen.getByText('Roster content')).toBeInTheDocument() }) + it('should show the missing-plugin installer across Agent routes', async () => { + usePluginDependencyStore.setState({ + dependencies: [ + { + type: 'marketplace', + value: { + organization: 'langgenius', + plugin: 'sample-plugin', + version: '1.0.0', + plugin_unique_identifier: 'langgenius/sample-plugin:1.0.0', + }, + }, + ], + }) + const { default: RosterLayout } = await import('../layout') + + render( + +
Agent route content
+
, + ) + + expect(screen.getByRole('dialog', { name: 'Install missing plugins' })).toHaveTextContent( + 'bundle-size:1', + ) + expect(screen.getByText('Agent route content')).toBeInTheDocument() + }) + it('should block rendering when the roster guard throws notFound', async () => { mocks.guardAgentV2Route.mockImplementation(() => { throw new Error('NEXT_NOT_FOUND') diff --git a/web/app/(commonLayout)/agents/layout.tsx b/web/app/(commonLayout)/agents/layout.tsx index 5f87a857cb1..ec47488a564 100644 --- a/web/app/(commonLayout)/agents/layout.tsx +++ b/web/app/(commonLayout)/agents/layout.tsx @@ -1,9 +1,15 @@ import type { ReactNode } from 'react' +import PluginDependency from '@/app/components/workflow/plugin-dependency' import { AgentsAccessGuard } from './agents-access-guard' import { guardAgentV2Route } from './feature-guard' export default function Layout({ children }: { children: ReactNode }) { guardAgentV2Route() - return {children} + return ( + + + {children} + + ) } diff --git a/web/app/components/workflow/plugin-dependency/index.tsx b/web/app/components/workflow/plugin-dependency/index.tsx index 1630d516c65..6fe95e0a321 100644 --- a/web/app/components/workflow/plugin-dependency/index.tsx +++ b/web/app/components/workflow/plugin-dependency/index.tsx @@ -1,3 +1,5 @@ +'use client' + import { useCallback } from 'react' import InstallBundle from '@/app/components/plugins/install-plugin/install-bundle' import { useStore } from './store' diff --git a/web/features/agent-v2/agent-composer/__tests__/store.spec.ts b/web/features/agent-v2/agent-composer/__tests__/store.spec.ts index ed7615457aa..c4dd9d3dab1 100644 --- a/web/features/agent-v2/agent-composer/__tests__/store.spec.ts +++ b/web/features/agent-v2/agent-composer/__tests__/store.spec.ts @@ -381,6 +381,40 @@ describe('agent composer store conversions', () => { }) }) + it('should preserve a plugin tool identity when hydrating and publishing imported config', () => { + const baseConfig = { + tools: { + dify_tools: [ + { + plugin_id: 'langgenius/google', + provider_id: 'langgenius/google/google', + provider_type: 'plugin', + tool_name: 'search', + credential_type: 'unauthorized', + }, + ], + }, + } satisfies AgentSoulConfig + + const formState = agentSoulConfigToFormState(baseConfig) + const publishConfig = formStateToAgentSoulConfig({ baseConfig, formState }) + + expect(formState.tools).toEqual([ + expect.objectContaining({ + id: 'langgenius/google/google', + name: 'google', + pluginId: 'langgenius/google', + }), + ]) + expect(publishConfig.tools?.dify_tools).toEqual([ + expect.objectContaining({ + plugin_id: 'langgenius/google', + provider: 'google', + provider_id: 'langgenius/google/google', + }), + ]) + }) + it('should hydrate legacy secret refs from ref when value is absent', () => { const formState = agentSoulConfigToFormState({ env: { diff --git a/web/features/agent-v2/agent-composer/conversions.ts b/web/features/agent-v2/agent-composer/conversions.ts index 4b79f162fce..abc2b698769 100644 --- a/web/features/agent-v2/agent-composer/conversions.ts +++ b/web/features/agent-v2/agent-composer/conversions.ts @@ -197,8 +197,21 @@ const toToolRuntimeParameters = (settings: Record | undefined) return runtimeParameters } +const getDifyToolProviderId = (tool: AgentSoulDifyToolConfig) => + tool.provider_id ?? + (tool.plugin_id && tool.provider + ? `${tool.plugin_id}/${tool.provider}` + : (tool.provider ?? tool.plugin_id ?? '')) + +const getDifyToolProviderName = (tool: AgentSoulDifyToolConfig) => { + if (tool.provider) return tool.provider + + const providerIdSegments = getDifyToolProviderId(tool).split('/').filter(Boolean) + return providerIdSegments.at(-1) ?? '' +} + const getDifyToolActionId = (tool: AgentSoulDifyToolConfig) => - `${tool.provider_id ?? tool.provider ?? tool.plugin_id ?? 'provider'}:${tool.tool_name ?? tool.name ?? 'tool'}` + `${getDifyToolProviderId(tool) || 'provider'}:${tool.tool_name ?? tool.name ?? 'tool'}` const toCredentialVariant = (tool: AgentSoulDifyToolConfig) => { const credentialType = tool.credential_type @@ -227,7 +240,7 @@ const toProviderToolFormState = ( const toolSettings: AgentSoulConfigFormState['toolSettings'] = {} for (const tool of config?.tools?.dify_tools ?? []) { - const providerId = tool.provider_id ?? tool.provider ?? tool.plugin_id ?? '' + const providerId = getDifyToolProviderId(tool) const toolName = tool.tool_name ?? tool.name ?? '' if (!providerId || !toolName) continue @@ -249,8 +262,9 @@ const toProviderToolFormState = ( toolByProviderId.set(providerId, { id: providerId, - name: tool.provider ?? providerId, + name: getDifyToolProviderName(tool), kind: 'provider', + pluginId: tool.plugin_id ?? undefined, iconClassName: 'i-custom-public-other-default-tool-icon text-text-tertiary', providerType: tool.provider_type, allowDelete: @@ -287,6 +301,7 @@ const toDifyToolConfigs = ( enabled: true, provider: tool.name, provider_id: tool.id, + plugin_id: tool.pluginId, provider_type: tool.providerType, tool_name: action.toolName, runtime_parameters: toToolRuntimeParameters(toolSettings[action.id]), diff --git a/web/features/agent-v2/agent-composer/form-state.ts b/web/features/agent-v2/agent-composer/form-state.ts index 76c6e7f3fd5..ad93897b302 100644 --- a/web/features/agent-v2/agent-composer/form-state.ts +++ b/web/features/agent-v2/agent-composer/form-state.ts @@ -93,6 +93,8 @@ type AgentProviderToolCredentialType = 'api-key' | 'oauth2' | 'unauthorized' export type AgentProviderTool = AgentToolBase & { kind: 'provider' displayName?: string + pluginId?: string + pluginUniqueIdentifier?: string iconClassName: string icon?: ToolDefaultValue['provider_icon'] iconDark?: ToolDefaultValue['provider_icon_dark'] diff --git a/web/features/agent-v2/agent-composer/store-modules/tools.ts b/web/features/agent-v2/agent-composer/store-modules/tools.ts index 9cbc98b3722..eaeeac348d3 100644 --- a/web/features/agent-v2/agent-composer/store-modules/tools.ts +++ b/web/features/agent-v2/agent-composer/store-modules/tools.ts @@ -88,6 +88,9 @@ export const addProviderTools = ( nextTools[existingToolIndex] = { ...existingTool, displayName: existingTool.displayName ?? selectedTool.provider_show_name, + pluginId: existingTool.pluginId ?? selectedTool.plugin_id, + pluginUniqueIdentifier: + existingTool.pluginUniqueIdentifier ?? selectedTool.plugin_unique_identifier, icon: existingTool.icon ?? selectedTool.provider_icon, iconDark: existingTool.iconDark ?? selectedTool.provider_icon_dark, allowDelete: existingTool.allowDelete ?? selectedTool.allowDelete, @@ -101,6 +104,8 @@ export const addProviderTools = ( name: selectedTool.provider_name, kind: 'provider', displayName: selectedTool.provider_show_name, + pluginId: selectedTool.plugin_id, + pluginUniqueIdentifier: selectedTool.plugin_unique_identifier, iconClassName: 'i-custom-public-other-default-tool-icon text-text-tertiary', icon: selectedTool.provider_icon, iconDark: selectedTool.provider_icon_dark, diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx index 6419ea4c7c1..48423b89a65 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/__tests__/index.spec.tsx @@ -2,7 +2,7 @@ import type { AddOAuthButtonProps, Credential } from '@/app/components/plugins/p import type { ToolWithProvider } from '@/app/components/workflow/types' import type { AgentSoulConfigFormState } from '@/features/agent-v2/agent-composer/form-state' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { act, cleanup, render, screen } from '@testing-library/react' +import { act, cleanup, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { createStore, Provider as JotaiProvider } from 'jotai' import { beforeEach, describe, expect, it, vi } from 'vitest' @@ -19,7 +19,7 @@ import { AgentOrchestrateReadOnlyContext } from '../../read-only-context' import { AgentTools } from '../index' const toolProviderState = vi.hoisted(() => ({ - builtInTools: [] as ToolWithProvider[], + builtInTools: [] as ToolWithProvider[] | undefined, })) const pluginAuthState = vi.hoisted(() => ({ canOAuth: true as boolean | undefined, @@ -28,6 +28,21 @@ const pluginAuthState = vi.hoisted(() => ({ notAllowCustomCredential: false, invalidPluginCredentialInfo: vi.fn(), })) +const pluginInstallState = vi.hoisted(() => ({ + manifest: undefined as + | { + label: Record + latest_package_identifier: string + } + | undefined, + fallbackManifest: undefined as + | { + latest_package_identifier: string + } + | undefined, + invalidateBuiltInTools: vi.fn(), + invalidateInstalledPluginList: vi.fn(), +})) vi.mock('@/app/components/workflow/block-selector/tool-picker', () => ({ ToolPickerContent: () =>
Mock tool picker
, @@ -48,6 +63,54 @@ vi.mock('@/app/components/workflow/block-icon', () => ({ ), })) +vi.mock('@/app/components/workflow/nodes/_base/components/install-plugin-button', () => ({ + InstallPluginButton: ({ + uniqueIdentifier, + onSuccess, + }: { + uniqueIdentifier: string + onSuccess?: () => void + }) => ( + + ), +})) + +vi.mock('@/service/use-plugins', () => ({ + useInvalidateInstalledPluginList: () => pluginInstallState.invalidateInstalledPluginList, + useFetchPluginsInMarketPlaceByInfo: (infos: Array<{ organization: string; plugin: string }>) => ({ + data: + infos.length > 0 && pluginInstallState.manifest + ? { + data: { + list: infos.map(({ organization, plugin }) => ({ + plugin: { + ...pluginInstallState.manifest, + name: plugin, + plugin_id: `${organization}/${plugin}`, + }, + })), + }, + } + : undefined, + }), + usePluginManifestInfo: (pluginId: string) => ({ + data: + pluginId && pluginInstallState.fallbackManifest + ? { + data: { + plugin: pluginInstallState.fallbackManifest, + }, + } + : undefined, + }), +})) + +vi.mock('@/utils/get-icon', () => ({ + getIconFromMarketPlace: (pluginId: string) => `https://marketplace.example.com/${pluginId}/icon`, +})) + vi.mock('@/app/components/plugins/plugin-auth/authorize/add-oauth-button', () => ({ default: ({ buttonText, onUpdate, renderTrigger }: AddOAuthButtonProps) => { if (renderTrigger) { @@ -99,6 +162,7 @@ vi.mock('@/service/use-tools', () => ({ useAllCustomTools: () => ({ data: [] }), useAllWorkflowTools: () => ({ data: [] }), useAllMCPTools: () => ({ data: [] }), + useInvalidateAllBuiltInTools: () => pluginInstallState.invalidateBuiltInTools, useInvalidToolsByType: () => vi.fn(), })) @@ -181,6 +245,30 @@ const reflectedUnauthorizedNoCredentialDraft = { ], } satisfies AgentSoulConfigFormState +const reflectedUninstalledPluginDraft = { + ...defaultAgentSoulConfigFormState, + tools: [ + { + id: 'langgenius/google/google', + kind: 'provider', + name: 'langgenius/google/google', + pluginId: 'langgenius/google', + iconClassName: 'i-custom-public-other-default-tool-icon', + providerType: 'plugin', + credentialType: 'unauthorized', + credentialVariant: 'unauthorized', + actions: [ + { + id: 'langgenius/google/google:search', + name: 'search', + toolName: 'search', + description: '', + }, + ], + }, + ], +} satisfies AgentSoulConfigFormState + const reflectedUnauthorizedOAuthCredentialTypeDraft = { ...defaultAgentSoulConfigFormState, tools: [ @@ -369,6 +457,10 @@ describe('AgentTools', () => { pluginAuthState.canApiKey = false pluginAuthState.credentials = [] pluginAuthState.notAllowCustomCredential = false + pluginInstallState.manifest = undefined + pluginInstallState.fallbackManifest = undefined + pluginInstallState.invalidateBuiltInTools.mockResolvedValue(undefined) + pluginInstallState.invalidateInstalledPluginList.mockResolvedValue(undefined) }) describe('User Interactions', () => { @@ -528,6 +620,77 @@ describe('AgentTools', () => { expect(screen.getByText('Google Search')).toBeInTheDocument() }) + it('should let users install a missing provider and show its marketplace icon', async () => { + const user = userEvent.setup() + pluginInstallState.manifest = { + label: { + en_US: 'Google Tools', + }, + latest_package_identifier: 'langgenius/google:1.0.0@checksum', + } + renderAgentTools(reflectedUninstalledPluginDraft) + + expect(screen.getByRole('button', { name: 'Google Tools' })).toBeInTheDocument() + expect( + screen.getByText('https://marketplace.example.com/langgenius/google/icon'), + ).toBeInTheDocument() + expect( + screen.queryByRole('button', { + name: 'tools.notAuthorized', + }), + ).not.toBeInTheDocument() + + const installButton = screen.getByRole('button', { + name: 'workflow.nodes.agent.pluginInstaller.install', + }) + expect(installButton).toHaveAttribute( + 'data-unique-identifier', + 'langgenius/google:1.0.0@checksum', + ) + + await user.click(installButton) + + await waitFor(() => { + expect(pluginInstallState.invalidateBuiltInTools).toHaveBeenCalledTimes(1) + expect(pluginInstallState.invalidateInstalledPluginList).toHaveBeenCalledTimes(1) + }) + }) + + it('should keep install actionable when batch marketplace metadata is unavailable', () => { + pluginInstallState.fallbackManifest = { + latest_package_identifier: 'langgenius/google:0.0.1@fallback', + } + renderAgentTools(reflectedUninstalledPluginDraft) + + expect( + screen.getByRole('button', { + name: 'google', + }), + ).toBeInTheDocument() + expect( + screen.getByText('https://marketplace.example.com/langgenius/google/icon'), + ).toBeInTheDocument() + expect( + screen.getByRole('button', { + name: 'workflow.nodes.agent.pluginInstaller.install', + }), + ).toHaveAttribute('data-unique-identifier', 'langgenius/google:0.0.1@fallback') + }) + + it('should wait for the provider catalog before showing an uninstalled status', () => { + toolProviderState.builtInTools = undefined + renderAgentTools(reflectedUnauthorizedNoCredentialDraft) + + expect( + screen.queryByText('plugin.detailPanel.toolSelector.uninstalledTitle'), + ).not.toBeInTheDocument() + expect( + screen.queryByRole('button', { + name: 'tools.notAuthorized', + }), + ).not.toBeInTheDocument() + }) + it('should hide unauthorized status when reflected provider tools do not require credentials', () => { toolProviderState.builtInTools = [duckDuckGoProvider] renderAgentTools(reflectedUnauthorizedNoCredentialDraft) diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/index.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/index.tsx index 52d1f11025c..ad1a9b3cd2f 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/index.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/index.tsx @@ -1,5 +1,6 @@ 'use client' +import type { MarketplacePlugin } from '@dify/contracts/marketplace' import type { AgentOrchestrateAddActionOptions } from '../add-actions-context' import type { ToolSettingTarget } from './types' import type { ToolDefaultValue, ToolValue } from '@/app/components/workflow/block-selector/types' @@ -25,12 +26,18 @@ import { setProviderToolCredentialAtom, } from '@/features/agent-v2/agent-composer/store-modules/tools' import { ENABLE_AGENT_CLI_TOOLS } from '@/features/agent-v2/agent-detail/configure/feature-flags' +import { + useFetchPluginsInMarketPlaceByInfo, + useInvalidateInstalledPluginList, +} from '@/service/use-plugins' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools, + useInvalidateAllBuiltInTools, } from '@/service/use-tools' +import { getIconFromMarketPlace } from '@/utils/get-icon' import { useRegisterAgentOrchestrateAddAction } from '../add-actions-context' import { ConfigureSectionAddButton } from '../common/add-button' import { ConfigureSectionEmpty } from '../common/empty' @@ -47,6 +54,12 @@ import { import { ProviderToolSettingsDialog } from './provider-tool/dialog' import { AgentProviderToolItem } from './provider-tool/item' +type DisplayAgentProviderTool = AgentProviderTool & { + isInstalled?: boolean +} + +type DisplayAgentTool = AgentCliTool | DisplayAgentProviderTool + const AgentToolItem = memo( ({ tool, @@ -56,8 +69,9 @@ const AgentToolItem = memo( onDeleteProviderToolAction, onEditCliTool, onCredentialChange, + onPluginInstalled, }: { - tool: AgentTool + tool: DisplayAgentTool onConfigureAction: (target: ToolSettingTarget) => void onDeleteCliTool: (toolId: string) => void onDeleteProviderTool: (toolId: string) => void @@ -68,6 +82,7 @@ const AgentToolItem = memo( credentialId?: string, credentialType?: AgentProviderTool['credentialType'], ) => void + onPluginInstalled: () => void }) => { const [isExpanded, setIsExpanded] = useState(false) @@ -101,12 +116,14 @@ const AgentToolItem = memo( return ( ) } @@ -125,6 +142,7 @@ function useAgentToolProviderMap() { return useMemo(() => { const providers = new Map() + const resolvedProviderTypes = new Set() const buildInToolList = Array.isArray(buildInTools) ? buildInTools : [] const customToolList = Array.isArray(customTools) ? customTools : [] const workflowToolList = Array.isArray(workflowTools) ? workflowTools : [] @@ -136,6 +154,14 @@ function useAgentToolProviderMap() { ...mcpToolList, ] + if (Array.isArray(buildInTools)) { + resolvedProviderTypes.add(CollectionType.builtIn) + resolvedProviderTypes.add('plugin') + } + if (Array.isArray(customTools)) resolvedProviderTypes.add(CollectionType.custom) + if (Array.isArray(workflowTools)) resolvedProviderTypes.add(CollectionType.workflow) + if (Array.isArray(mcpTools)) resolvedProviderTypes.add(CollectionType.mcp) + allProviders.forEach((provider) => { providers.set(provider.id, provider) providers.set(provider.name, provider) @@ -145,14 +171,43 @@ function useAgentToolProviderMap() { } }) - return providers + return { + providerById: providers, + resolvedProviderTypes, + } }, [buildInTools, customTools, workflowTools, mcpTools]) } -function getLocalizedText(text: Record | undefined, language: string) { +function getLocalizedText(text: Partial> | undefined, language: string) { return text?.[language] ?? text?.en_US ?? text?.zh_Hans } +function getProviderPluginId(tool: AgentProviderTool) { + if (tool.pluginId) return tool.pluginId + + if (tool.providerType !== 'plugin' && tool.providerType !== CollectionType.builtIn) return '' + + const providerIdSegments = tool.id.split('/') + if (providerIdSegments.length !== 3) return '' + + return providerIdSegments.slice(0, 2).join('/') +} + +function getProviderDisplayName(tool: AgentProviderTool) { + const providerIdSegments = tool.name.split('/').filter(Boolean) + return providerIdSegments.at(-1) ?? tool.name +} + +function getMarketplacePluginInfo(pluginId: string) { + const [organization, plugin, ...remainingSegments] = pluginId.split('/') + if (!organization || !plugin || remainingSegments.length > 0) return undefined + + return { + organization, + plugin, + } +} + function getProviderCredentialType( provider?: ToolWithProvider, ): AgentProviderTool['credentialType'] { @@ -191,16 +246,42 @@ function getProviderCredentialVariant( : ('unauthorized' as const) } -function useDisplayTools(tools: AgentTool[], providerById: Map) { +function useDisplayTools( + tools: AgentTool[], + providerById: Map, + resolvedProviderTypes: Set, + marketplacePluginById: Map, +) { const language = useGetLanguage() return useMemo(() => { - return tools.map((tool) => { + return tools.map((tool): DisplayAgentTool => { if (tool.kind !== 'provider') return tool const provider = providerById.get(tool.id) ?? providerById.get(tool.name) - if (!provider) return tool + if (!provider) { + const providerPluginId = getProviderPluginId(tool) + const marketplacePlugin = marketplacePluginById.get(providerPluginId) + + return { + ...tool, + isInstalled: resolvedProviderTypes.has(tool.providerType) ? false : undefined, + pluginId: tool.pluginId ?? providerPluginId, + pluginUniqueIdentifier: + tool.pluginUniqueIdentifier ?? marketplacePlugin?.latest_package_identifier, + displayName: + tool.displayName ?? + getLocalizedText(marketplacePlugin?.label ?? marketplacePlugin?.labels, language) ?? + marketplacePlugin?.name ?? + getProviderDisplayName(tool), + icon: + tool.icon ?? + (marketplacePlugin && providerPluginId + ? getIconFromMarketPlace(providerPluginId) + : undefined), + } + } const providerToolByName = new Map( provider.tools.map((providerTool) => [providerTool.name, providerTool]), @@ -209,6 +290,7 @@ function useDisplayTools(tools: AgentTool[], providerById: Map(addToolDefaultView) - const providerById = useAgentToolProviderMap() + const { providerById } = useAgentToolProviderMap() const openToolPicker = useCallback(() => { setView('tool-picker') @@ -402,7 +484,9 @@ export function AgentTools() { const { t } = useTranslation('agentV2') const readOnly = useAgentOrchestrateReadOnly() const setProviderToolCredential = useSetAtom(setProviderToolCredentialAtom) - const providerById = useAgentToolProviderMap() + const invalidateAllBuiltInTools = useInvalidateAllBuiltInTools() + const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + const { providerById, resolvedProviderTypes } = useAgentToolProviderMap() const tools = useAtomValue(agentComposerToolsAtom) const selectedTools = useSelectedProviderTools() const addTools = useSetAtom(addProviderToolsAtom) @@ -432,11 +516,53 @@ export function AgentTools() { }, [setProviderToolCredential], ) + const handlePluginInstalled = useCallback(() => { + void Promise.allSettled([invalidateAllBuiltInTools(), invalidateInstalledPluginList()]) + }, [invalidateAllBuiltInTools, invalidateInstalledPluginList]) const visibleTools = useMemo( () => (ENABLE_AGENT_CLI_TOOLS ? tools : tools.filter((tool) => tool.kind !== 'cli')), [tools], ) - const displayTools = useDisplayTools(visibleTools, providerById) + const missingMarketplacePluginInfos = useMemo(() => { + const pluginIds = new Set() + + visibleTools.forEach((tool) => { + if ( + tool.kind !== 'provider' || + !resolvedProviderTypes.has(tool.providerType) || + providerById.has(tool.id) || + providerById.has(tool.name) + ) + return + + const pluginId = getProviderPluginId(tool) + if (pluginId) pluginIds.add(pluginId) + }) + + return Array.from(pluginIds).flatMap((pluginId) => { + const info = getMarketplacePluginInfo(pluginId) + return info ? [info] : [] + }) + }, [providerById, resolvedProviderTypes, visibleTools]) + const { data: missingMarketplacePluginsData } = useFetchPluginsInMarketPlaceByInfo( + missingMarketplacePluginInfos, + ) + const marketplacePluginById = useMemo( + () => + new Map( + (missingMarketplacePluginsData?.data.list ?? []).map(({ plugin }) => [ + plugin.plugin_id, + plugin, + ]), + ), + [missingMarketplacePluginsData], + ) + const displayTools = useDisplayTools( + visibleTools, + providerById, + resolvedProviderTypes, + marketplacePluginById, + ) /* * knip-ignore-start * Keep this disabled sync logic while backend credential snapshots are being investigated. @@ -558,6 +684,7 @@ export function AgentTools() { onDeleteProviderToolAction={deleteProviderToolAction} onEditCliTool={editCliTool} onCredentialChange={handleProviderCredentialChange} + onPluginInstalled={handlePluginInstalled} /> )) )} diff --git a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/provider-tool/item.tsx b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/provider-tool/item.tsx index ab60f638d66..bd083c85302 100644 --- a/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/provider-tool/item.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/orchestrate/tools/provider-tool/item.tsx @@ -21,9 +21,12 @@ import { AuthCategory, Authorized, usePluginAuth } from '@/app/components/plugin import AuthorizedInNode from '@/app/components/plugins/plugin-auth/authorized-in-node' import { CollectionType } from '@/app/components/tools/types' import BlockIcon from '@/app/components/workflow/block-icon' +import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' import { BlockEnum } from '@/app/components/workflow/types' import useTheme from '@/hooks/use-theme' +import { usePluginManifestInfo } from '@/service/use-plugins' import { Theme } from '@/types/app' +import { getIconFromMarketPlace } from '@/utils/get-icon' import { useAgentOrchestrateReadOnly } from '../../read-only-context' function ProviderIcon({ @@ -119,6 +122,36 @@ function UnauthorizedCredentialStatus({ ) } +function UninstalledPluginStatus({ + installInfo, + extraIdentifiers, + onInstall, +}: { + installInfo?: string + extraIdentifiers: string[] + onInstall: () => void +}) { + const { t } = useTranslation() + + if (installInfo) { + return ( + + ) + } + + return ( + + {t(($) => $['detailPanel.toolSelector.uninstalledTitle'], { ns: 'plugin' })} + + + ) +} + function CredentialStatus({ tool, onCredentialChange, @@ -231,19 +264,23 @@ const ProviderToolActionItem = memo( export const AgentProviderToolItem = memo( ({ tool, + isInstalled, isExpanded, onOpenChange, onConfigureAction, onRemoveAction, onRemoveProvider, onCredentialChange, + onInstall, }: { tool: AgentProviderTool + isInstalled?: boolean isExpanded: boolean onOpenChange: (open: boolean) => void onConfigureAction: (target: ToolSettingTarget) => void onRemoveAction: (actionId: string) => void onRemoveProvider: () => void + onInstall: () => void onCredentialChange: ( credentialId?: string, credentialType?: AgentProviderTool['credentialType'], @@ -252,7 +289,20 @@ export const AgentProviderToolItem = memo( const { t } = useTranslation('agentV2') const readOnly = useAgentOrchestrateReadOnly() const { theme } = useTheme() - const icon = theme === Theme.dark && tool.iconDark ? tool.iconDark : tool.icon + const shouldFetchPluginManifest = + isInstalled === false && !!tool.pluginId && !tool.pluginUniqueIdentifier + const { data: pluginManifestData } = usePluginManifestInfo( + shouldFetchPluginManifest ? tool.pluginId! : '', + ) + const pluginManifest = pluginManifestData?.data.plugin + const configuredIcon = theme === Theme.dark && tool.iconDark ? tool.iconDark : tool.icon + const icon = + configuredIcon ?? + (pluginManifest && tool.pluginId ? getIconFromMarketPlace(tool.pluginId) : undefined) + const installInfo = tool.pluginUniqueIdentifier ?? pluginManifest?.latest_package_identifier + const installIdentifiers = [tool.pluginId, tool.id].filter((identifier): identifier is string => + Boolean(identifier), + ) const displayName = tool.displayName ?? tool.name return ( @@ -277,32 +327,41 @@ export const AgentProviderToolItem = memo( {!readOnly && ( - <> - - $['agentDetail.configure.tools.moreActions'], { - name: tool.name, - })} - className="flex size-6 shrink-0 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:bg-state-base-hover" + + $['agentDetail.configure.tools.moreActions'], { + name: tool.name, + })} + className="flex size-6 shrink-0 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary focus-visible:ring-2 focus-visible:ring-state-accent-solid focus-visible:outline-hidden data-popup-open:bg-state-base-hover" + > + + {t(($) => $['agentDetail.configure.tools.moreActions'], { name: tool.name })} + + + + + - - {t(($) => $['agentDetail.configure.tools.moreActions'], { name: tool.name })} - - - - - - - {t(($) => $['agentDetail.configure.tools.removeProvider'])} - - - - - + + {t(($) => $['agentDetail.configure.tools.removeProvider'])} + + + + )} + {isInstalled === false && ( +
+ +
+ )} + {!readOnly && isInstalled === true && ( + )} From 04158ac8ea8e57c109d7df5f325b54121ad36b2d Mon Sep 17 00:00:00 2001 From: Escape0707 Date: Tue, 28 Jul 2026 17:52:12 +0900 Subject: [PATCH 03/17] ci: replace custom noqa markers with guard ignores (#39679) --- api/controllers/common/session.py | 2 +- api/controllers/console/app/workflow.py | 2 +- api/controllers/console/datasets/datasets.py | 4 +- api/controllers/console/datasets/external.py | 2 +- api/controllers/console/explore/trial.py | 2 +- api/fields/conversation_fields.py | 2 +- api/fields/dataset_fields.py | 2 +- api/fields/document_fields.py | 2 +- api/services/app_service.py | 4 +- .../commands/test_check_no_new_getattr.py | 48 ++++++++++++++++--- dify-agent/src/shellctl/__init__.py | 2 +- dify-agent/src/shellctl/shared/__init__.py | 2 +- scripts/ast_grep_guard.py | 6 +-- scripts/check_no_new_controller_sqlalchemy.py | 4 +- scripts/check_no_new_getattr.py | 4 +- scripts/lint_controller_sqlalchemy.py | 2 +- 16 files changed, 63 insertions(+), 27 deletions(-) diff --git a/api/controllers/common/session.py b/api/controllers/common/session.py index 24b1a8729d3..fdffa46b189 100644 --- a/api/controllers/common/session.py +++ b/api/controllers/common/session.py @@ -52,7 +52,7 @@ def with_session[T, **P, R]( session.commit() return result except Exception: - session.rollback() # noqa: no-new-controller-sqlalchemy decorator owns transaction rollback + session.rollback() # guard-ignore: no-new-controller-sqlalchemy -- decorator owns rollback raise with session_factory.create_session() as session: diff --git a/api/controllers/console/app/workflow.py b/api/controllers/console/app/workflow.py index 6025d02fe39..f934f440b0e 100644 --- a/api/controllers/console/app/workflow.py +++ b/api/controllers/console/app/workflow.py @@ -317,7 +317,7 @@ class _WorkflowResponseSource: self._session = session def __getattr__(self, name: str) -> object: - return getattr(self._workflow, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self._workflow, name) # guard-ignore: no-new-getattr -- delegates model fields @property def created_by_account(self) -> Account | None: diff --git a/api/controllers/console/datasets/datasets.py b/api/controllers/console/datasets/datasets.py index 2ec5a9fe103..19a6c4dc0dc 100644 --- a/api/controllers/console/datasets/datasets.py +++ b/api/controllers/console/datasets/datasets.py @@ -218,7 +218,7 @@ class _DatasetQueryResponseSource: return self.query.get_queries(session=self.session) def __getattr__(self, name: str) -> Any: - return getattr(self.query, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self.query, name) # guard-ignore: no-new-getattr -- delegates model fields class DatasetQueryListResponse(ResponseModel): @@ -257,7 +257,7 @@ class _RelatedAppResponseSource: return self.app.mode_compatible_with_agent_with_session(session=self.session) def __getattr__(self, name: str) -> Any: - return getattr(self.app, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self.app, name) # guard-ignore: no-new-getattr -- delegates model fields class RelatedAppListResponse(ResponseModel): diff --git a/api/controllers/console/datasets/external.py b/api/controllers/console/datasets/external.py index 94efe388561..55867e9cbbb 100644 --- a/api/controllers/console/datasets/external.py +++ b/api/controllers/console/datasets/external.py @@ -106,7 +106,7 @@ class ExternalKnowledgeApiResponseSource: return self.external_knowledge_api.get_dataset_bindings(session=self.session) def __getattr__(self, name: str) -> Any: - return getattr(self.external_knowledge_api, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self.external_knowledge_api, name) # guard-ignore: no-new-getattr -- delegates model fields def external_knowledge_api_response( diff --git a/api/controllers/console/explore/trial.py b/api/controllers/console/explore/trial.py index 553e65202ce..d0823f178cb 100644 --- a/api/controllers/console/explore/trial.py +++ b/api/controllers/console/explore/trial.py @@ -404,7 +404,7 @@ class TrialWorkflowResponseSource: return self.workflow.get_tool_published(session=self.session) def __getattr__(self, name: str) -> Any: - return getattr(self.workflow, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self.workflow, name) # guard-ignore: no-new-getattr -- delegates model fields register_schema_models( diff --git a/api/fields/conversation_fields.py b/api/fields/conversation_fields.py index 073305d2dd9..7612d09f58d 100644 --- a/api/fields/conversation_fields.py +++ b/api/fields/conversation_fields.py @@ -34,7 +34,7 @@ class _SessionResponseSource[SourceT]: self._session = session def __getattr__(self, name: str) -> object: - return getattr(self._source, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self._source, name) # guard-ignore: no-new-getattr -- delegates model fields class _FeedbackResponseSource(_SessionResponseSource[MessageFeedback]): diff --git a/api/fields/dataset_fields.py b/api/fields/dataset_fields.py index 4846aa9689c..c81fb79df3f 100644 --- a/api/fields/dataset_fields.py +++ b/api/fields/dataset_fields.py @@ -227,7 +227,7 @@ class DatasetDetailResponseSource: return self.dataset.get_total_available_documents(session=self.session) def __getattr__(self, name: str) -> Any: - return getattr(self.dataset, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self.dataset, name) # guard-ignore: no-new-getattr -- delegates model fields def dataset_detail_response_source(dataset: Any, *, session: Session) -> DatasetDetailResponseSource: diff --git a/api/fields/document_fields.py b/api/fields/document_fields.py index aa3b4135ec6..16cc49541b7 100644 --- a/api/fields/document_fields.py +++ b/api/fields/document_fields.py @@ -90,7 +90,7 @@ class DocumentWithSession: return self.document.get_doc_metadata_details(session=self.session) def __getattr__(self, name: str) -> Any: - return getattr(self.document, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self.document, name) # guard-ignore: no-new-getattr -- delegates model fields def document_response(document: Document, *, session: Session) -> DocumentResponse: diff --git a/api/services/app_service.py b/api/services/app_service.py index a858163e2fb..6faa88eb114 100644 --- a/api/services/app_service.py +++ b/api/services/app_service.py @@ -114,7 +114,7 @@ class AppModelConfigResponseView: self._session = session def __getattr__(self, name: str) -> Any: - return getattr(self._app_model_config, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self._app_model_config, name) # guard-ignore: no-new-getattr -- delegates model fields @property def annotation_reply_dict(self) -> Any: @@ -129,7 +129,7 @@ class AppResponseView: self._session = session def __getattr__(self, name: str) -> Any: - return getattr(self._app, name) # noqa: no-new-getattr response adapter delegates model fields + return getattr(self._app, name) # guard-ignore: no-new-getattr -- delegates model fields @property def desc_or_prompt(self) -> str: diff --git a/api/tests/unit_tests/commands/test_check_no_new_getattr.py b/api/tests/unit_tests/commands/test_check_no_new_getattr.py index 4efcb37da92..a63569c706f 100644 --- a/api/tests/unit_tests/commands/test_check_no_new_getattr.py +++ b/api/tests/unit_tests/commands/test_check_no_new_getattr.py @@ -85,6 +85,42 @@ def main_branch_rev(repo: Path) -> str: return git(repo, "rev-parse", "main") +@pytest.mark.parametrize( + ("source_line", "rule_id"), + [ + ( + "value = getattr(module, name) # guard-ignore: no-new-getattr -- lazy export proxy", + "no-new-getattr", + ), + ( + "session.rollback() # guard-ignore: no-new-controller-sqlalchemy -- decorator owns rollback", + "no-new-controller-sqlalchemy", + ), + ], +) +def test_has_reasoned_guard_ignore_accepts_custom_rules(source_line: str, rule_id: str) -> None: + module = load_guard_module() + + assert module.has_reasoned_guard_ignore(source_line, rule_id) + + +@pytest.mark.parametrize( + ("source_line", "rule_id"), + [ + ("value = getattr(module, name) # noqa: no-new-getattr legacy marker", "no-new-getattr"), + ("value = getattr(module, name) # guard-ignore: no-new-getattr", "no-new-getattr"), + ( + "value = getattr(module, name) # guard-ignore: another-rule -- wrong rule", + "no-new-getattr", + ), + ], +) +def test_has_reasoned_guard_ignore_rejects_invalid_markers(source_line: str, rule_id: str) -> None: + module = load_guard_module() + + assert not module.has_reasoned_guard_ignore(source_line, rule_id) + + def test_resolve_ast_grep_command_prefers_ast_grep(monkeypatch: pytest.MonkeyPatch) -> None: module = load_guard_module() monkeypatch.setattr( @@ -776,7 +812,7 @@ def test_modified_hunk_with_increased_getattr_count_fails(tmp_path: Path) -> Non assert "net-new getattr" in result.stderr -def test_inline_noqa_suppression_with_explanatory_text_skips_added_getattr(tmp_path: Path) -> None: +def test_inline_guard_ignore_with_explanatory_text_skips_added_getattr(tmp_path: Path) -> None: init_repo(tmp_path) write_repo_file( tmp_path, @@ -795,20 +831,20 @@ def test_inline_noqa_suppression_with_explanatory_text_skips_added_getattr(tmp_p "pkg/existing.py", """ def read_value(obj): - return getattr(obj, "dynamic_name", None) # noqa: no-new-getattr needed for plugin-defined attributes + return getattr(obj, "dynamic_name", None) # guard-ignore: no-new-getattr -- plugin-defined attributes """, ) commit_all(tmp_path, "add suppressed getattr") result = run_script(tmp_path, "--base-rev", base_rev) - assert "no-new-getattr needed for plugin-defined attributes" in (tmp_path / "pkg/existing.py").read_text( + assert "guard-ignore: no-new-getattr -- plugin-defined attributes" in (tmp_path / "pkg/existing.py").read_text( encoding="utf-8" ) assert result.returncode == 0, stderr_lines(result) -def test_inline_noqa_without_explanatory_text_is_not_sufficient(tmp_path: Path) -> None: +def test_inline_guard_ignore_without_explanatory_text_is_not_sufficient(tmp_path: Path) -> None: init_repo(tmp_path) write_repo_file( tmp_path, @@ -827,10 +863,10 @@ def test_inline_noqa_without_explanatory_text_is_not_sufficient(tmp_path: Path) "pkg/existing.py", """ def read_value(obj): - return getattr(obj, "dynamic_name", None) # noqa: no-new-getattr + return getattr(obj, "dynamic_name", None) # guard-ignore: no-new-getattr """, ) - commit_all(tmp_path, "add bare noqa getattr") + commit_all(tmp_path, "add bare guard ignore getattr") result = run_script(tmp_path, "--base-rev", base_rev) diff --git a/dify-agent/src/shellctl/__init__.py b/dify-agent/src/shellctl/__init__.py index 3f2237dc690..cb294b365cf 100644 --- a/dify-agent/src/shellctl/__init__.py +++ b/dify-agent/src/shellctl/__init__.py @@ -115,7 +115,7 @@ def __getattr__(name: str) -> Any: if name not in _EXPORTS: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") module = import_module(_EXPORTS[name]) - value = getattr(module, name) # noqa: no-new-getattr lazy export proxy + value = getattr(module, name) # guard-ignore: no-new-getattr -- lazy export proxy globals()[name] = value return value diff --git a/dify-agent/src/shellctl/shared/__init__.py b/dify-agent/src/shellctl/shared/__init__.py index 5f235b19d9f..83419e4e691 100644 --- a/dify-agent/src/shellctl/shared/__init__.py +++ b/dify-agent/src/shellctl/shared/__init__.py @@ -173,7 +173,7 @@ def __getattr__(name: str) -> Any: if name not in _EXPORTS: raise AttributeError(f"module {__name__!r} has no attribute {name!r}") module = import_module(_EXPORTS[name]) - value = getattr(module, name) # noqa: no-new-getattr lazy export proxy + value = getattr(module, name) # guard-ignore: no-new-getattr -- lazy export proxy globals()[name] = value return value diff --git a/scripts/ast_grep_guard.py b/scripts/ast_grep_guard.py index 3e46486f24e..9f67bfd6d08 100644 --- a/scripts/ast_grep_guard.py +++ b/scripts/ast_grep_guard.py @@ -245,13 +245,13 @@ def extract_meta_variables(raw_match: dict[str, Any]) -> dict[str, str]: return result -def has_reasoned_noqa(source_line: str, rule_id: str) -> bool: - pattern = re.compile(rf"# noqa: {re.escape(rule_id)}(?:\s+(?P\S.*))?\s*$") +def has_reasoned_guard_ignore(source_line: str, rule_id: str) -> bool: + pattern = re.compile(rf"# guard-ignore: {re.escape(rule_id)} -- (?P\S.*)\s*$") match = pattern.search(source_line) if not match: return False reason = match.group("reason") - return reason is not None and bool(reason.strip()) + return bool(reason.strip()) def collect_hunk_violations( diff --git a/scripts/check_no_new_controller_sqlalchemy.py b/scripts/check_no_new_controller_sqlalchemy.py index bf1bea336b7..3812ce8f762 100644 --- a/scripts/check_no_new_controller_sqlalchemy.py +++ b/scripts/check_no_new_controller_sqlalchemy.py @@ -10,7 +10,7 @@ from __future__ import annotations import re from pathlib import Path -from ast_grep_guard import Match, has_reasoned_noqa, rule_path, run_guard +from ast_grep_guard import Match, has_reasoned_guard_ignore, rule_path, run_guard RULE_ID = "no-new-controller-sqlalchemy" @@ -40,7 +40,7 @@ def is_flask_session_get(match: Match) -> bool: def is_suppressed(match: Match) -> bool: - return has_reasoned_noqa(match.source_line, RULE_ID) + return has_reasoned_guard_ignore(match.source_line, RULE_ID) def is_reportable_match(match: Match) -> bool: diff --git a/scripts/check_no_new_getattr.py b/scripts/check_no_new_getattr.py index e788395c62c..9c2248ecf2a 100644 --- a/scripts/check_no_new_getattr.py +++ b/scripts/check_no_new_getattr.py @@ -3,7 +3,7 @@ from __future__ import annotations -from ast_grep_guard import Match, has_reasoned_noqa, is_python_source_path, rule_path, run_guard +from ast_grep_guard import Match, has_reasoned_guard_ignore, is_python_source_path, rule_path, run_guard RULE_ID = "no-new-getattr" @@ -12,7 +12,7 @@ VIOLATION_MESSAGE = "no-new-getattr net-new getattr() in added code" def is_reportable_match(match: Match) -> bool: - return not has_reasoned_noqa(match.source_line, RULE_ID) + return not has_reasoned_guard_ignore(match.source_line, RULE_ID) def main() -> int: diff --git a/scripts/lint_controller_sqlalchemy.py b/scripts/lint_controller_sqlalchemy.py index 87c9582b17f..bef362a42b4 100644 --- a/scripts/lint_controller_sqlalchemy.py +++ b/scripts/lint_controller_sqlalchemy.py @@ -44,7 +44,7 @@ def parse_args() -> argparse.Namespace: help="Files or directories to scan. Defaults to api/controllers.", ) parser.add_argument("--include-allowed", action="store_true", help="Print allowed flush()/commit() findings.") - parser.add_argument("--include-suppressed", action="store_true", help="Print reasoned noqa suppressions.") + parser.add_argument("--include-suppressed", action="store_true", help="Print reasoned guard suppressions.") parser.add_argument("--summary-only", action="store_true", help="Print counts without per-finding details.") parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON.") parser.add_argument( From eef709e475ba641fd35eb213f0a1a4fb1e374191 Mon Sep 17 00:00:00 2001 From: Escape0707 Date: Tue, 28 Jul 2026 18:10:02 +0900 Subject: [PATCH 04/17] ci: enforce strict checks for unit tests (#39682) --- .../.ruff.toml | 184 ++-- .../pyrefly.toml | 42 +- api/tests/unit_tests/.ruff.toml | 430 ++++++++ api/tests/unit_tests/pyrefly.toml | 961 ++++++++++++++++++ dev/pyrefly-check-local | 14 + 5 files changed, 1493 insertions(+), 138 deletions(-) create mode 100644 api/tests/unit_tests/.ruff.toml create mode 100644 api/tests/unit_tests/pyrefly.toml diff --git a/api/tests/test_containers_integration_tests/.ruff.toml b/api/tests/test_containers_integration_tests/.ruff.toml index 54bc58a79ad..49244091ffb 100644 --- a/api/tests/test_containers_integration_tests/.ruff.toml +++ b/api/tests/test_containers_integration_tests/.ruff.toml @@ -2,114 +2,94 @@ extend = "../../.ruff.toml" src = ["../.."] [lint] -extend-select = ["ANN401", "ARG", "TID251"] +extend-select = ["ANN401", "ARG"] +# Existing strict-mode debt. Remove a file entry when bringing it under strict checking. [lint.per-file-ignores] -"core/rag/pipeline/test_queue_integration.py" = ["ANN401", "TID251", "ARG"] +"controllers/console/test_apikey.py" = ["ARG002"] +"controllers/openapi/test_app_dsl.py" = ["ARG002"] +"controllers/service_api/dataset/test_dataset.py" = ["ARG002"] +"controllers/web/test_conversation.py" = ["ARG002"] +"controllers/web/test_human_input_form.py" = ["ARG001"] +"controllers/web/test_wraps.py" = ["ARG002"] +"core/app/layers/test_pause_state_persist_layer.py" = ["ARG002"] +"core/rag/pipeline/test_queue_integration.py" = ["ARG002", "TID251"] +"core/rag/retrieval/test_dataset_retrieval_integration.py" = ["ARG002"] +"models/test_conversation_message_inputs.py" = ["ARG001"] "models/test_types_enum_text.py" = ["ANN401", "TID251"] -"services/test_app_dsl_service.py" = ["ANN401", "TID251", "ARG"] -"services/test_file_service_zip_and_lookup.py" = ["ANN401", "TID251", "ARG"] -"trigger/conftest.py" = ["ANN401", "TID251"] -"trigger/test_trigger_e2e.py" = ["ANN401", "TID251", "ARG"] -"controllers/console/app/test_app_apis.py" = ["ARG"] -"controllers/console/app/test_app_import_api.py" = ["ARG"] -"controllers/console/auth/test_oauth.py" = ["ARG"] -"controllers/console/auth/test_password_reset.py" = ["ARG"] -"controllers/console/datasets/test_data_source.py" = ["ARG"] -"controllers/console/test_apikey.py" = ["ARG"] -"controllers/console/workspace/test_tool_provider.py" = ["ARG"] -"controllers/mcp/test_mcp.py" = ["ARG"] -"controllers/openapi/test_app_dsl.py" = ["ARG"] -"controllers/openapi/test_workspaces.py" = ["ARG"] -"controllers/service_api/dataset/test_dataset.py" = ["ARG"] -"controllers/web/test_conversation.py" = ["ARG"] -"controllers/web/test_human_input_form.py" = ["ARG"] -"controllers/web/test_wraps.py" = ["ARG"] -"core/app/layers/test_pause_state_persist_layer.py" = ["ARG"] -"core/rag/retrieval/test_dataset_retrieval_integration.py" = ["ARG"] -"models/test_conversation_message_inputs.py" = ["ARG"] -"models/test_conversation_status_count.py" = ["ARG"] -"repositories/test_sqlalchemy_api_workflow_run_repository.py" = ["ARG"] -"repositories/test_workflow_run_repository.py" = ["ARG"] -"services/auth/test_api_key_auth_service.py" = ["ARG"] -"services/auth/test_auth_integration.py" = ["ARG"] -"services/dataset_collection_binding.py" = ["ARG"] -"services/dataset_service_update_delete.py" = ["ARG"] -"services/document_service_status.py" = ["ARG"] -"services/enterprise/test_account_deletion_sync.py" = ["ARG"] -"services/plugin/test_plugin_parameter_service.py" = ["ARG"] -"services/plugin/test_plugin_service.py" = ["ARG"] -"services/rag_pipeline/test_rag_pipeline_service_db.py" = ["ARG"] -"services/recommend_app/test_database_retrieval.py" = ["ARG"] -"services/test_account_service.py" = ["ARG"] -"services/test_advanced_prompt_template_service.py" = ["ARG"] -"services/test_annotation_service.py" = ["ARG"] -"services/test_api_based_extension_service.py" = ["ARG"] -"services/test_api_token_service.py" = ["ARG"] -"services/test_app_generate_service.py" = ["ARG"] -"services/test_app_service.py" = ["ARG"] -"services/test_attachment_service.py" = ["ARG"] -"services/test_conversation_variable_updater.py" = ["ARG"] -"services/test_dataset_permission_service.py" = ["ARG"] -"services/test_dataset_service_batch_update_document_status.py" = ["ARG"] -"services/test_dataset_service_retrieval.py" = ["ARG"] -"services/test_delete_archived_workflow_run.py" = ["ARG"] -"services/test_document_service_rename_document.py" = ["ARG"] -"services/test_end_user_service.py" = ["ARG"] -"services/test_feature_service.py" = ["ARG"] -"services/test_feedback_service.py" = ["ARG"] -"services/test_file_service.py" = ["ARG"] -"services/test_human_input_delivery_test_service.py" = ["ARG"] -"services/test_message_service.py" = ["ARG"] -"services/test_messages_clean_service.py" = ["ARG", "S110"] -"services/test_metadata_partial_update.py" = ["ARG"] -"services/test_metadata_service.py" = ["ARG"] -"services/test_model_load_balancing_service.py" = ["ARG"] -"services/test_model_provider_service.py" = ["ARG"] -"services/test_oauth_server_service.py" = ["ARG"] -"services/test_ops_service.py" = ["ARG"] -"services/test_saved_message_service.py" = ["ARG"] -"services/test_web_conversation_service.py" = ["ARG"] -"services/test_webapp_auth_service.py" = ["ARG"] -"services/test_webhook_service.py" = ["ARG"] -"services/test_workflow_app_service.py" = ["ARG"] -"services/test_workflow_draft_variable_service.py" = ["ARG"] -"services/test_workflow_run_service.py" = ["ARG"] -"services/test_workflow_service.py" = ["ARG"] -"services/test_workspace_service.py" = ["ARG"] -"services/tools/test_api_tools_manage_service.py" = ["ARG"] -"services/tools/test_mcp_tools_manage_service.py" = ["ARG"] -"services/tools/test_tools_transform_service.py" = ["ARG"] -"services/workflow/test_workflow_converter.py" = ["ARG"] -"tasks/test_add_document_to_index_task.py" = ["ARG"] -"tasks/test_batch_clean_document_task.py" = ["ARG"] -"tasks/test_batch_create_segment_to_index_task.py" = ["ARG"] +"repositories/test_sqlalchemy_api_workflow_run_repository.py" = ["ARG002", "ARG005"] +"repositories/test_workflow_run_repository.py" = ["ARG002"] +"services/auth/test_auth_integration.py" = ["ARG002"] +"services/dataset_collection_binding.py" = ["ARG002"] +"services/document_service_status.py" = ["ARG002"] +"services/rag_pipeline/test_rag_pipeline_service_db.py" = ["ARG002"] +"services/recommend_app/test_database_retrieval.py" = ["ARG002"] +"services/test_account_service.py" = ["ARG002"] +"services/test_advanced_prompt_template_service.py" = ["ARG002"] +"services/test_app_dsl_service.py" = ["ANN401", "ARG001", "ARG002", "ARG005", "TID251"] +"services/test_app_service.py" = ["ARG002"] +"services/test_attachment_service.py" = ["ARG002"] +"services/test_conversation_variable_updater.py" = ["ARG002"] +"services/test_dataset_service_batch_update_document_status.py" = ["ARG002"] +"services/test_delete_archived_workflow_run.py" = ["ARG002"] +"services/test_document_service_rename_document.py" = ["ARG001"] +"services/test_end_user_service.py" = ["ARG002"] +"services/test_feature_service.py" = ["ARG002"] +"services/test_file_service.py" = ["ARG002"] +"services/test_file_service_zip_and_lookup.py" = ["TID251"] +"services/test_messages_clean_service.py" = ["ARG002", "S110"] +"services/test_metadata_partial_update.py" = ["ARG002"] +"services/test_metadata_service.py" = ["ARG002"] +"services/test_model_load_balancing_service.py" = ["ARG002"] +"services/test_model_provider_service.py" = ["ARG002"] +"services/test_ops_service.py" = ["ARG002"] +"services/test_webapp_auth_service.py" = ["ARG002"] +"services/test_webhook_service.py" = ["ARG002"] +"services/test_workflow_draft_variable_service.py" = ["ARG002"] +"services/test_workflow_run_service.py" = ["ARG002"] +"services/test_workflow_service.py" = ["ARG002"] +"services/test_workspace_service.py" = ["ARG002"] +"services/tools/test_api_tools_manage_service.py" = ["ARG002"] +"services/tools/test_mcp_tools_manage_service.py" = ["ARG002", "ARG005"] +"services/tools/test_tools_transform_service.py" = ["ARG002"] +"services/workflow/test_workflow_converter.py" = ["ARG002"] +"tasks/test_add_document_to_index_task.py" = ["ARG002"] +"tasks/test_batch_clean_document_task.py" = ["ARG002"] +"tasks/test_batch_create_segment_to_index_task.py" = ["ARG001", "ARG002"] "tasks/test_clean_dataset_task.py" = ["T201"] -"tasks/test_clean_notion_document_task.py" = ["ARG"] -"tasks/test_create_segment_to_index_task.py" = ["ARG"] -"tasks/test_dataset_indexing_task.py" = ["ARG"] -"tasks/test_deal_dataset_vector_index_task.py" = ["ARG"] -"tasks/test_delete_segment_from_index_task.py" = ["ARG"] -"tasks/test_disable_segment_from_index_task.py" = ["ARG"] -"tasks/test_disable_segments_from_index_task.py" = ["ARG"] -"tasks/test_document_indexing_sync_task.py" = ["ARG"] -"tasks/test_document_indexing_task.py" = ["ARG"] -"tasks/test_document_indexing_update_task.py" = ["ARG"] -"tasks/test_duplicate_document_indexing_task.py" = ["ARG"] -"tasks/test_enable_segments_to_index_task.py" = ["ARG"] -"tasks/test_mail_change_mail_task.py" = ["ARG"] -"tasks/test_mail_email_code_login_task.py" = ["ARG"] -"tasks/test_mail_human_input_delivery_task.py" = ["ARG"] -"tasks/test_mail_inner_task.py" = ["ARG"] -"tasks/test_mail_invite_member_task.py" = ["ARG"] -"tasks/test_mail_owner_transfer_task.py" = ["ARG"] -"tasks/test_mail_register_task.py" = ["ARG"] -"tasks/test_rag_pipeline_run_tasks.py" = ["ARG"] +"tasks/test_clean_notion_document_task.py" = ["ARG002"] +"tasks/test_create_segment_to_index_task.py" = ["ARG002"] +"tasks/test_dataset_indexing_task.py" = ["ARG002"] +"tasks/test_deal_dataset_vector_index_task.py" = ["ARG002"] +"tasks/test_delete_segment_from_index_task.py" = ["ARG002"] +"tasks/test_disable_segment_from_index_task.py" = ["ARG002"] +"tasks/test_disable_segments_from_index_task.py" = ["ARG002"] +"tasks/test_document_indexing_sync_task.py" = ["ARG002"] +"tasks/test_document_indexing_task.py" = ["ARG002"] +"tasks/test_document_indexing_update_task.py" = ["ARG002"] +"tasks/test_duplicate_document_indexing_task.py" = ["ARG002"] +"tasks/test_enable_segments_to_index_task.py" = ["ARG002"] +"tasks/test_mail_change_mail_task.py" = ["ARG002"] +"tasks/test_mail_email_code_login_task.py" = ["ARG002"] +"tasks/test_mail_human_input_delivery_task.py" = ["ARG001"] +"tasks/test_mail_inner_task.py" = ["ARG002"] +"tasks/test_mail_invite_member_task.py" = ["ARG002"] +"tasks/test_mail_owner_transfer_task.py" = ["ARG002"] +"tasks/test_mail_register_task.py" = ["ARG002"] +"tasks/test_rag_pipeline_run_tasks.py" = ["ARG002"] "test_workflow_pause_integration.py" = ["T201"] -"workflow/nodes/code_executor/test_code_javascript.py" = ["ARG"] -"workflow/nodes/code_executor/test_code_jinja2.py" = ["ARG"] -"workflow/nodes/code_executor/test_code_python3.py" = ["ARG"] +"trigger/conftest.py" = ["ANN401", "TID251"] +"trigger/test_trigger_e2e.py" = ["ANN401", "ARG001", "TID251"] +"workflow/nodes/code_executor/test_code_javascript.py" = ["ARG002"] +"workflow/nodes/code_executor/test_code_jinja2.py" = ["ARG002"] +"workflow/nodes/code_executor/test_code_python3.py" = ["ARG002"] "workflow/nodes/code_executor/test_utils.py" = ["T201"] +[lint.flake8-tidy-imports.banned-api."flask_restx.reqparse"] +msg = "Use Pydantic payload/query models instead of reqparse." + +[lint.flake8-tidy-imports.banned-api."flask_restx.reqparse.RequestParser"] +msg = "Use Pydantic payload/query models instead of reqparse." + [lint.flake8-tidy-imports.banned-api."typing.Any"] msg = "Use object, Protocol, TypedDict, TypeVar, ParamSpec, or a localized cast instead." diff --git a/api/tests/test_containers_integration_tests/pyrefly.toml b/api/tests/test_containers_integration_tests/pyrefly.toml index e73d5bbe117..a02e394465a 100644 --- a/api/tests/test_containers_integration_tests/pyrefly.toml +++ b/api/tests/test_containers_integration_tests/pyrefly.toml @@ -1,42 +1,24 @@ preset = "strict" -strict-callable-subtyping = true project-includes = ["."] search-path = ["../.."] +python-platform = "linux" +python-version = "3.12.0" +infer-with-first-use = true +min-severity = "warn" -# Verify project-excludes from the repo root: -# tmp_config=$(mktemp --tmpdir=api/tests/test_containers_integration_tests pyrefly-no-excludes.XXXXXX.toml) -# awk 'BEGIN {skip=0} /^project-excludes = \[/ {skip=1; next} skip && /^\]/ {skip=0; next} !skip {print}' api/tests/test_containers_integration_tests/pyrefly.toml > "$tmp_config" -# tmp_name=$(basename "$tmp_config") -# comm -3 <(sed -n 's/^ "\(.*\)",$/\1/p' api/tests/test_containers_integration_tests/pyrefly.toml | sort) <(uv --directory api run pyrefly check --config "tests/test_containers_integration_tests/$tmp_name" --summary=none --output-format=min-text 2>/dev/null | rg '^ERROR ' | sed -E 's#^ERROR (tests/test_containers_integration_tests/[^:]+):.*#\1#' | sed 's#^tests/test_containers_integration_tests/##' | sort -u) -# rm --force "$tmp_config" +# Existing strict-mode debt. Remove a file when bringing it under strict checking. project-excludes = [ "commands/test_legacy_model_type_migration.py", - "controllers/console/app/test_app_apis.py", - "controllers/console/app/test_app_import_api.py", "controllers/console/app/test_chat_conversation_status_count_api.py", "controllers/console/app/test_conversation_read_timestamp.py", "controllers/console/app/test_workflow_draft_variable.py", - "controllers/console/auth/test_email_register.py", - "controllers/console/auth/test_forgot_password.py", - "controllers/console/auth/test_oauth.py", - "controllers/console/auth/test_password_reset.py", - "controllers/console/datasets/rag_pipeline/test_rag_pipeline.py", - "controllers/console/datasets/rag_pipeline/test_rag_pipeline_datasets.py", - "controllers/console/datasets/rag_pipeline/test_rag_pipeline_import.py", - "controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow.py", - "controllers/console/datasets/test_data_source.py", - "controllers/console/explore/test_conversation.py", "controllers/console/test_api_based_extension.py", "controllers/console/test_apikey.py", - "controllers/console/workspace/test_tool_provider.py", - "controllers/console/workspace/test_trigger_providers.py", "controllers/console/workspace/test_workspace_wraps.py", - "controllers/mcp/test_mcp.py", "controllers/service_api/dataset/test_dataset.py", "controllers/service_api/test_site.py", "controllers/web/test_conversation.py", "controllers/web/test_site.py", - "controllers/web/test_web_forgot_password.py", "controllers/web/test_wraps.py", "core/app/layers/test_pause_state_persist_layer.py", "core/rag/pipeline/test_queue_integration.py", @@ -57,22 +39,16 @@ project-excludes = [ "repositories/test_sqlalchemy_execution_extra_content_repository.py", "repositories/test_sqlalchemy_workflow_node_execution_repository.py", "repositories/test_workflow_run_repository.py", - "services/auth/test_api_key_auth_service.py", "services/auth/test_auth_integration.py", "services/dataset_collection_binding.py", "services/dataset_service_update_delete.py", "services/document_service_status.py", - "services/enterprise/test_account_deletion_sync.py", - "services/plugin/test_plugin_parameter_service.py", - "services/plugin/test_plugin_service.py", - "services/rag_pipeline/test_rag_pipeline_service_db.py", "services/recommend_app/test_database_retrieval.py", "services/test_account_service.py", "services/test_advanced_prompt_template_service.py", "services/test_agent_service.py", "services/test_annotation_service.py", "services/test_api_based_extension_service.py", - "services/test_api_token_service.py", "services/test_app_dsl_service.py", "services/test_app_generate_service.py", "services/test_app_service.py", @@ -97,20 +73,15 @@ project-excludes = [ "services/test_document_service_rename_document.py", "services/test_end_user_service.py", "services/test_feature_service.py", - "services/test_feedback_service.py", "services/test_file_service.py", "services/test_human_input_delivery_test.py", - "services/test_human_input_delivery_test_service.py", "services/test_message_export_service.py", "services/test_message_service.py", "services/test_message_service_execution_extra_content.py", "services/test_message_service_extra_contents.py", "services/test_messages_clean_service.py", - "services/test_metadata_partial_update.py", - "services/test_metadata_service.py", "services/test_model_load_balancing_service.py", "services/test_model_provider_service.py", - "services/test_oauth_server_service.py", "services/test_ops_service.py", "services/test_restore_archived_workflow_run.py", "services/test_saved_message_service.py", @@ -124,7 +95,6 @@ project-excludes = [ "services/test_workflow_run_service.py", "services/test_workflow_service.py", "services/test_workspace_service.py", - "services/tools/test_api_tools_manage_service.py", "services/tools/test_mcp_tools_manage_service.py", "services/tools/test_tools_transform_service.py", "services/tools/test_workflow_tools_manage_service.py", @@ -161,7 +131,6 @@ project-excludes = [ "test_workflow_pause_integration.py", "trigger/conftest.py", "trigger/test_trigger_e2e.py", - "workflow/nodes/code_executor/test_code_executor.py", "workflow/nodes/code_executor/test_code_javascript.py", "workflow/nodes/code_executor/test_code_jinja2.py", "workflow/nodes/code_executor/test_code_python3.py", @@ -169,6 +138,7 @@ project-excludes = [ ] [errors] +missing-override-decorator = "error" redundant-cast = true unannotated-return = true unnecessary-type-conversion = true diff --git a/api/tests/unit_tests/.ruff.toml b/api/tests/unit_tests/.ruff.toml new file mode 100644 index 00000000000..65c2cff31c0 --- /dev/null +++ b/api/tests/unit_tests/.ruff.toml @@ -0,0 +1,430 @@ +extend = "../../.ruff.toml" +src = ["../.."] + +[lint] +extend-select = ["ANN401", "ARG"] + +# Existing strict-mode debt. Remove a file entry when bringing it under strict checking. +[lint.per-file-ignores] +"clients/agent_backend/test_request_builder.py" = ["TID251"] +"commands/test_archive_workflow_runs.py" = ["ARG005"] +"commands/test_data_migration_wizard.py" = ["ARG005"] +"commands/test_legacy_model_type_migration.py" = ["ARG001", "ARG002", "ARG005"] +"controllers/common/test_agent_app_parameters.py" = ["ARG005", "TID251"] +"controllers/common/test_app_access.py" = ["ARG005"] +"controllers/console/agent/test_agent_controllers.py" = ["ARG001", "ARG002", "ARG003", "ARG005", "TID251"] +"controllers/console/app/test_agent_app_sandbox.py" = ["ARG002", "ARG005"] +"controllers/console/app/test_agent_config_inspector.py" = ["ARG005"] +"controllers/console/app/test_agent_drive_inspector.py" = ["ARG005"] +"controllers/console/app/test_agent_manage_guard.py" = ["ARG001"] +"controllers/console/app/test_agent_skills.py" = ["ARG005"] +"controllers/console/app/test_annotation_security.py" = ["ARG002"] +"controllers/console/app/test_app_apis.py" = ["ARG001", "ARG002"] +"controllers/console/app/test_app_import_api.py" = ["ARG001", "ARG002", "ARG005"] +"controllers/console/app/test_app_response_models.py" = ["ARG002", "ARG004", "ARG005"] +"controllers/console/app/test_conversation_api.py" = ["ARG001"] +"controllers/console/app/test_generator_api.py" = ["ARG001"] +"controllers/console/app/test_mcp_server_response.py" = ["ARG002"] +"controllers/console/app/test_message_api.py" = ["ARG001"] +"controllers/console/app/test_statistic_api.py" = ["ANN401", "ARG001", "TID251"] +"controllers/console/app/test_workflow.py" = ["ARG001", "ARG005"] +"controllers/console/app/test_workflow_convert_api.py" = ["ARG005"] +"controllers/console/app/test_workflow_node_output_inspector.py" = ["ANN401", "ARG001", "TID251"] +"controllers/console/app/test_workflow_run_api.py" = ["ANN401", "TID251"] +"controllers/console/app/workflow_draft_variables_test.py" = ["TID251"] +"controllers/console/auth/test_account_activation.py" = ["ARG002"] +"controllers/console/auth/test_authentication_security.py" = ["ARG002"] +"controllers/console/auth/test_email_verification.py" = ["ARG002"] +"controllers/console/auth/test_login_logout.py" = ["ARG002"] +"controllers/console/auth/test_oauth.py" = ["ARG002"] +"controllers/console/auth/test_oauth_timezone.py" = ["ARG001"] +"controllers/console/auth/test_password_reset.py" = ["ARG002"] +"controllers/console/auth/test_token_refresh.py" = ["ARG002"] +"controllers/console/billing/test_billing.py" = ["ARG002"] +"controllers/console/datasets/test_datasets.py" = ["ARG005"] +"controllers/console/datasets/test_datasets_document.py" = ["ARG002", "ARG005"] +"controllers/console/datasets/test_datasets_document_download.py" = ["ARG005"] +"controllers/console/datasets/test_datasets_segments.py" = ["TID251"] +"controllers/console/datasets/test_external.py" = ["TID251"] +"controllers/console/datasets/test_wraps.py" = ["ARG001"] +"controllers/console/explore/test_trial.py" = ["ANN401", "ARG001", "ARG005", "TID251"] +"controllers/console/explore/test_wraps.py" = ["ARG001"] +"controllers/console/snippets/test_snippet_workflow.py" = ["ARG001"] +"controllers/console/tag/test_tags.py" = ["ARG002"] +"controllers/console/test_files.py" = ["ARG001", "ARG002"] +"controllers/console/test_human_input_form.py" = ["ARG001", "ARG005"] +"controllers/console/test_init_validate.py" = ["ARG005"] +"controllers/console/test_workspace_account.py" = ["ARG002"] +"controllers/console/test_workspace_members.py" = ["ARG002", "ARG005"] +"controllers/console/test_wraps.py" = ["ARG001", "ARG002"] +"controllers/console/workspace/test_load_balancing_config.py" = ["ARG001"] +"controllers/console/workspace/test_plugin.py" = ["ARG002", "TID251"] +"controllers/console/workspace/test_snippets.py" = ["ARG002"] +"controllers/console/workspace/test_tool_providers.py" = ["ARG001", "ARG005"] +"controllers/console/workspace/test_trigger_providers.py" = ["ARG001"] +"controllers/console/workspace/test_workspace.py" = ["ARG005"] +"controllers/files/test_image_preview.py" = ["ARG005"] +"controllers/files/test_tool_files.py" = ["ARG002", "ARG005"] +"controllers/files/test_upload.py" = ["ARG002", "ARG005"] +"controllers/inner_api/plugin/test_plugin.py" = ["ARG002"] +"controllers/inner_api/plugin/test_plugin_wraps.py" = ["ARG001", "ARG002", "ARG003", "TID251"] +"controllers/inner_api/test_runtime_credentials.py" = ["ARG001"] +"controllers/mcp/test_mcp.py" = ["ARG002"] +"controllers/openapi/auth/test_conditions.py" = ["ARG005"] +"controllers/openapi/auth/test_flow.py" = ["ARG005"] +"controllers/openapi/auth/test_pipeline.py" = ["ARG001"] +"controllers/openapi/conftest.py" = ["ARG001"] +"controllers/openapi/test_account.py" = ["ARG005"] +"controllers/openapi/test_app_describe_builder.py" = ["ARG001"] +"controllers/openapi/test_app_run_streaming.py" = ["ARG001"] +"controllers/openapi/test_contract.py" = ["ARG001", "TID251"] +"controllers/openapi/test_error_contract.py" = ["ARG002"] +"controllers/openapi/test_human_input_form.py" = ["ARG002"] +"controllers/openapi/test_oauth_sso_claims.py" = ["ARG002"] +"controllers/openapi/test_workflow_events_openapi.py" = ["ARG002", "ARG005"] +"controllers/openapi/test_workspaces_members.py" = ["ARG001"] +"controllers/service_api/app/test_app.py" = ["ARG002"] +"controllers/service_api/app/test_completion.py" = ["ARG002"] +"controllers/service_api/app/test_file.py" = ["ARG002"] +"controllers/service_api/app/test_hitl_service_api.py" = ["ARG002", "ARG005"] +"controllers/service_api/app/test_workflow_events.py" = ["ARG005"] +"controllers/service_api/dataset/rag_pipeline/test_rag_pipeline_workflow.py" = ["ARG002"] +"controllers/service_api/dataset/test_dataset_segment.py" = ["ARG002"] +"controllers/service_api/dataset/test_document.py" = ["ARG001", "ARG002"] +"controllers/service_api/dataset/test_metadata.py" = ["ARG002"] +"controllers/service_api/test_trace_session_id_parsing.py" = ["ARG001"] +"controllers/service_api/test_wraps.py" = ["ARG001", "ARG002"] +"controllers/trigger/test_trigger.py" = ["ARG002"] +"controllers/trigger/test_webhook.py" = ["ARG002"] +"controllers/web/conftest.py" = ["ANN401", "TID251"] +"controllers/web/test_app.py" = ["ARG002", "ARG005"] +"controllers/web/test_audio.py" = ["ARG002"] +"controllers/web/test_completion.py" = ["ARG002"] +"controllers/web/test_feature.py" = ["ARG002"] +"controllers/web/test_human_input_form.py" = ["ARG001", "ARG002", "ARG005"] +"controllers/web/test_message_endpoints.py" = ["ARG002"] +"controllers/web/test_remote_files.py" = ["ARG002"] +"controllers/web/test_saved_message.py" = ["ARG002"] +"controllers/web/test_web_login.py" = ["ARG002"] +"controllers/web/test_web_passport.py" = ["ARG002"] +"controllers/web/test_workflow.py" = ["ARG002"] +"core/agent/test_base_agent_runner.py" = ["ARG002"] +"core/agent/test_cot_agent_runner.py" = ["ARG001"] +"core/agent/test_cot_chat_agent_runner.py" = ["ARG002"] +"core/agent/test_fc_agent_runner.py" = ["TID251"] +"core/app/app_config/common/test_parameters_mapping.py" = ["ARG002"] +"core/app/app_config/easy_ui_based_app/test_dataset_manager.py" = ["ARG001", "ARG002"] +"core/app/app_config/easy_ui_based_app/test_model_config_converter.py" = ["ARG002"] +"core/app/app_config/easy_ui_based_app/test_variables_manager.py" = ["ARG002"] +"core/app/apps/advanced_chat/test_app_generator.py" = ["ARG001", "ARG002", "ARG005"] +"core/app/apps/advanced_chat/test_app_runner_input_moderation.py" = ["ARG001", "ARG005"] +"core/app/apps/advanced_chat/test_generate_task_pipeline.py" = ["ARG005"] +"core/app/apps/advanced_chat/test_generate_task_pipeline_core.py" = ["ARG001", "ARG002", "ARG005"] +"core/app/apps/agent_app/test_app_generator.py" = ["ARG001", "ARG005"] +"core/app/apps/agent_app/test_app_runner.py" = ["ANN401", "ARG002", "ARG005", "TID251"] +"core/app/apps/agent_app/test_input_guards.py" = ["ANN401", "ARG002", "TID251"] +"core/app/apps/agent_app/test_resolve_agent.py" = ["ANN401", "TID251"] +"core/app/apps/agent_app/test_runtime_request_builder.py" = ["ARG002", "TID251"] +"core/app/apps/agent_chat/test_agent_chat_app_config_manager.py" = ["ARG005"] +"core/app/apps/agent_chat/test_agent_chat_app_generator.py" = ["ARG001"] +"core/app/apps/chat/test_app_config_manager.py" = ["ARG001"] +"core/app/apps/chat/test_app_generator_and_runner.py" = ["ARG001", "ARG002", "ARG005"] +"core/app/apps/common/test_workflow_response_converter_truncation.py" = ["TID251"] +"core/app/apps/completion/test_app_runner.py" = ["ARG001", "ARG002", "ARG005"] +"core/app/apps/pipeline/test_pipeline_generator.py" = ["ARG001", "ARG002", "ARG005"] +"core/app/apps/pipeline/test_pipeline_runner.py" = ["ARG001", "ARG002"] +"core/app/apps/test_advanced_chat_app_generator.py" = ["ARG001"] +"core/app/apps/test_base_app_generator.py" = ["ARG005"] +"core/app/apps/test_base_app_runner.py" = ["ARG001", "ARG002", "ARG005"] +"core/app/apps/test_pause_resume.py" = ["ANN401", "TID251"] +"core/app/apps/test_streaming_utils.py" = ["ARG001"] +"core/app/apps/test_workflow_app_generator.py" = ["ARG005"] +"core/app/apps/test_workflow_app_runner_core.py" = ["ARG001", "ARG002", "ARG004", "ARG005"] +"core/app/apps/test_workflow_app_runner_single_node.py" = ["ANN401", "TID251"] +"core/app/apps/test_workflow_pause_events.py" = ["ARG005"] +"core/app/apps/workflow/test_app_generator_extra.py" = ["ARG005"] +"core/app/apps/workflow/test_generate_task_pipeline_core.py" = ["ARG002", "ARG005"] +"core/app/features/rate_limiting/test_rate_limit.py" = ["ARG001"] +"core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline.py" = ["ARG002"] +"core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline_core.py" = ["ARG001", "ARG002", "ARG005"] +"core/app/task_pipeline/test_message_cycle_manager_optimization.py" = ["ARG002"] +"core/app/test_easy_ui_model_config_manager.py" = ["ARG005"] +"core/app/workflow/layers/test_persistence_inspector_publish.py" = ["ANN401", "ARG005", "TID251"] +"core/app/workflow/test_file_runtime.py" = ["ARG001", "ARG005"] +"core/app/workflow/test_observability_layer_extra.py" = ["ARG005"] +"core/app/workflow/test_persistence_layer.py" = ["ARG001"] +"core/base/test_app_generator_tts_publisher.py" = ["ARG002"] +"core/callback_handler/test_agent_tool_callback_handler.py" = ["ARG002"] +"core/callback_handler/test_workflow_tool_callback_handler.py" = ["ARG002"] +"core/datasource/__base/test_datasource_provider.py" = ["ARG002"] +"core/datasource/test_datasource_file_manager.py" = ["ARG001", "ARG002"] +"core/datasource/test_notion_provider.py" = ["ARG002", "TID251"] +"core/datasource/test_website_crawl.py" = ["ARG002"] +"core/datasource/utils/test_message_transformer.py" = ["ARG002"] +"core/entities/test_entities_mcp_provider.py" = ["ARG001"] +"core/entities/test_entities_provider_configuration.py" = ["ANN401", "ARG001", "ARG005", "TID251"] +"core/extension/test_extensible.py" = ["ARG002", "ARG005"] +"core/external_data_tool/api/test_api.py" = ["ARG001"] +"core/external_data_tool/test_base.py" = ["TID251"] +"core/external_data_tool/test_external_data_fetch.py" = ["ARG001"] +"core/helper/code_executor/test_code_executor.py" = ["TID251"] +"core/helper/code_executor/test_template_transformer.py" = ["ANN401", "TID251"] +"core/llm_generator/test_llm_generator.py" = ["ARG002"] +"core/mcp/auth/test_auth_flow.py" = ["ARG002"] +"core/mcp/client/test_session.py" = ["ARG001", "TID251"] +"core/mcp/client/test_sse.py" = ["ARG001", "TID251"] +"core/mcp/client/test_streamable_http.py" = ["ARG001", "ARG005", "S110", "TID251"] +"core/mcp/session/test_base_session.py" = ["S110"] +"core/mcp/session/test_client_session.py" = ["ARG005"] +"core/mcp/test_mcp_client.py" = ["ARG002"] +"core/memory/test_token_buffer_memory.py" = ["ARG002"] +"core/moderation/test_content_moderation.py" = ["TID251"] +"core/moderation/test_output_moderation.py" = ["ARG001", "ARG002"] +"core/ops/test_base_trace_instance.py" = ["ARG001"] +"core/ops/test_lookup_helpers.py" = ["ARG002"] +"core/ops/test_ops_trace_manager.py" = ["ARG001", "ARG002", "ARG005"] +"core/ops/test_trace_queue_manager.py" = ["ARG004"] +"core/ops/test_trace_session_metadata.py" = ["ARG001", "ARG005"] +"core/plugin/impl/test_agent_client.py" = ["ARG001"] +"core/plugin/impl/test_datasource_manager.py" = ["ARG001"] +"core/plugin/impl/test_oauth_handler.py" = ["ARG001"] +"core/plugin/impl/test_tool_manager.py" = ["ARG001"] +"core/plugin/impl/test_trigger_client.py" = ["ARG001"] +"core/plugin/test_endpoint_client.py" = ["ARG002"] +"core/plugin/test_model_runtime_adapter.py" = ["ARG002"] +"core/plugin/test_plugin_runtime.py" = ["ARG001", "ARG002", "TID251"] +"core/prompt/test_advanced_prompt_transform.py" = ["ARG005"] +"core/prompt/test_prompt_transform.py" = ["ARG005"] +"core/rag/datasource/keyword/jieba/test_jieba.py" = ["ARG001", "TID251"] +"core/rag/datasource/keyword/jieba/test_jieba_keyword_table_handler.py" = ["ARG004"] +"core/rag/datasource/keyword/test_keyword_factory.py" = ["ARG005"] +"core/rag/datasource/test_datasource_retrieval.py" = ["ARG001", "ARG002", "ARG005", "TID251"] +"core/rag/datasource/test_retrieval_attachment_access.py" = ["ARG005"] +"core/rag/datasource/vdb/test_vector_factory.py" = ["ARG002"] +"core/rag/embedding/test_embedding_base.py" = ["TID251"] +"core/rag/embedding/test_embedding_service.py" = ["ARG001"] +"core/rag/extractor/firecrawl/test_firecrawl.py" = ["TID251"] +"core/rag/extractor/test_csv_extractor.py" = ["ARG001", "ARG005"] +"core/rag/extractor/test_excel_extractor.py" = ["ARG002", "ARG005"] +"core/rag/extractor/test_extract_processor.py" = ["ARG001", "ARG005"] +"core/rag/extractor/test_helpers.py" = ["ARG002"] +"core/rag/extractor/test_markdown_extractor.py" = ["ARG001"] +"core/rag/extractor/test_notion_extractor.py" = ["ARG002", "ARG005"] +"core/rag/extractor/test_pdf_extractor.py" = ["ARG001", "ARG005"] +"core/rag/extractor/test_text_extractor.py" = ["ARG001"] +"core/rag/extractor/test_word_extractor.py" = ["ARG001", "ARG002", "ARG005"] +"core/rag/extractor/unstructured/test_unstructured_extractors.py" = ["ARG001", "ARG005"] +"core/rag/extractor/watercrawl/test_watercrawl.py" = ["ARG001", "ARG005", "TID251"] +"core/rag/indexing/processor/conftest.py" = ["ANN401", "ARG002", "TID251"] +"core/rag/indexing/processor/test_paragraph_index_processor.py" = ["ARG002", "TID251"] +"core/rag/indexing/processor/test_qa_index_processor.py" = ["ARG001", "TID251"] +"core/rag/indexing/test_index_processor.py" = ["ARG005"] +"core/rag/indexing/test_indexing_runner.py" = ["ARG005", "TID251"] +"core/rag/pipeline/test_queue.py" = ["ARG002"] +"core/rag/retrieval/test_dataset_retrieval.py" = ["ARG001", "ARG002", "ARG005", "TID251"] +"core/rag/splitter/test_text_splitter.py" = ["ARG002", "ARG005"] +"core/repositories/test_celery_workflow_execution_repository.py" = ["ARG002"] +"core/repositories/test_celery_workflow_node_execution_repository.py" = ["ARG002"] +"core/repositories/test_human_input_form_repository_impl.py" = ["ARG001", "ARG005"] +"core/repositories/test_human_input_repository.py" = ["ANN401", "ARG001", "ARG005", "TID251"] +"core/repositories/test_sqlalchemy_workflow_node_execution_repository.py" = ["ANN401", "ARG002", "ARG005", "TID251"] +"core/repositories/test_workflow_node_execution_truncation.py" = ["TID251"] +"core/schemas/test_resolver.py" = ["ARG005", "T201"] +"core/telemetry/test_facade.py" = ["ARG002", "ARG004"] +"core/telemetry/test_gateway_integration.py" = ["ARG002"] +"core/test_model_manager.py" = ["ARG001"] +"core/test_trigger_debug_event_selectors.py" = ["ARG002"] +"core/tools/test_base_tool.py" = ["ANN401", "ARG002", "TID251"] +"core/tools/test_builtin_tool_base.py" = ["ARG001", "ARG002", "TID251"] +"core/tools/test_builtin_tool_provider.py" = ["ARG001", "ARG005", "TID251"] +"core/tools/test_builtin_tools_extra.py" = ["ARG005"] +"core/tools/test_custom_tool.py" = ["ARG001", "ARG005", "TID251"] +"core/tools/test_dataset_retriever_tool.py" = ["ARG005"] +"core/tools/test_mcp_tool.py" = ["S110"] +"core/tools/test_tool_engine.py" = ["ANN401", "ARG002", "ARG005", "TID251"] +"core/tools/test_tool_file_manager.py" = ["ARG001"] +"core/tools/test_tool_label_manager.py" = ["TID251"] +"core/tools/test_tool_manager.py" = ["ANN401", "ARG001", "ARG005", "TID251"] +"core/tools/test_tool_provider_controller.py" = ["TID251"] +"core/tools/utils/test_configuration.py" = ["ARG001", "ARG002", "TID251"] +"core/tools/utils/test_encryption.py" = ["ANN401", "TID251"] +"core/tools/utils/test_message_transformer.py" = ["TID251"] +"core/tools/utils/test_misc_utils_extra.py" = ["ARG002"] +"core/tools/utils/test_model_invocation_utils.py" = ["ARG005", "TID251"] +"core/tools/utils/test_parser.py" = ["TID251"] +"core/tools/utils/test_web_reader_tool.py" = ["ARG001", "ARG002", "ARG005"] +"core/tools/workflow_as_tool/test_provider.py" = ["TID251"] +"core/tools/workflow_as_tool/test_tool.py" = ["ANN401", "ARG001", "ARG005", "TID251"] +"core/trigger/conftest.py" = ["ANN401", "TID251"] +"core/trigger/debug/test_debug_event_selectors.py" = ["ARG002", "TID251"] +"core/variables/test_segment_type_validation.py" = ["TID251"] +"core/workflow/context/test_execution_context.py" = ["ANN401", "ARG002", "S110", "TID251"] +"core/workflow/context/test_flask_app_context.py" = ["ARG002"] +"core/workflow/generator/test_runner.py" = ["ARG001", "ARG002", "TID251"] +"core/workflow/generator/test_runner_missing.py" = ["ARG003"] +"core/workflow/generator/test_tool_catalogue.py" = ["ARG002"] +"core/workflow/graph_engine/layers/test_observability.py" = ["ARG002"] +"core/workflow/graph_engine/test_mock_config.py" = ["TID251"] +"core/workflow/graph_engine/test_mock_factory.py" = ["TID251"] +"core/workflow/graph_engine/test_mock_nodes.py" = ["ANN401", "S110", "TID251"] +"core/workflow/graph_engine/test_parallel_human_input_join_resume.py" = ["ARG002", "TID251"] +"core/workflow/graph_engine/test_table_runner.py" = ["ARG001", "TID251"] +"core/workflow/nodes/agent_v2/test_agent_node.py" = ["ARG001", "ARG002", "ARG005"] +"core/workflow/nodes/agent_v2/test_ask_human_hitl.py" = ["ANN401", "TID251"] +"core/workflow/nodes/agent_v2/test_dify_tools_builder.py" = ["ANN401", "ARG001", "ARG002", "ARG005", "TID251"] +"core/workflow/nodes/agent_v2/test_output_adapter.py" = ["ARG005"] +"core/workflow/nodes/agent_v2/test_runtime_request_builder.py" = ["ARG002"] +"core/workflow/nodes/agent_v2/test_validators.py" = ["ARG001"] +"core/workflow/nodes/http_request/test_http_request_node.py" = ["ANN401", "ARG002", "TID251"] +"core/workflow/nodes/human_input/test_entities.py" = ["TID251"] +"core/workflow/nodes/human_input/test_human_input_form_filled_event.py" = ["TID251"] +"core/workflow/nodes/iteration/test_iteration_child_engine_errors.py" = ["ARG002", "TID251"] +"core/workflow/nodes/knowledge_index/test_knowledge_index_node.py" = ["ARG002"] +"core/workflow/nodes/knowledge_retrieval/test_knowledge_retrieval_node.py" = ["ARG002"] +"core/workflow/nodes/llm/test_node.py" = ["ARG002"] +"core/workflow/nodes/parameter_extractor/test_parameter_extractor_node.py" = ["TID251"] +"core/workflow/nodes/test_document_extractor_node.py" = ["ARG001"] +"core/workflow/nodes/tool/test_tool_node.py" = ["ANN401", "ARG002", "TID251"] +"core/workflow/nodes/webhook/test_webhook_file_conversion.py" = ["TID251"] +"core/workflow/nodes/webhook/test_webhook_node.py" = ["TID251"] +"core/workflow/test_form_input_serialization_compat.py" = ["ANN401", "TID251"] +"core/workflow/test_human_input_adapter.py" = ["ARG005"] +"core/workflow/test_node_factory.py" = ["ARG002"] +"core/workflow/test_workflow_entry.py" = ["ARG001"] +"enterprise/telemetry/test_enterprise_trace.py" = ["ARG002", "TID251"] +"enterprise/telemetry/test_exporter.py" = ["ARG001"] +"enterprise/telemetry/test_gateway.py" = ["ARG002"] +"events/event_handlers/test_delete_tool_parameters_cache_when_sync_draft_workflow.py" = ["ARG005"] +"extensions/logstore/test_sql_escape.py" = ["ARG001", "ARG002"] +"extensions/otel/decorators/handlers/test_generate_handler.py" = ["ARG001", "ARG002"] +"extensions/otel/decorators/handlers/test_workflow_app_runner_handler.py" = ["ARG001"] +"extensions/otel/decorators/test_base.py" = ["ARG002"] +"extensions/otel/decorators/test_handler.py" = ["ARG002"] +"extensions/otel/test_retrieval_tracing.py" = ["ARG001"] +"extensions/test_ext_request_logging.py" = ["ARG002"] +"extensions/test_redis.py" = ["ARG001"] +"factories/test_build_from_mapping.py" = ["ARG001"] +"factories/test_file_factory.py" = ["ARG001"] +"factories/test_variable_factory.py" = ["TID251"] +"fields/test_file_fields.py" = ["ARG005"] +"libs/_human_input/support.py" = ["TID251"] +"libs/broadcast_channel/redis/test_channel_unit_tests.py" = ["ARG002", "ARG005"] +"libs/broadcast_channel/redis/test_streams_channel_unit_tests.py" = ["ARG001", "ARG002", "TID251"] +"libs/test_cron_compatibility.py" = ["S110"] +"libs/test_email_i18n.py" = ["ANN401", "TID251"] +"libs/test_oauth_bearer_rate_limit_ordering.py" = ["ARG001"] +"libs/test_pyrefly_type_coverage.py" = ["TID251"] +"libs/test_schedule_utils_enhanced.py" = ["S110"] +"libs/test_sendgrid_client.py" = ["ARG001", "TID251"] +"libs/test_smtp_client.py" = ["TID251"] +"models/test_dataset_models.py" = ["ARG005"] +"models/test_plugin_entities.py" = ["TID251"] +"models/test_snippet.py" = ["ARG001"] +"oss/__mock/aliyun_oss.py" = ["ARG002"] +"oss/__mock/baidu_obs.py" = ["ARG002"] +"oss/__mock/base.py" = ["ARG002"] +"oss/__mock/tencent_cos.py" = ["ARG002"] +"oss/__mock/volcengine_tos.py" = ["ARG002"] +"oss/aliyun_oss/aliyun_oss/test_aliyun_oss.py" = ["ARG002"] +"oss/baidu_obs/test_baidu_obs.py" = ["ARG002"] +"oss/opendal/test_opendal.py" = ["ARG002"] +"oss/tencent_cos/test_tencent_cos.py" = ["ARG002"] +"oss/volcengine_tos/test_volcengine_tos.py" = ["ARG002"] +"services/agent/test_agent_observability_service.py" = ["ARG002", "ARG005"] +"services/agent/test_agent_services.py" = ["ARG001", "ARG002", "ARG003", "ARG005"] +"services/agent/test_composer_candidates.py" = ["ARG005"] +"services/agent/test_prompt_mentions.py" = ["ARG005"] +"services/agent/test_skill_tool_inference_service.py" = ["ARG001", "ARG005"] +"services/auth/test_jina_auth_standalone_module.py" = ["TID251"] +"services/controller_api.py" = ["ARG002"] +"services/data_migration/test_import_service.py" = ["ARG002", "ARG005"] +"services/dataset_service_test_helpers.py" = ["TID251"] +"services/enterprise/test_account_deletion_sync.py" = ["ARG001"] +"services/enterprise/test_rbac_service.py" = ["ARG002"] +"services/enterprise/test_traceparent_propagation.py" = ["ARG002"] +"services/hit_service.py" = ["TID251"] +"services/plugin/test_plugin_parameter_service.py" = ["ARG002"] +"services/rag_pipeline/pipeline_template/test_built_in_retrieval.py" = ["ARG001"] +"services/rag_pipeline/test_rag_pipeline_dsl_service.py" = ["ARG001", "ARG005", "T201", "TID251"] +"services/rag_pipeline/test_rag_pipeline_service.py" = ["ARG001", "ARG005"] +"services/rag_pipeline/test_rag_pipeline_task_proxy.py" = ["ARG001", "ARG005"] +"services/rag_pipeline/test_rag_pipeline_transform_service.py" = ["ARG001"] +"services/recommend_app/test_remote_retrieval.py" = ["ARG002"] +"services/retention/workflow_run/test_archive_download_preparation.py" = ["ARG002"] +"services/retention/workflow_run/test_archive_log_service.py" = ["ARG001", "ARG002"] +"services/retention/workflow_run/test_bundle_archive_maintenance.py" = ["TID251"] +"services/retention/workflow_run/test_restore_archived_workflow_run.py" = ["ARG002"] +"services/test_account_service.py" = ["ARG001", "ARG002"] +"services/test_annotation_service.py" = ["ANN401", "TID251"] +"services/test_api_token_service.py" = ["ARG002"] +"services/test_app_generate_service.py" = ["ARG001", "ARG002", "ARG004"] +"services/test_app_generate_service_streaming_integration.py" = ["ARG002", "TID251"] +"services/test_archive_workflow_run_logs.py" = ["ARG002"] +"services/test_audio_service.py" = ["ARG002", "TID251"] +"services/test_batch_indexing_base.py" = ["ANN401", "TID251"] +"services/test_billing_service.py" = ["ARG001", "ARG002"] +"services/test_clear_free_plan_expired_workflow_run_logs.py" = ["ANN401", "ARG002", "ARG005", "TID251"] +"services/test_clear_free_plan_tenant_expired_logs.py" = ["ARG002", "ARG003"] +"services/test_dataset_service_document.py" = ["ARG002"] +"services/test_dataset_service_lock_not_owned.py" = ["ARG001", "ARG005"] +"services/test_dataset_service_segment.py" = ["ARG002"] +"services/test_datasource_provider_service.py" = ["ARG002"] +"services/test_external_dataset_service.py" = ["ARG002", "TID251"] +"services/test_feature_service_human_input_email_delivery.py" = ["ARG005"] +"services/test_feedback_service.py" = ["ARG002"] +"services/test_human_input_delivery_test_service.py" = ["ARG005"] +"services/test_knowledge_service.py" = ["TID251"] +"services/test_message_service.py" = ["ARG002"] +"services/test_messages_clean_service.py" = ["TID251"] +"services/test_model_load_balancing_service.py" = ["ANN401", "ARG001", "ARG005", "TID251"] +"services/test_model_provider_service.py" = ["ANN401", "TID251"] +"services/test_model_provider_service_sanitization.py" = ["ARG002", "ARG005"] +"services/test_oauth_server_service.py" = ["ARG002"] +"services/test_operation_service.py" = ["TID251"] +"services/test_rag_pipeline_task_proxy.py" = ["ARG002"] +"services/test_recommended_app_service.py" = ["ARG001"] +"services/test_schedule_service.py" = ["ANN401", "TID251"] +"services/test_snippet_service.py" = ["ARG001", "ARG002"] +"services/test_summary_index_service.py" = ["ARG001"] +"services/test_telemetry_service.py" = ["ARG001", "ARG005"] +"services/test_variable_truncator.py" = ["ARG002", "TID251"] +"services/test_variable_truncator_additional.py" = ["ANN401", "TID251"] +"services/test_vector_service.py" = ["ARG001", "TID251"] +"services/test_webhook_service_additional.py" = ["ANN401", "ARG002", "TID251"] +"services/test_website_service.py" = ["TID251"] +"services/test_workflow_comment_service.py" = ["ARG001", "ARG002"] +"services/test_workflow_run_service.py" = ["ANN401", "ARG002", "TID251"] +"services/test_workflow_service.py" = ["ANN401", "ARG002", "TID251"] +"services/tools/test_builtin_tools_manage_service.py" = ["ARG001", "ARG002"] +"services/tools/test_tools_manage_service.py" = ["ARG002"] +"services/workflow/test_inspector_events.py" = ["ANN401", "TID251"] +"services/workflow/test_node_output_inspector_service.py" = ["TID251"] +"services/workflow/test_workflow_converter_additional.py" = ["ANN401", "ARG001", "ARG005", "TID251"] +"services/workflow/test_workflow_event_snapshot_service.py" = ["ANN401", "ARG002", "ARG005", "TID251"] +"services/workflow/test_workflow_event_snapshot_service_additional.py" = ["ANN401", "ARG002", "ARG005", "TID251"] +"tasks/test_agent_backend_session_cleanup_task.py" = ["ARG005"] +"tasks/test_clean_dataset_task.py" = ["ARG002"] +"tasks/test_clean_document_task.py" = ["ARG002"] +"tasks/test_dataset_indexing_task.py" = ["ARG001", "ARG002"] +"tasks/test_document_indexing_sync_task.py" = ["ARG002"] +"tasks/test_duplicate_document_indexing_task.py" = ["ARG002"] +"tasks/test_human_input_timeout_tasks.py" = ["ARG001", "ARG002", "ARG005", "TID251"] +"tasks/test_initialize_created_app_rbac_access_task.py" = ["ARG005"] +"tasks/test_mail_send_task.py" = ["ARG002"] +"tasks/test_ops_trace_task.py" = ["ARG004"] +"tasks/test_process_tenant_plugin_autoupgrade_check_task.py" = ["ARG001"] +"tasks/test_remove_app_and_related_data_task.py" = ["ARG002"] +"tasks/test_trigger_processing_tasks.py" = ["ARG002"] +"tasks/test_workflow_execute_task.py" = ["ARG005"] +"test_app_factory.py" = ["ARG001"] +"test_pytest_dify.py" = ["ARG001"] +"tools/test_mcp_tool.py" = ["TID251"] + +[lint.flake8-tidy-imports.banned-api."flask_restx.reqparse"] +msg = "Use Pydantic payload/query models instead of reqparse." + +[lint.flake8-tidy-imports.banned-api."flask_restx.reqparse.RequestParser"] +msg = "Use Pydantic payload/query models instead of reqparse." + +[lint.flake8-tidy-imports.banned-api."typing.Any"] +msg = "Use object, Protocol, TypedDict, TypeVar, ParamSpec, or a localized cast instead." diff --git a/api/tests/unit_tests/pyrefly.toml b/api/tests/unit_tests/pyrefly.toml new file mode 100644 index 00000000000..ad591ee5530 --- /dev/null +++ b/api/tests/unit_tests/pyrefly.toml @@ -0,0 +1,961 @@ +preset = "strict" +project-includes = ["."] +search-path = ["../.."] +python-platform = "linux" +python-version = "3.12.0" +infer-with-first-use = true +min-severity = "warn" + +# Existing strict-mode debt. Remove a file when bringing it under strict checking. +project-excludes = [ + "clients/agent_backend/test_cleanup_composition_compositor_integration.py", + "clients/agent_backend/test_client.py", + "clients/agent_backend/test_event_adapter.py", + "clients/agent_backend/test_fake_client.py", + "clients/agent_backend/test_request_builder.py", + "clients/agent_backend/test_session_cleanup.py", + "commands/test_archive_workflow_runs.py", + "commands/test_clean_expired_messages.py", + "commands/test_data_migration_commands.py", + "commands/test_data_migration_wizard.py", + "commands/test_fix_app_site_missing.py", + "commands/test_generate_swagger_markdown_docs.py", + "commands/test_generate_swagger_specs.py", + "commands/test_legacy_model_type_migration.py", + "commands/test_lint_response_contracts.py", + "commands/test_reset_encrypt_key_pair.py", + "commands/test_upgrade_db.py", + "configs/test_dify_config.py", + "configs/test_env_consistency.py", + "configs/test_nacos_http_client.py", + "conftest.py", + "controllers/common/test_agent_app_parameters.py", + "controllers/common/test_app_access.py", + "controllers/common/test_errors.py", + "controllers/common/test_fields.py", + "controllers/common/test_file_response.py", + "controllers/common/test_helpers.py", + "controllers/common/test_schema.py", + "controllers/common/test_session.py", + "controllers/console/agent/test_agent_controllers.py", + "controllers/console/app/test_agent_app_sandbox.py", + "controllers/console/app/test_agent_config_inspector.py", + "controllers/console/app/test_agent_drive_inspector.py", + "controllers/console/app/test_agent_manage_guard.py", + "controllers/console/app/test_agent_skills.py", + "controllers/console/app/test_annotation_api.py", + "controllers/console/app/test_annotation_security.py", + "controllers/console/app/test_app_apis.py", + "controllers/console/app/test_app_import_api.py", + "controllers/console/app/test_app_response_models.py", + "controllers/console/app/test_audio.py", + "controllers/console/app/test_conversation_api.py", + "controllers/console/app/test_conversation_variables_api.py", + "controllers/console/app/test_create_app_payload.py", + "controllers/console/app/test_description_validation.py", + "controllers/console/app/test_generator_api.py", + "controllers/console/app/test_generator_api_missing.py", + "controllers/console/app/test_mcp_server_response.py", + "controllers/console/app/test_message_api.py", + "controllers/console/app/test_model_config_api.py", + "controllers/console/app/test_ops_trace_api.py", + "controllers/console/app/test_statistic_api.py", + "controllers/console/app/test_workflow.py", + "controllers/console/app/test_workflow_app_log_api.py", + "controllers/console/app/test_workflow_comment_api.py", + "controllers/console/app/test_workflow_convert_api.py", + "controllers/console/app/test_workflow_node_output_inspector.py", + "controllers/console/app/test_workflow_pause_details_api.py", + "controllers/console/app/test_workflow_run_api.py", + "controllers/console/app/test_workflow_trigger_api.py", + "controllers/console/app/test_wraps.py", + "controllers/console/app/workflow_draft_variables_test.py", + "controllers/console/auth/test_account_activation.py", + "controllers/console/auth/test_authentication_security.py", + "controllers/console/auth/test_data_source_bearer_auth.py", + "controllers/console/auth/test_email_register.py", + "controllers/console/auth/test_email_register_language.py", + "controllers/console/auth/test_email_verification.py", + "controllers/console/auth/test_forgot_password.py", + "controllers/console/auth/test_login_logout.py", + "controllers/console/auth/test_oauth.py", + "controllers/console/auth/test_oauth_timezone.py", + "controllers/console/auth/test_password_reset.py", + "controllers/console/auth/test_token_refresh.py", + "controllers/console/billing/test_billing.py", + "controllers/console/datasets/rag_pipeline/test_datasource_auth.py", + "controllers/console/datasets/rag_pipeline/test_datasource_content_preview.py", + "controllers/console/datasets/rag_pipeline/test_rag_pipeline.py", + "controllers/console/datasets/rag_pipeline/test_rag_pipeline_draft_variable.py", + "controllers/console/datasets/rag_pipeline/test_rag_pipeline_workflow.py", + "controllers/console/datasets/test_data_source.py", + "controllers/console/datasets/test_datasets.py", + "controllers/console/datasets/test_datasets_document.py", + "controllers/console/datasets/test_datasets_document_download.py", + "controllers/console/datasets/test_datasets_segments.py", + "controllers/console/datasets/test_external.py", + "controllers/console/datasets/test_hit_testing.py", + "controllers/console/datasets/test_hit_testing_base.py", + "controllers/console/datasets/test_metadata.py", + "controllers/console/datasets/test_website.py", + "controllers/console/datasets/test_wraps.py", + "controllers/console/explore/test_audio.py", + "controllers/console/explore/test_banner.py", + "controllers/console/explore/test_completion.py", + "controllers/console/explore/test_installed_app.py", + "controllers/console/explore/test_message.py", + "controllers/console/explore/test_parameter.py", + "controllers/console/explore/test_recommended_app.py", + "controllers/console/explore/test_saved_message.py", + "controllers/console/explore/test_trial.py", + "controllers/console/explore/test_workflow.py", + "controllers/console/explore/test_wraps.py", + "controllers/console/snippets/test_snippet_workflow.py", + "controllers/console/snippets/test_snippet_workflow_draft_variable.py", + "controllers/console/tag/test_tags.py", + "controllers/console/test_document_detail_api_data_source_info.py", + "controllers/console/test_extension.py", + "controllers/console/test_fastopenapi_ping.py", + "controllers/console/test_fastopenapi_setup.py", + "controllers/console/test_fastopenapi_version.py", + "controllers/console/test_feature.py", + "controllers/console/test_files.py", + "controllers/console/test_files_security.py", + "controllers/console/test_human_input_form.py", + "controllers/console/test_knowledge_fs_proxy.py", + "controllers/console/test_remote_files.py", + "controllers/console/test_spec.py", + "controllers/console/test_version.py", + "controllers/console/test_workflow_run_archive.py", + "controllers/console/test_workspace_account.py", + "controllers/console/test_workspace_members.py", + "controllers/console/test_wraps.py", + "controllers/console/workspace/test_accounts.py", + "controllers/console/workspace/test_agent_providers.py", + "controllers/console/workspace/test_endpoint.py", + "controllers/console/workspace/test_load_balancing_config.py", + "controllers/console/workspace/test_members.py", + "controllers/console/workspace/test_model_providers.py", + "controllers/console/workspace/test_models.py", + "controllers/console/workspace/test_plugin.py", + "controllers/console/workspace/test_rbac.py", + "controllers/console/workspace/test_snippets.py", + "controllers/console/workspace/test_tool_providers.py", + "controllers/console/workspace/test_workspace.py", + "controllers/files/test_image_preview.py", + "controllers/files/test_tool_files.py", + "controllers/files/test_upload.py", + "controllers/inner_api/app/test_dsl.py", + "controllers/inner_api/plugin/test_agent_config.py", + "controllers/inner_api/plugin/test_agent_drive.py", + "controllers/inner_api/plugin/test_plugin.py", + "controllers/inner_api/plugin/test_plugin_wraps.py", + "controllers/inner_api/test_auth_wraps.py", + "controllers/inner_api/test_knowledge_retrieval.py", + "controllers/inner_api/test_mail.py", + "controllers/inner_api/test_runtime_credentials.py", + "controllers/inner_api/workspace/test_workspace.py", + "controllers/mcp/test_mcp.py", + "controllers/openapi/auth/test_composition.py", + "controllers/openapi/auth/test_conditions.py", + "controllers/openapi/auth/test_data.py", + "controllers/openapi/auth/test_flow.py", + "controllers/openapi/auth/test_pipeline.py", + "controllers/openapi/auth/test_prepare.py", + "controllers/openapi/auth/test_verify.py", + "controllers/openapi/conftest.py", + "controllers/openapi/test_account.py", + "controllers/openapi/test_app_describe_builder.py", + "controllers/openapi/test_app_list_query.py", + "controllers/openapi/test_app_payloads.py", + "controllers/openapi/test_app_run_dispatch.py", + "controllers/openapi/test_app_run_rate_limit.py", + "controllers/openapi/test_app_run_streaming.py", + "controllers/openapi/test_apps_permitted_external_query.py", + "controllers/openapi/test_audit_app_run.py", + "controllers/openapi/test_contract.py", + "controllers/openapi/test_cors.py", + "controllers/openapi/test_device_approve_deny.py", + "controllers/openapi/test_device_code.py", + "controllers/openapi/test_device_lookup.py", + "controllers/openapi/test_device_sso.py", + "controllers/openapi/test_device_token.py", + "controllers/openapi/test_error_contract.py", + "controllers/openapi/test_health.py", + "controllers/openapi/test_human_input_form.py", + "controllers/openapi/test_input_schema.py", + "controllers/openapi/test_meta_version.py", + "controllers/openapi/test_models.py", + "controllers/openapi/test_oauth_sso_claims.py", + "controllers/openapi/test_oauth_sso_csrf.py", + "controllers/openapi/test_oauth_sso_host_header.py", + "controllers/openapi/test_pagination_envelope.py", + "controllers/openapi/test_supported_app_type.py", + "controllers/openapi/test_version_gate.py", + "controllers/openapi/test_workflow_events_openapi.py", + "controllers/openapi/test_workspaces.py", + "controllers/openapi/test_workspaces_members.py", + "controllers/service_api/app/test_annotation.py", + "controllers/service_api/app/test_app.py", + "controllers/service_api/app/test_audio.py", + "controllers/service_api/app/test_chat_request_payload.py", + "controllers/service_api/app/test_completion.py", + "controllers/service_api/app/test_conversation.py", + "controllers/service_api/app/test_file.py", + "controllers/service_api/app/test_file_preview.py", + "controllers/service_api/app/test_hitl_service_api.py", + "controllers/service_api/app/test_human_input_form.py", + "controllers/service_api/app/test_message.py", + "controllers/service_api/app/test_workflow.py", + "controllers/service_api/app/test_workflow_events.py", + "controllers/service_api/conftest.py", + "controllers/service_api/dataset/rag_pipeline/test_rag_pipeline_workflow.py", + "controllers/service_api/dataset/test_dataset_segment.py", + "controllers/service_api/dataset/test_document.py", + "controllers/service_api/dataset/test_hit_testing.py", + "controllers/service_api/dataset/test_metadata.py", + "controllers/service_api/dataset/test_rag_pipeline_file_upload_serialization.py", + "controllers/service_api/dataset/test_rag_pipeline_route_registration.py", + "controllers/service_api/test_index.py", + "controllers/service_api/test_trace_session_id_parsing.py", + "controllers/service_api/test_wraps.py", + "controllers/test_compare_versions.py", + "controllers/test_conversation_rename_payload.py", + "controllers/test_swagger.py", + "controllers/trigger/test_trigger.py", + "controllers/trigger/test_webhook.py", + "controllers/web/conftest.py", + "controllers/web/test_app.py", + "controllers/web/test_completion.py", + "controllers/web/test_human_input_file_upload.py", + "controllers/web/test_human_input_form.py", + "controllers/web/test_message_endpoints.py", + "controllers/web/test_message_list.py", + "controllers/web/test_pydantic_models.py", + "controllers/web/test_web_forgot_password.py", + "controllers/web/test_web_login.py", + "controllers/web/test_web_passport.py", + "controllers/web/test_workflow.py", + "controllers/web/test_wraps.py", + "core/agent/conftest.py", + "core/agent/output_parser/test_cot_output_parser.py", + "core/agent/strategy/test_base.py", + "core/agent/strategy/test_plugin.py", + "core/agent/test_base_agent_runner.py", + "core/agent/test_cot_agent_runner.py", + "core/agent/test_cot_chat_agent_runner.py", + "core/agent/test_cot_completion_agent_runner.py", + "core/agent/test_fc_agent_runner.py", + "core/agent/test_plugin_entities.py", + "core/app/app_config/common/test_parameters_mapping.py", + "core/app/app_config/common/test_sensitive_word_avoidance_manager.py", + "core/app/app_config/easy_ui_based_app/test_agent_manager.py", + "core/app/app_config/easy_ui_based_app/test_dataset_manager.py", + "core/app/app_config/easy_ui_based_app/test_model_config_converter.py", + "core/app/app_config/easy_ui_based_app/test_model_config_manager.py", + "core/app/app_config/easy_ui_based_app/test_prompt_template_manager.py", + "core/app/app_config/easy_ui_based_app/test_variables_manager.py", + "core/app/app_config/features/file_upload/test_manager.py", + "core/app/app_config/features/test_additional_feature_managers.py", + "core/app/app_config/test_base_app_config_manager.py", + "core/app/app_config/test_entities.py", + "core/app/app_config/workflow_ui_based_app/test_workflow_ui_based_app_manager.py", + "core/app/apps/advanced_chat/test_app_config_manager.py", + "core/app/apps/advanced_chat/test_app_generator.py", + "core/app/apps/advanced_chat/test_app_runner_conversation_variables.py", + "core/app/apps/advanced_chat/test_app_runner_input_moderation.py", + "core/app/apps/advanced_chat/test_generate_response_converter.py", + "core/app/apps/advanced_chat/test_generate_task_pipeline.py", + "core/app/apps/advanced_chat/test_generate_task_pipeline_core.py", + "core/app/apps/agent_app/test_app_config_manager.py", + "core/app/apps/agent_app/test_app_generator.py", + "core/app/apps/agent_app/test_app_runner.py", + "core/app/apps/agent_app/test_input_guards.py", + "core/app/apps/agent_app/test_resolve_agent.py", + "core/app/apps/agent_app/test_runtime_request_builder.py", + "core/app/apps/agent_app/test_session_store.py", + "core/app/apps/agent_chat/test_agent_chat_app_config_manager.py", + "core/app/apps/agent_chat/test_agent_chat_app_generator.py", + "core/app/apps/agent_chat/test_agent_chat_app_runner.py", + "core/app/apps/agent_chat/test_agent_chat_generate_response_converter.py", + "core/app/apps/chat/test_app_config_manager.py", + "core/app/apps/chat/test_app_generator_and_runner.py", + "core/app/apps/chat/test_base_app_runner_multimodal.py", + "core/app/apps/chat/test_generate_response_converter.py", + "core/app/apps/common/test_graph_runtime_state_support.py", + "core/app/apps/common/test_workflow_response_converter.py", + "core/app/apps/common/test_workflow_response_converter_human_input.py", + "core/app/apps/common/test_workflow_response_converter_resumption.py", + "core/app/apps/common/test_workflow_response_converter_truncation.py", + "core/app/apps/completion/test_app_runner.py", + "core/app/apps/completion/test_completion_app_config_manager.py", + "core/app/apps/completion/test_completion_completion_app_generator.py", + "core/app/apps/completion/test_completion_generate_response_converter.py", + "core/app/apps/pipeline/test_pipeline_config_manager.py", + "core/app/apps/pipeline/test_pipeline_generate_response_converter.py", + "core/app/apps/pipeline/test_pipeline_generator.py", + "core/app/apps/pipeline/test_pipeline_queue_manager.py", + "core/app/apps/pipeline/test_pipeline_runner.py", + "core/app/apps/test_advanced_chat_app_generator.py", + "core/app/apps/test_base_app_generate_response_converter.py", + "core/app/apps/test_base_app_generator.py", + "core/app/apps/test_base_app_queue_manager.py", + "core/app/apps/test_base_app_runner.py", + "core/app/apps/test_exc.py", + "core/app/apps/test_message_based_app_generator.py", + "core/app/apps/test_message_based_app_queue_manager.py", + "core/app/apps/test_message_generator.py", + "core/app/apps/test_pause_resume.py", + "core/app/apps/test_streaming_utils.py", + "core/app/apps/test_trace_session_id_generate_extras.py", + "core/app/apps/test_workflow_app_generator.py", + "core/app/apps/test_workflow_app_runner_core.py", + "core/app/apps/test_workflow_app_runner_notifications.py", + "core/app/apps/test_workflow_app_runner_single_node.py", + "core/app/apps/test_workflow_pause_events.py", + "core/app/apps/workflow/test_active_workflow_tasks.py", + "core/app/apps/workflow/test_app_config_manager.py", + "core/app/apps/workflow/test_app_generator_extra.py", + "core/app/apps/workflow/test_app_queue_manager.py", + "core/app/apps/workflow/test_command_channels.py", + "core/app/apps/workflow/test_errors.py", + "core/app/apps/workflow/test_generate_response_converter.py", + "core/app/apps/workflow/test_generate_task_pipeline.py", + "core/app/apps/workflow/test_generate_task_pipeline_core.py", + "core/app/entities/test_app_invoke_entities.py", + "core/app/entities/test_queue_entities.py", + "core/app/entities/test_rag_pipeline_invoke_entities.py", + "core/app/entities/test_task_entities.py", + "core/app/features/rate_limiting/conftest.py", + "core/app/features/rate_limiting/test_rate_limit.py", + "core/app/features/test_annotation_reply.py", + "core/app/features/test_hosting_moderation.py", + "core/app/layers/test_conversation_variable_persist_layer.py", + "core/app/layers/test_pause_state_persist_layer.py", + "core/app/layers/test_suspend_layer.py", + "core/app/layers/test_timeslice_layer.py", + "core/app/layers/test_trigger_post_layer.py", + "core/app/task_pipeline/test_based_generate_task_pipeline.py", + "core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline.py", + "core/app/task_pipeline/test_easy_ui_based_generate_task_pipeline_core.py", + "core/app/task_pipeline/test_exc.py", + "core/app/task_pipeline/test_message_cycle_manager_optimization.py", + "core/app/test_easy_ui_model_config_manager.py", + "core/app/test_invoke_from.py", + "core/app/test_llm_quota.py", + "core/app/workflow/layers/test_persistence.py", + "core/app/workflow/layers/test_persistence_inspector_publish.py", + "core/app/workflow/test_file_runtime.py", + "core/app/workflow/test_node_factory.py", + "core/app/workflow/test_observability_layer_extra.py", + "core/app/workflow/test_persistence_layer.py", + "core/base/test_app_generator_tts_publisher.py", + "core/callback_handler/test_agent_tool_callback_handler.py", + "core/callback_handler/test_index_tool_callback_handler.py", + "core/callback_handler/test_workflow_tool_callback_handler.py", + "core/datasource/__base/test_datasource_plugin.py", + "core/datasource/__base/test_datasource_provider.py", + "core/datasource/__base/test_datasource_runtime.py", + "core/datasource/entities/test_api_entities.py", + "core/datasource/entities/test_common_entities.py", + "core/datasource/entities/test_datasource_entities.py", + "core/datasource/local_file/test_local_file_plugin.py", + "core/datasource/local_file/test_local_file_provider.py", + "core/datasource/online_document/test_online_document_plugin.py", + "core/datasource/online_document/test_online_document_provider.py", + "core/datasource/online_drive/test_online_drive_plugin.py", + "core/datasource/online_drive/test_online_drive_provider.py", + "core/datasource/test_datasource_file_manager.py", + "core/datasource/test_datasource_manager.py", + "core/datasource/test_errors.py", + "core/datasource/test_file_upload.py", + "core/datasource/test_notion_provider.py", + "core/datasource/test_website_crawl.py", + "core/datasource/utils/test_message_transformer.py", + "core/datasource/website_crawl/test_website_crawl_plugin.py", + "core/datasource/website_crawl/test_website_crawl_provider.py", + "core/entities/test_entities_mcp_provider.py", + "core/entities/test_entities_provider_configuration.py", + "core/extension/test_api_based_extension_requestor.py", + "core/extension/test_extensible.py", + "core/extension/test_extension.py", + "core/external_data_tool/api/test_api.py", + "core/external_data_tool/test_base.py", + "core/external_data_tool/test_external_data_fetch.py", + "core/external_data_tool/test_factory.py", + "core/file/test_models.py", + "core/file/test_remote_fetcher.py", + "core/helper/code_executor/javascript/test_javascript_transformer.py", + "core/helper/code_executor/jinja2/test_jinja2_sandbox.py", + "core/helper/code_executor/python3/test_python3_transformer.py", + "core/helper/code_executor/test_code_executor.py", + "core/helper/code_executor/test_code_node_provider.py", + "core/helper/code_executor/test_template_transformer.py", + "core/helper/test_creators.py", + "core/helper/test_credential_utils.py", + "core/helper/test_csv_sanitizer.py", + "core/helper/test_encrypter.py", + "core/helper/test_ssrf_proxy.py", + "core/helper/test_trace_id_helper.py", + "core/llm_generator/output_parser/test_rule_config_generator.py", + "core/llm_generator/output_parser/test_structured_output.py", + "core/llm_generator/test_llm_generator.py", + "core/llm_generator/test_llm_generator_missing.py", + "core/logging/test_context.py", + "core/logging/test_filters.py", + "core/logging/test_structured_formatter.py", + "core/logging/test_trace_helpers.py", + "core/mcp/auth/test_auth_flow.py", + "core/mcp/client/test_session.py", + "core/mcp/client/test_sse.py", + "core/mcp/client/test_streamable_http.py", + "core/mcp/server/test_streamable_http.py", + "core/mcp/session/test_base_session.py", + "core/mcp/session/test_client_session.py", + "core/mcp/test_auth_client_inheritance.py", + "core/mcp/test_entities.py", + "core/mcp/test_error.py", + "core/mcp/test_mcp_client.py", + "core/mcp/test_types.py", + "core/mcp/test_utils.py", + "core/memory/test_token_buffer_memory.py", + "core/model_runtime/test_model_provider_factory.py", + "core/moderation/api/test_api.py", + "core/moderation/test_content_moderation.py", + "core/moderation/test_input_moderation.py", + "core/moderation/test_output_moderation.py", + "core/moderation/test_sensitive_word_filter.py", + "core/ops/test_base_trace_instance.py", + "core/ops/test_config_entity.py", + "core/ops/test_lookup_helpers.py", + "core/ops/test_ops_trace_manager.py", + "core/ops/test_trace_queue_manager.py", + "core/ops/test_trace_session_metadata.py", + "core/ops/test_utils.py", + "core/plugin/impl/test_agent_client.py", + "core/plugin/impl/test_asset_manager.py", + "core/plugin/impl/test_base_client_impl.py", + "core/plugin/impl/test_datasource_manager.py", + "core/plugin/impl/test_debugging_client.py", + "core/plugin/impl/test_endpoint_client_impl.py", + "core/plugin/impl/test_exc_impl.py", + "core/plugin/impl/test_model_client.py", + "core/plugin/impl/test_model_runtime_factory.py", + "core/plugin/impl/test_oauth_handler.py", + "core/plugin/impl/test_tool_manager.py", + "core/plugin/impl/test_trigger_client.py", + "core/plugin/test_backwards_invocation_app.py", + "core/plugin/test_backwards_invocation_model.py", + "core/plugin/test_endpoint_client.py", + "core/plugin/test_model_runtime_adapter.py", + "core/plugin/test_plugin_entities.py", + "core/plugin/test_plugin_manager.py", + "core/plugin/test_plugin_runtime.py", + "core/plugin/utils/test_chunk_merger.py", + "core/plugin/utils/test_http_parser.py", + "core/prompt/test_advanced_prompt_transform.py", + "core/prompt/test_agent_history_prompt_transform.py", + "core/prompt/test_extract_thread_messages.py", + "core/prompt/test_prompt_message.py", + "core/prompt/test_prompt_transform.py", + "core/prompt/test_simple_prompt_transform.py", + "core/rag/cleaner/test_clean_processor.py", + "core/rag/data_post_processor/test_data_post_processor.py", + "core/rag/datasource/keyword/jieba/test_jieba.py", + "core/rag/datasource/keyword/jieba/test_jieba_keyword_table_handler.py", + "core/rag/datasource/keyword/jieba/test_stopwords.py", + "core/rag/datasource/keyword/test_keyword_base.py", + "core/rag/datasource/keyword/test_keyword_factory.py", + "core/rag/datasource/test_datasource_retrieval.py", + "core/rag/datasource/vdb/test_field.py", + "core/rag/datasource/vdb/test_vector_base.py", + "core/rag/datasource/vdb/test_vector_factory.py", + "core/rag/docstore/test_dataset_docstore.py", + "core/rag/embedding/test_cached_embedding.py", + "core/rag/embedding/test_embedding_base.py", + "core/rag/embedding/test_embedding_service.py", + "core/rag/extractor/blob/test_blob.py", + "core/rag/extractor/firecrawl/test_firecrawl.py", + "core/rag/extractor/test_csv_extractor.py", + "core/rag/extractor/test_excel_extractor.py", + "core/rag/extractor/test_extract_processor.py", + "core/rag/extractor/test_extractor_base.py", + "core/rag/extractor/test_helpers.py", + "core/rag/extractor/test_html_extractor.py", + "core/rag/extractor/test_jina_reader_extractor.py", + "core/rag/extractor/test_markdown_extractor.py", + "core/rag/extractor/test_notion_extractor.py", + "core/rag/extractor/test_pdf_extractor.py", + "core/rag/extractor/test_text_extractor.py", + "core/rag/extractor/test_word_extractor.py", + "core/rag/extractor/unstructured/test_unstructured_extractors.py", + "core/rag/extractor/watercrawl/test_watercrawl.py", + "core/rag/indexing/processor/test_paragraph_index_processor.py", + "core/rag/indexing/processor/test_qa_index_processor.py", + "core/rag/indexing/test_index_processor.py", + "core/rag/indexing/test_index_processor_base.py", + "core/rag/indexing/test_indexing_runner.py", + "core/rag/pipeline/test_queue.py", + "core/rag/rerank/test_reranker.py", + "core/rag/retrieval/test_dataset_retrieval.py", + "core/rag/retrieval/test_dataset_retrieval_methods.py", + "core/rag/retrieval/test_multi_dataset_function_call_router.py", + "core/rag/retrieval/test_multi_dataset_react_route.py", + "core/rag/splitter/test_text_splitter.py", + "core/repositories/test_celery_workflow_execution_repository.py", + "core/repositories/test_celery_workflow_node_execution_repository.py", + "core/repositories/test_factory.py", + "core/repositories/test_human_input_form_repository_impl.py", + "core/repositories/test_human_input_repository.py", + "core/repositories/test_sqlalchemy_workflow_execution_repository.py", + "core/repositories/test_sqlalchemy_workflow_node_execution_repository.py", + "core/repositories/test_workflow_node_execution_conflict_handling.py", + "core/repositories/test_workflow_node_execution_truncation.py", + "core/schemas/test_registry.py", + "core/schemas/test_resolver.py", + "core/schemas/test_schema_manager.py", + "core/telemetry/test_facade.py", + "core/telemetry/test_gateway_integration.py", + "core/test_file.py", + "core/test_model_manager.py", + "core/test_provider_configuration.py", + "core/test_provider_manager.py", + "core/test_trigger_debug_event_selectors.py", + "core/tools/entities/test_api_entities.py", + "core/tools/test_base_tool.py", + "core/tools/test_builtin_tool_base.py", + "core/tools/test_builtin_tool_provider.py", + "core/tools/test_builtin_tools_extra.py", + "core/tools/test_custom_tool.py", + "core/tools/test_custom_tool_provider.py", + "core/tools/test_dataset_retriever_tool.py", + "core/tools/test_mcp_tool.py", + "core/tools/test_mcp_tool_provider.py", + "core/tools/test_plugin_tool.py", + "core/tools/test_plugin_tool_provider.py", + "core/tools/test_tool_engine.py", + "core/tools/test_tool_entities.py", + "core/tools/test_tool_file_manager.py", + "core/tools/test_tool_label_manager.py", + "core/tools/test_tool_manager.py", + "core/tools/test_tool_parameter_type.py", + "core/tools/test_tool_provider_controller.py", + "core/tools/utils/test_configuration.py", + "core/tools/utils/test_encryption.py", + "core/tools/utils/test_message_transformer.py", + "core/tools/utils/test_misc_utils_extra.py", + "core/tools/utils/test_model_invocation_utils.py", + "core/tools/utils/test_parser.py", + "core/tools/utils/test_system_oauth_encryption.py", + "core/tools/utils/test_tool_engine_serialization.py", + "core/tools/utils/test_web_reader_tool.py", + "core/tools/utils/test_workflow_configuration_sync.py", + "core/tools/workflow_as_tool/test_provider.py", + "core/tools/workflow_as_tool/test_tool.py", + "core/trigger/conftest.py", + "core/trigger/debug/test_debug_event_bus.py", + "core/trigger/debug/test_debug_event_selectors.py", + "core/trigger/test_provider.py", + "core/trigger/test_trigger_manager.py", + "core/trigger/utils/test_utils_encryption.py", + "core/trigger/utils/test_utils_endpoint.py", + "core/trigger/utils/test_utils_locks.py", + "core/variables/test_segment.py", + "core/variables/test_segment_type.py", + "core/variables/test_segment_type_validation.py", + "core/variables/test_variables.py", + "core/workflow/context/test_execution_context.py", + "core/workflow/context/test_flask_app_context.py", + "core/workflow/entities/test_private_workflow_pause.py", + "core/workflow/generator/test_prompts.py", + "core/workflow/generator/test_runner.py", + "core/workflow/generator/test_runner_missing.py", + "core/workflow/generator/test_tool_catalogue.py", + "core/workflow/graph_engine/layers/conftest.py", + "core/workflow/graph_engine/layers/test_llm_quota.py", + "core/workflow/graph_engine/layers/test_observability.py", + "core/workflow/graph_engine/test_mock_config.py", + "core/workflow/graph_engine/test_mock_factory.py", + "core/workflow/graph_engine/test_mock_nodes.py", + "core/workflow/graph_engine/test_parallel_human_input_join_resume.py", + "core/workflow/graph_engine/test_table_runner.py", + "core/workflow/graph_engine/test_tool_in_chatflow.py", + "core/workflow/nodes/agent/test_message_transformer.py", + "core/workflow/nodes/agent/test_runtime_support.py", + "core/workflow/nodes/agent_v2/test_agent_node.py", + "core/workflow/nodes/agent_v2/test_binding_resolver.py", + "core/workflow/nodes/agent_v2/test_dify_tools_builder.py", + "core/workflow/nodes/agent_v2/test_file_tenant_validator.py", + "core/workflow/nodes/agent_v2/test_output_adapter.py", + "core/workflow/nodes/agent_v2/test_output_failure_orchestrator.py", + "core/workflow/nodes/agent_v2/test_output_file_rebacker.py", + "core/workflow/nodes/agent_v2/test_output_type_checker.py", + "core/workflow/nodes/agent_v2/test_runtime_request_builder.py", + "core/workflow/nodes/agent_v2/test_session_cleanup_layer.py", + "core/workflow/nodes/agent_v2/test_session_store.py", + "core/workflow/nodes/agent_v2/test_validators.py", + "core/workflow/nodes/answer/test_answer.py", + "core/workflow/nodes/base/test_base_node.py", + "core/workflow/nodes/base/test_get_node_type_classes_mapping.py", + "core/workflow/nodes/code/code_node_spec.py", + "core/workflow/nodes/datasource/test_datasource_node.py", + "core/workflow/nodes/http_request/test_http_request_executor.py", + "core/workflow/nodes/http_request/test_http_request_node.py", + "core/workflow/nodes/human_input/test_dify_owned_contracts.py", + "core/workflow/nodes/human_input/test_email_delivery_config.py", + "core/workflow/nodes/human_input/test_entities.py", + "core/workflow/nodes/human_input/test_human_input_form_filled_event.py", + "core/workflow/nodes/iteration/test_iteration_child_engine_errors.py", + "core/workflow/nodes/knowledge_index/test_knowledge_index_node.py", + "core/workflow/nodes/knowledge_retrieval/test_knowledge_retrieval_node.py", + "core/workflow/nodes/list_operator/node_spec.py", + "core/workflow/nodes/llm/test_llm_utils.py", + "core/workflow/nodes/llm/test_node.py", + "core/workflow/nodes/parameter_extractor/test_parameter_extractor_node.py", + "core/workflow/nodes/template_transform/template_transform_node_spec.py", + "core/workflow/nodes/template_transform/test_template_transform_node.py", + "core/workflow/nodes/test_base_node.py", + "core/workflow/nodes/test_document_extractor_node.py", + "core/workflow/nodes/test_if_else.py", + "core/workflow/nodes/test_list_operator.py", + "core/workflow/nodes/test_start_node_json_object.py", + "core/workflow/nodes/tool/test_tool_node.py", + "core/workflow/nodes/tool/test_tool_node_runtime.py", + "core/workflow/nodes/webhook/test_entities.py", + "core/workflow/nodes/webhook/test_exceptions.py", + "core/workflow/nodes/webhook/test_webhook_file_conversion.py", + "core/workflow/nodes/webhook/test_webhook_node.py", + "core/workflow/test_enrich_pause_reasons.py", + "core/workflow/test_form_input_serialization_compat.py", + "core/workflow/test_graph_topology.py", + "core/workflow/test_human_input_adapter.py", + "core/workflow/test_human_input_callback.py", + "core/workflow/test_human_input_forms.py", + "core/workflow/test_node_factory.py", + "core/workflow/test_node_mapping_bootstrap.py", + "core/workflow/test_node_runtime.py", + "core/workflow/test_system_variable.py", + "core/workflow/test_variable_pool.py", + "core/workflow/test_workflow_entry.py", + "core/workflow/test_workflow_entry_helpers.py", + "core/workflow/test_workflow_entry_redis_channel.py", + "dev/test_generate_knowledge_fs_contract.py", + "enterprise/telemetry/test_contracts.py", + "enterprise/telemetry/test_draft_trace.py", + "enterprise/telemetry/test_enterprise_trace.py", + "enterprise/telemetry/test_event_handlers.py", + "enterprise/telemetry/test_exporter.py", + "enterprise/telemetry/test_gateway.py", + "enterprise/telemetry/test_metric_handler.py", + "enums/test_quota_type.py", + "events/event_handlers/test_clean_when_document_deleted.py", + "events/event_handlers/test_delete_tool_parameters_cache_when_sync_draft_workflow.py", + "events/test_app_event_signals.py", + "events/test_events_package_compat.py", + "events/test_update_provider_when_message_created.py", + "extensions/logstore/repositories/test_logstore_api_workflow_node_execution_repository.py", + "extensions/logstore/test_sql_escape.py", + "extensions/otel/conftest.py", + "extensions/otel/decorators/handlers/test_generate_handler.py", + "extensions/otel/decorators/handlers/test_workflow_app_runner_handler.py", + "extensions/otel/decorators/test_base.py", + "extensions/otel/decorators/test_handler.py", + "extensions/otel/test_celery_sqlcommenter.py", + "extensions/otel/test_context.py", + "extensions/otel/test_retrieval_tracing.py", + "extensions/otel/test_runtime.py", + "extensions/storage/test_supabase_storage.py", + "extensions/test_celery_ssl.py", + "extensions/test_ext_blueprints_openapi.py", + "extensions/test_ext_login.py", + "extensions/test_ext_request_logging.py", + "extensions/test_ext_socketio.py", + "extensions/test_pubsub_channel.py", + "extensions/test_redis.py", + "extensions/test_set_secretkey.py", + "extensions/test_workflow_warm_shutdown.py", + "factories/test_build_from_mapping.py", + "factories/test_file_factory.py", + "factories/test_file_validation.py", + "factories/test_variable_factory.py", + "fields/test_dataset_fields.py", + "fields/test_file_fields.py", + "fields/test_message_fields.py", + "libs/_human_input/support.py", + "libs/_human_input/test_form_service.py", + "libs/_human_input/test_models.py", + "libs/broadcast_channel/redis/test_channel_unit_tests.py", + "libs/broadcast_channel/redis/test_streams_channel_unit_tests.py", + "libs/test_api_token_cache.py", + "libs/test_archive_storage.py", + "libs/test_cron_compatibility.py", + "libs/test_custom_inputs.py", + "libs/test_datetime_utils.py", + "libs/test_email.py", + "libs/test_email_i18n.py", + "libs/test_encryption.py", + "libs/test_external_api.py", + "libs/test_file_utils.py", + "libs/test_flask_utils.py", + "libs/test_helper.py", + "libs/test_json_in_md_parser.py", + "libs/test_jwt_imports.py", + "libs/test_login.py", + "libs/test_oauth_base.py", + "libs/test_oauth_bearer.py", + "libs/test_oauth_bearer_layer0_cache.py", + "libs/test_oauth_bearer_rate_limit_ordering.py", + "libs/test_oauth_bearer_require_scope.py", + "libs/test_oauth_clients.py", + "libs/test_orjson.py", + "libs/test_pagination.py", + "libs/test_pandas.py", + "libs/test_passport.py", + "libs/test_password.py", + "libs/test_rate_limit_bearer.py", + "libs/test_rate_limiter.py", + "libs/test_rsa.py", + "libs/test_schedule_utils_enhanced.py", + "libs/test_sendgrid_client.py", + "libs/test_smtp_client.py", + "libs/test_time_parser.py", + "libs/test_token.py", + "libs/test_token_manager.py", + "libs/test_uuid_utils.py", + "libs/test_workspace_member_helper.py", + "libs/test_workspace_permission.py", + "libs/test_yarl.py", + "migrations/test_agent_drive_skill_metadata_refactor.py", + "migrations/test_uuidv7_pg18_migration.py", + "models/test_account_models.py", + "models/test_agent.py", + "models/test_app_models.py", + "models/test_base.py", + "models/test_conversation_variable.py", + "models/test_dataset_models.py", + "models/test_end_user_type.py", + "models/test_enums_creator_user_role.py", + "models/test_model.py", + "models/test_plugin_entities.py", + "models/test_provider_models.py", + "models/test_tool_models.py", + "models/test_types.py", + "models/test_workflow.py", + "models/test_workflow_models.py", + "models/test_workflow_node_execution_offload.py", + "oss/__mock/aliyun_oss.py", + "oss/__mock/baidu_obs.py", + "oss/__mock/base.py", + "oss/__mock/local.py", + "oss/__mock/tencent_cos.py", + "oss/__mock/volcengine_tos.py", + "oss/aliyun_oss/aliyun_oss/test_aliyun_oss.py", + "oss/baidu_obs/test_baidu_obs.py", + "oss/opendal/test_opendal.py", + "oss/tencent_cos/test_tencent_cos.py", + "oss/volcengine_tos/test_volcengine_tos.py", + "repositories/test_sqlalchemy_api_workflow_run_repository.py", + "services/agent/test_agent_composer_entities.py", + "services/agent/test_agent_dsl_service.py", + "services/agent/test_agent_observability_service.py", + "services/agent/test_agent_services.py", + "services/agent/test_composer_candidates.py", + "services/agent/test_composer_mention_validation.py", + "services/agent/test_prompt_mentions.py", + "services/agent/test_skill_package_service.py", + "services/agent/test_skill_standardize_service.py", + "services/agent/test_skill_tool_inference_service.py", + "services/agent/test_workflow_publish_service.py", + "services/auth/test_api_key_auth_base.py", + "services/auth/test_api_key_auth_factory.py", + "services/auth/test_api_key_auth_service.py", + "services/auth/test_auth_type.py", + "services/auth/test_firecrawl_auth.py", + "services/auth/test_jina_auth.py", + "services/auth/test_jina_auth_standalone_module.py", + "services/auth/test_watercrawl_auth.py", + "services/controller_api.py", + "services/data_migration/test_dependency_discovery_service.py", + "services/data_migration/test_entities.py", + "services/data_migration/test_export_service.py", + "services/data_migration/test_import_service.py", + "services/data_migration/test_package_service.py", + "services/data_migration/test_report_service.py", + "services/dataset_service_test_helpers.py", + "services/document_service_validation.py", + "services/enterprise/test_account_deletion_sync.py", + "services/enterprise/test_app_permitted_service.py", + "services/enterprise/test_enterprise_service.py", + "services/enterprise/test_plugin_manager_service.py", + "services/enterprise/test_rbac_service.py", + "services/enterprise/test_traceparent_propagation.py", + "services/hit_service.py", + "services/openapi/test_mint_policy.py", + "services/plugin/conftest.py", + "services/plugin/test_dependencies_analysis.py", + "services/plugin/test_endpoint_service.py", + "services/plugin/test_oauth_service.py", + "services/plugin/test_plugin_migration.py", + "services/plugin/test_plugin_parameter_service.py", + "services/plugin/test_plugin_service.py", + "services/plugin/test_plugin_service_installation.py", + "services/rag_pipeline/pipeline_template/test_built_in_retrieval.py", + "services/rag_pipeline/pipeline_template/test_pipeline_template_base.py", + "services/rag_pipeline/test_pipeline_generate_service.py", + "services/rag_pipeline/test_rag_pipeline_dsl_service.py", + "services/rag_pipeline/test_rag_pipeline_service.py", + "services/rag_pipeline/test_rag_pipeline_task_proxy.py", + "services/rag_pipeline/test_rag_pipeline_transform_service.py", + "services/recommend_app/test_buildin_retrieval.py", + "services/recommend_app/test_category_order.py", + "services/recommend_app/test_recommend_app_factory.py", + "services/recommend_app/test_recommend_app_type.py", + "services/recommend_app/test_remote_retrieval.py", + "services/retention/test_messages_clean_policy.py", + "services/retention/workflow_run/test_archive_download_preparation.py", + "services/retention/workflow_run/test_archive_download_task_cache.py", + "services/retention/workflow_run/test_archive_log_service.py", + "services/retention/workflow_run/test_bundle_archive_maintenance.py", + "services/retention/workflow_run/test_clear_free_plan_expired_workflow_run_logs.py", + "services/retention/workflow_run/test_restore_archived_workflow_run.py", + "services/test_account_service.py", + "services/test_agent_app_feature_service.py", + "services/test_agent_app_sandbox_service.py", + "services/test_agent_config_service.py", + "services/test_agent_drive_service.py", + "services/test_agent_file_request_service.py", + "services/test_annotation_service.py", + "services/test_api_token_service.py", + "services/test_app_dsl_service.py", + "services/test_app_generate_service.py", + "services/test_app_generate_service_streaming_integration.py", + "services/test_app_model_config_service.py", + "services/test_app_service.py", + "services/test_app_task_service.py", + "services/test_archive_workflow_run_logs.py", + "services/test_async_workflow_service.py", + "services/test_audio_service.py", + "services/test_billing_service.py", + "services/test_clear_free_plan_expired_workflow_run_logs.py", + "services/test_clear_free_plan_tenant_expired_logs.py", + "services/test_code_based_extension_service.py", + "services/test_conversation_service.py", + "services/test_credential_permission_service.py", + "services/test_credit_pool_service.py", + "services/test_dataset_service_dataset.py", + "services/test_dataset_service_document.py", + "services/test_dataset_service_lock_not_owned.py", + "services/test_dataset_service_segment.py", + "services/test_datasource_provider_service.py", + "services/test_document_indexing_task_proxy.py", + "services/test_duplicate_document_indexing_task_proxy.py", + "services/test_export_app_messages.py", + "services/test_external_dataset_service.py", + "services/test_feature_service_app_dsl_version.py", + "services/test_feature_service_enable_app_deploy.py", + "services/test_feature_service_human_input_email_delivery.py", + "services/test_feature_service_learn_app.py", + "services/test_feature_service_licensed_seats.py", + "services/test_feature_service_trial_models.py", + "services/test_feature_service_vector_space.py", + "services/test_feature_service_webapp_public_access.py", + "services/test_feedback_service.py", + "services/test_file_service.py", + "services/test_human_input_delivery_test_service.py", + "services/test_human_input_file_upload_service.py", + "services/test_human_input_service.py", + "services/test_knowledge_retrieval_inner_service.py", + "services/test_knowledge_service.py", + "services/test_message_service.py", + "services/test_messages_clean_service.py", + "services/test_metadata_nullable_bug.py", + "services/test_metadata_service_session_boundary.py", + "services/test_model_load_balancing_service.py", + "services/test_model_provider_service.py", + "services/test_model_provider_service_sanitization.py", + "services/test_oauth_device_flow.py", + "services/test_oauth_server_service.py", + "services/test_operation_service.py", + "services/test_rag_pipeline_task_proxy.py", + "services/test_schedule_service.py", + "services/test_snippet_dsl_service.py", + "services/test_snippet_generate_service.py", + "services/test_snippet_service.py", + "services/test_step_by_step_tour_service.py", + "services/test_summary_index_service.py", + "services/test_telemetry_service.py", + "services/test_trigger_provider_service.py", + "services/test_variable_truncator.py", + "services/test_vector_service.py", + "services/test_webhook_service.py", + "services/test_webhook_service_additional.py", + "services/test_website_service.py", + "services/test_workflow_app_service_metadata.py", + "services/test_workflow_collaboration_service.py", + "services/test_workflow_comment_service.py", + "services/test_workflow_generator_service.py", + "services/test_workflow_node_execution_trace_service.py", + "services/test_workflow_run_service.py", + "services/test_workflow_run_service_pause.py", + "services/test_workflow_service.py", + "services/tools/test_api_tools_manage_service.py", + "services/tools/test_builtin_tools_manage_service.py", + "services/tools/test_mcp_tools_transform.py", + "services/tools/test_tool_labels_service.py", + "services/tools/test_tools_manage_service.py", + "services/tools/test_tools_transform_service.py", + "services/workflow/test_draft_var_loader_simple.py", + "services/workflow/test_inspector_events.py", + "services/workflow/test_node_output_inspector_service.py", + "services/workflow/test_queue_dispatcher.py", + "services/workflow/test_scheduler.py", + "services/workflow/test_workflow_converter_additional.py", + "services/workflow/test_workflow_draft_variable_service.py", + "services/workflow/test_workflow_event_snapshot_service.py", + "services/workflow/test_workflow_event_snapshot_service_additional.py", + "services/workflow/test_workflow_human_input_delivery.py", + "services/workflow/test_workflow_restore.py", + "tasks/test_agent_backend_session_cleanup_task.py", + "tasks/test_async_workflow_tasks.py", + "tasks/test_batch_clean_document_task.py", + "tasks/test_clean_dataset_task.py", + "tasks/test_clean_document_task.py", + "tasks/test_community_telemetry_task.py", + "tasks/test_dataset_indexing_task.py", + "tasks/test_document_indexing_sync_task.py", + "tasks/test_document_indexing_update_task.py", + "tasks/test_duplicate_document_indexing_task.py", + "tasks/test_enable_segment_index_tasks.py", + "tasks/test_enterprise_telemetry_task.py", + "tasks/test_human_input_timeout_tasks.py", + "tasks/test_initialize_created_app_rbac_access_task.py", + "tasks/test_install_default_plugins_task.py", + "tasks/test_mail_human_input_delivery_task.py", + "tasks/test_mail_send_task.py", + "tasks/test_ops_trace_task.py", + "tasks/test_process_tenant_plugin_autoupgrade_check_task.py", + "tasks/test_refresh_billing_vector_space_task.py", + "tasks/test_remove_app_and_related_data_task.py", + "tasks/test_resume_agent_app_task.py", + "tasks/test_summary_queue_isolation.py", + "tasks/test_trigger_processing_tasks.py", + "tasks/test_workflow_execute_task.py", + "test_app_factory.py", + "test_makefile_backend_tests.py", + "test_pytest_dify.py", + "tools/test_api_tool.py", + "tools/test_mcp_tool.py", + "utils/encryption/test_system_encryption.py", + "utils/http_parser/test_oauth_convert_request_to_raw_data.py", + "utils/position_helper/test_position_helper.py", + "utils/structured_output_parser/test_structured_output_parser.py", + "utils/test_text_processing.py", + "utils/yaml/test_yaml_utils.py", +] + +[errors] +missing-override-decorator = "error" +redundant-cast = true +unannotated-return = true +unnecessary-type-conversion = true +unused-ignore = true diff --git a/dev/pyrefly-check-local b/dev/pyrefly-check-local index 4b975792701..b14dfba996f 100755 --- a/dev/pyrefly-check-local +++ b/dev/pyrefly-check-local @@ -7,6 +7,7 @@ REPO_ROOT="$SCRIPT_DIR/.." cd "$REPO_ROOT" EXCLUDES_FILE="api/pyrefly-local-excludes.txt" +UNIT_TESTS_CONFIG="tests/unit_tests/pyrefly.toml" TEST_CONTAINERS_DIR="tests/test_containers_integration_tests" TEST_CONTAINERS_CONFIG="$TEST_CONTAINERS_DIR/pyrefly.toml" @@ -74,6 +75,19 @@ fi run_pyrefly "${pyrefly_command[@]}" || status=$? if (( ${#target_paths[@]} == 0 )); then + unit_tests_args=( + "--summary=none" + "--use-ignore-files=false" + "--config=$UNIT_TESTS_CONFIG" + ) + if [[ "${PYREFLY_OUTPUT_FORMAT:-}" == "github" ]]; then + unit_tests_args+=("--output-format=github") + fi + run_pyrefly \ + uv run --directory api --dev pyrefly check \ + "${unit_tests_args[@]}" \ + || status=$? + test_containers_args=( "--summary=none" "--use-ignore-files=false" From 798e5ed7a736dbb88f7e5194dc3bd5d4fe3d6eb9 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Tue, 28 Jul 2026 17:32:03 +0800 Subject: [PATCH 05/17] refactor(web): derive workspace billing from current workspace (#39674) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../__tests__/workspace-card.spec.tsx | 42 +++++-------------- .../main-nav/components/workspace-card.tsx | 17 ++++---- 2 files changed, 18 insertions(+), 41 deletions(-) diff --git a/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx b/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx index dfc1b35d04b..fb9205b2248 100644 --- a/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx +++ b/web/app/components/main-nav/components/__tests__/workspace-card.spec.tsx @@ -1,6 +1,7 @@ +import type { PostWorkspacesCurrentResponse } from '@dify/contracts/api/console/workspaces/types.gen' import type { ModalContextState } from '@/context/modal-context' import type { ProviderContextState } from '@/context/provider-context' -import type { ICurrentWorkspace, IWorkspace } from '@/models/common' +import type { IWorkspace } from '@/models/common' import { fireEvent, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { Plan } from '@/app/components/billing/type' @@ -95,14 +96,13 @@ vi.mock('@/service/client', async (importOriginal) => { } }) -const currentWorkspaceValue: ICurrentWorkspace = { +const currentWorkspaceValue: PostWorkspacesCurrentResponse = { id: 'workspace-1', name: 'Solar Studio', plan: Plan.sandbox, status: 'normal', created_at: 0, role: 'owner', - providers: [], trial_credits: 10000, trial_credits_used: 2500, trial_credits_exhausted_at: 0, @@ -111,11 +111,11 @@ const currentWorkspaceValue: ICurrentWorkspace = { const mockSetShowPricingModal = vi.fn() const mockSetShowAccountSettingModal = vi.fn() -let mockCurrentWorkspace: ICurrentWorkspace | undefined = currentWorkspaceValue +let mockCurrentWorkspace: PostWorkspacesCurrentResponse | undefined = currentWorkspaceValue let mockWorkspaces: IWorkspace[] = [] const mockCurrentWorkspaceQuery = ( - data: ICurrentWorkspace | undefined = currentWorkspaceValue, + data: PostWorkspacesCurrentResponse | undefined = currentWorkspaceValue, isPending = false, ) => { mockCurrentWorkspace = isPending ? undefined : data @@ -284,13 +284,12 @@ describe('WorkspaceCard', () => { plan: Plan.team, }) vi.mocked(useProviderContext).mockReturnValue({ - enableBilling: true, + enableBilling: false, isEducationAccount: false, isEducationWorkspace: false, isFetchedPlan: true, - plan: { type: Plan.team }, + plan: { type: Plan.sandbox }, } as ProviderContextState) - renderWorkspaceCard({ systemFeatures: { deployment_edition: 'CLOUD' } }) expect(screen.getByText(Plan.team)).toBeInTheDocument() @@ -304,14 +303,6 @@ describe('WorkspaceCard', () => { ...currentWorkspaceValue, plan: Plan.team, }) - vi.mocked(useProviderContext).mockReturnValue({ - enableBilling: true, - isEducationAccount: false, - isEducationWorkspace: false, - isFetchedPlan: true, - plan: { type: Plan.team }, - } as ProviderContextState) - renderWorkspaceCard({ systemFeatures: { deployment_edition: 'CLOUD' } }) expect(screen.getByText(Plan.team)).toBeInTheDocument() @@ -322,14 +313,6 @@ describe('WorkspaceCard', () => { ...currentWorkspaceValue, plan: '', }) - vi.mocked(useProviderContext).mockReturnValue({ - enableBilling: true, - isEducationAccount: false, - isEducationWorkspace: false, - isFetchedPlan: false, - plan: { type: Plan.sandbox }, - } as ProviderContextState) - renderWorkspaceCard({ systemFeatures: { deployment_edition: 'ENTERPRISE', @@ -471,13 +454,10 @@ describe('WorkspaceCard', () => { }) it('opens members settings from workspace menu when billing is disabled', async () => { - vi.mocked(useProviderContext).mockReturnValue({ - enableBilling: false, - isEducationAccount: false, - isEducationWorkspace: false, - isFetchedPlan: false, - plan: { type: Plan.sandbox }, - } as ProviderContextState) + mockCurrentWorkspaceQuery({ + ...currentWorkspaceValue, + plan: null, + }) renderWorkspaceCard() diff --git a/web/app/components/main-nav/components/workspace-card.tsx b/web/app/components/main-nav/components/workspace-card.tsx index 4c446efa3e5..301a57ef2b7 100644 --- a/web/app/components/main-nav/components/workspace-card.tsx +++ b/web/app/components/main-nav/components/workspace-card.tsx @@ -16,7 +16,6 @@ import LicenseNav from '@/app/components/header/license-env' import { buildIntegrationPath } from '@/app/components/integrations/routes' import { useModalContext } from '@/context/modal-context' import { workspacePermissionKeysAtom } from '@/context/permission-state' -import { useProviderContext } from '@/context/provider-context' import { systemFeaturesQueryOptions } from '@/features/system-features/client' import Link from '@/next/link' import { consoleQuery } from '@/service/client' @@ -259,26 +258,24 @@ export function WorkspaceCard() { const switchWorkspaceMutation = useMutation(consoleQuery.workspaces.switch.post.mutationOptions()) const currentWorkspace = currentWorkspaceQuery.data const workspaces = workspacesQuery.data?.workspaces - const { enableBilling } = useProviderContext() const workspacePermissionKeys = useAtomValue(workspacePermissionKeysAtom) const { setShowPricingModal, setShowAccountSettingModal } = useModalContext() - const showCloudBilling = deploymentEdition === 'CLOUD' && enableBilling + const isCloudEdition = deploymentEdition === 'CLOUD' const prefetchWorkspaces = () => { void queryClient.prefetchQuery(workspacesQueryOptions) } if (currentWorkspaceQuery.isPending || !currentWorkspace?.name) { return ( - + ) } const workspacePlan = isWorkspacePlan(currentWorkspace.plan) ? currentWorkspace.plan : null - const isFreePlan = workspacePlan === Plan.sandbox + const hasBillingPlan = typeof currentWorkspace.plan === 'string' + const showCloudBilling = isCloudEdition && hasBillingPlan const showPlanAction = showCloudBilling && workspacePlan !== null + const isFreePlan = workspacePlan === Plan.sandbox const planActionLabel = t( ($) => $[isFreePlan ? 'upgradeBtn.encourageShort' : 'upgradeBtn.plain'], { ns: 'billing' }, @@ -286,7 +283,7 @@ export function WorkspaceCard() { const showInviteMembers = hasPermission(workspacePermissionKeys, 'workspace.member.manage') const renderWorkspaceStatus = () => { if (deploymentEdition === 'CLOUD') - return enableBilling && workspacePlan ? : null + return workspacePlan ? : null if (deploymentEdition === 'ENTERPRISE') return return null } @@ -333,7 +330,7 @@ export function WorkspaceCard() { onOpenSettings={() => { setOpen(false) setShowAccountSettingModal({ - payload: enableBilling ? ACCOUNT_SETTING_TAB.BILLING : ACCOUNT_SETTING_TAB.MEMBERS, + payload: hasBillingPlan ? ACCOUNT_SETTING_TAB.BILLING : ACCOUNT_SETTING_TAB.MEMBERS, }) }} onInviteMembers={() => { From 3bc8c69def6d6c0ad739db9ee73b0871c3b2577c Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 28 Jul 2026 18:06:46 +0800 Subject: [PATCH 06/17] feat: label agent conversation reset action on hover (#39691) --- .../preview/__tests__/header.spec.tsx | 16 +++++++++++ .../configure/components/preview/header.tsx | 27 +++++++++++++------ web/i18n/ar-TN/agent-v-2.json | 2 +- web/i18n/de-DE/agent-v-2.json | 2 +- web/i18n/en-US/agent-v-2.json | 2 +- web/i18n/es-ES/agent-v-2.json | 2 +- web/i18n/fa-IR/agent-v-2.json | 2 +- web/i18n/fr-FR/agent-v-2.json | 2 +- web/i18n/hi-IN/agent-v-2.json | 2 +- web/i18n/id-ID/agent-v-2.json | 2 +- web/i18n/it-IT/agent-v-2.json | 2 +- web/i18n/ja-JP/agent-v-2.json | 2 +- web/i18n/ko-KR/agent-v-2.json | 2 +- web/i18n/nl-NL/agent-v-2.json | 2 +- web/i18n/pl-PL/agent-v-2.json | 2 +- web/i18n/pt-BR/agent-v-2.json | 2 +- web/i18n/ro-RO/agent-v-2.json | 2 +- web/i18n/ru-RU/agent-v-2.json | 2 +- web/i18n/sl-SI/agent-v-2.json | 2 +- web/i18n/th-TH/agent-v-2.json | 2 +- web/i18n/tr-TR/agent-v-2.json | 2 +- web/i18n/uk-UA/agent-v-2.json | 2 +- web/i18n/vi-VN/agent-v-2.json | 2 +- web/i18n/zh-Hans/agent-v-2.json | 2 +- web/i18n/zh-Hant/agent-v-2.json | 2 +- 25 files changed, 58 insertions(+), 31 deletions(-) diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/header.spec.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/header.spec.tsx index 1d4a503b779..8b93790baec 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/header.spec.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/__tests__/header.spec.tsx @@ -76,6 +76,22 @@ describe('AgentPreviewHeader', () => { expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument() }) + it.each(['build', 'preview'] as const)( + 'should show the start-fresh tooltip on hover in %s mode', + async (mode) => { + const user = userEvent.setup() + renderHeader({ mode }) + + await user.hover( + screen.getByRole('button', { name: 'agentV2.agentDetail.configure.preview.restart' }), + ) + + expect( + await screen.findByText('agentV2.agentDetail.configure.preview.restart'), + ).toBeInTheDocument() + }, + ) + it('should not emit refresh when the restart button is disabled', async () => { const user = userEvent.setup() const onRefresh = vi.fn() diff --git a/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx b/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx index 084db8fe25d..3f5a7ff0b5b 100644 --- a/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx +++ b/web/features/agent-v2/agent-detail/configure/components/preview/header.tsx @@ -6,6 +6,7 @@ import { SegmentedControlDivider, SegmentedControlItem, } from '@langgenius/dify-ui/segmented-control' +import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { useTranslation } from 'react-i18next' import { useDocLink } from '@/context/i18n' import { AgentConfigureClearSessionConfirmDialog } from '../confirm-clear-session-dialog' @@ -138,6 +139,7 @@ export function AgentPreviewHeader({ const previewTipBody = t(($) => $['agentDetail.configure.rightPanel.previewTipBody']) const previewDisabledTip = t(($) => $['agentDetail.configure.rightPanel.previewDisabledTip']) const learnMoreLabel = t(($) => $['agentDetail.configure.rightPanel.learnMore']) + const restartLabel = t(($) => $['agentDetail.configure.preview.restart']) const modeTip = `${buildLabel}. ${buildTipBody} ${learnMoreLabel} ${previewLabel}. ${previewTipBody}` const restartButton = ( @@ -202,13 +204,22 @@ export function AgentPreviewHeader({
- {mode === 'preview' ? ( - restartButton - ) : ( - - {restartButton} - - )} + + + {mode === 'preview' ? ( + restartButton + ) : ( + + {restartButton} + + )} + + } + /> + {restartLabel} + {mode === 'build' && showWorkingDirectoryAction && (