diff --git a/e2e/features/agent-v2/build-draft.feature b/e2e/features/agent-v2/build-draft.feature index 918f46b5efb..0968a32ccc8 100644 --- a/e2e/features/agent-v2/build-draft.feature +++ b/e2e/features/agent-v2/build-draft.feature @@ -10,18 +10,20 @@ Feature: Agent v2 build draft Then Agent v2 Build chat should be blocked until a model is configured And the Agent v2 Build draft should not be checked out - @external-model @agent-backend-runtime @stable-model - Scenario: Generating a Build draft leaves the normal Agent configuration unchanged + @external-model @agent-backend-runtime @agent-decision-model + Scenario: Generating a Build note draft leaves the normal Agent configuration unchanged Given I am signed in as the default E2E admin - And the Agent Builder stable chat model is available + And the Agent Builder agent-decision chat model is available And the Agent v2 runtime backend is available - And a runnable Agent v2 test agent has been created via API + And a runnable Agent v2 test agent using the agent-decision model has been created via API When I open the Agent v2 configure page And I generate an Agent v2 Build draft from the fixed instruction Then I should see the Agent v2 Build draft pending changes And I should see the Agent v2 Build mode confirmation state + And the Agent v2 Build draft should include the generated build note + And I should see the generated Agent v2 build note in Configure And the normal Agent v2 draft should still use the normal E2E prompt - And the normal Agent v2 draft should not include the e2e-summary-skill Skill + And the normal Agent v2 draft should not include the generated build note @core Scenario: Discarding a Build draft keeps the original Agent configuration diff --git a/e2e/features/agent-v2/support/agent-build-draft.ts b/e2e/features/agent-v2/support/agent-build-draft.ts index b22d3c7d543..d548be5a367 100644 --- a/e2e/features/agent-v2/support/agent-build-draft.ts +++ b/e2e/features/agent-v2/support/agent-build-draft.ts @@ -54,6 +54,18 @@ export async function agentBuildDraftExists(agentId: string): Promise { } } +export async function getAgentBuildDraft(agentId: string): Promise { + const ctx = await createApiContext() + try { + const response = await ctx.get(`/console/api/agent/${agentId}/build-draft`) + await expectApiResponseOK(response, `Get Agent v2 build draft for ${agentId}`) + return (await response.json()) as AgentBuildDraftResponse + } + finally { + await ctx.dispose() + } +} + export async function applyAgentBuildDraft(agentId: string): Promise { const ctx = await createApiContext() try { diff --git a/e2e/features/step-definitions/agent-v2/build-draft.steps.ts b/e2e/features/step-definitions/agent-v2/build-draft.steps.ts index 6071f072876..261697d5036 100644 --- a/e2e/features/step-definitions/agent-v2/build-draft.steps.ts +++ b/e2e/features/step-definitions/agent-v2/build-draft.steps.ts @@ -10,6 +10,7 @@ import { import { agentBuildDraftExists, applyAgentBuildDraft, + getAgentBuildDraft, saveAgentBuildDraft, } from '../../agent-v2/support/agent-build-draft' import { agentBuilderFixedInputs, agentBuilderPreseededResources } from '../../agent-v2/support/agent-builder-resources' @@ -33,10 +34,22 @@ import { } from './configure-helpers' const BUILD_DRAFT_RUNTIME_STEP_TIMEOUT_MS = 180_000 +const BUILD_NOTE_FILE_NAME = 'build_note.md' +const BUILD_NOTE_MARKER = 'E2E_BUILD_DRAFT_PASS' +const BUILD_NOTE_GENERATED_BADGE = 'Generated' const getBuildDraftBar = (page: Page) => page.getByRole('group', { name: 'Build draft' }) +const getBuildNoteFileButton = (page: Page) => + page.getByRole('region', { name: 'Files' }) + .getByRole('button') + .filter({ hasText: BUILD_NOTE_FILE_NAME }) + .filter({ hasText: BUILD_NOTE_GENERATED_BADGE }) + +const getConfigNote = (value: Awaited>) => + value.agent_soul?.config_note ?? '' + Given( 'an Agent v2 Build draft adds the supported E2E files, skills, and env', async function (this: DifyWorld) { @@ -274,6 +287,20 @@ Then('I should see the Agent v2 Build mode confirmation state', async function ( await expect(page.getByText('Shape this setup through the chat on the right, then Apply.')).toBeVisible() }) +Then( + 'the Agent v2 Build draft should include the generated build note', + async function (this: DifyWorld) { + await expect.poll( + async () => getConfigNote(await getAgentBuildDraft(getCurrentAgentId(this))), + { timeout: 30_000 }, + ).toContain(BUILD_NOTE_MARKER) + }, +) + +Then('I should see the generated Agent v2 build note in Configure', async function (this: DifyWorld) { + await expect(getBuildNoteFileButton(this.getPage())).toBeVisible() +}) + Then('Agent v2 Build chat should be blocked until a model is configured', async function (this: DifyWorld) { await expectAgentModelRequiredFeedback(this.getPage()) }) @@ -331,6 +358,16 @@ Then( }, ) +Then( + 'the normal Agent v2 draft should not include the generated build note', + async function (this: DifyWorld) { + await expect.poll( + async () => (await getAgentComposerDraft(getCurrentAgentId(this))).agent_soul?.config_note ?? '', + { timeout: 30_000 }, + ).not.toContain(BUILD_NOTE_MARKER) + }, +) + Then( 'the normal Agent v2 draft should not include the Agent Builder JSON Replace tool', async function (this: DifyWorld) { diff --git a/e2e/fixtures/test-materials/agent-build-instruction.txt b/e2e/fixtures/test-materials/agent-build-instruction.txt index 82ebf01d3a7..018b254d50e 100644 --- a/e2e/fixtures/test-materials/agent-build-instruction.txt +++ b/e2e/fixtures/test-materials/agent-build-instruction.txt @@ -1,3 +1,3 @@ -Update only the Agent instructions. -After applying, every response should briefly mention E2E_BUILD_DRAFT_PASS. -Do not add files, skills, tools, knowledge, environment variables, or other capabilities. +Update only the Agent build note for this Build draft. +The build note should record this durable context exactly: E2E_BUILD_DRAFT_PASS. +Do not update Agent instructions, prompt, files, skills, tools, knowledge, environment variables, or other capabilities. diff --git a/e2e/scripts/setup.ts b/e2e/scripts/setup.ts index 0654772142f..a60842ed1ed 100644 --- a/e2e/scripts/setup.ts +++ b/e2e/scripts/setup.ts @@ -38,8 +38,10 @@ const shellctlPort = Number(process.env.E2E_SHELLCTL_PORT || 5004) const shellctlContainerName = process.env.E2E_SHELLCTL_CONTAINER_NAME || 'dify-agent-e2e-shellctl' const shellctlImage = process.env.E2E_SHELLCTL_IMAGE || 'dify-agent-local-sandbox:e2e' const shellctlUrl = `http://${shellctlHost}:${shellctlPort}` +const agentStubApiBaseUrl = `http://host.docker.internal:${agentBackendPort}/agent-stub` const defaultPluginDaemonKey = 'lYkiYYT6owG+71oLerGzA7GXCgOT++6ovaezWAjpCjf+Sjc3ZtU+qUEi' const defaultInnerApiKeyForPlugin = 'QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1' +const defaultAgentServerSecretKey = 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY' const middlewareDataPaths = [ path.join(dockerDir, 'volumes', 'db', 'data'), @@ -104,6 +106,12 @@ const getAgentBackendEnvironment = async () => { || apiEnv.INNER_API_KEY_FOR_PLUGIN || defaultInnerApiKeyForPlugin, DIFY_AGENT_INNER_API_URL: process.env.DIFY_AGENT_INNER_API_URL || `http://${apiHost}:${apiPort}`, + DIFY_AGENT_SERVER_SECRET_KEY: + process.env.DIFY_AGENT_SERVER_SECRET_KEY + || defaultAgentServerSecretKey, + DIFY_AGENT_STUB_API_BASE_URL: + process.env.DIFY_AGENT_STUB_API_BASE_URL + || agentStubApiBaseUrl, DIFY_AGENT_PLUGIN_DAEMON_API_KEY: process.env.DIFY_AGENT_PLUGIN_DAEMON_API_KEY || process.env.PLUGIN_DAEMON_KEY @@ -420,6 +428,7 @@ export const startShellctlSandbox = async () => { '--rm', '--name', shellctlContainerName, + ...(process.platform === 'linux' ? ['--add-host', 'host.docker.internal:host-gateway'] : []), '-p', `${shellctlHost}:${shellctlPort}:5004`, ...(process.env.E2E_SHELLCTL_AUTH_TOKEN