feat(api): Agent App type S1 — AppMode.AGENT + create flow + binding#36829
Merged
Conversation
First slice of the Agent App type (新 Agent 作为独立 App 类型,替代 chatbot/agent-legacy/completion). Design: https://km.dify.langgenius.ai/wiki/spaces/DT/pages/460161070/Agent+App+Type * New ``AppMode.AGENT = "agent"`` (distinct from legacy ``agent-chat`` ReAct app). Runtime model/prompt/tools live in the bound Agent Soul, so the default template seeds no model_config. * Create flow: creating an Agent App also creates a roster Agent bound 1:1 via ``Agent.app_id`` (decision Q1), inside the same transaction so app + backing agent persist atomically. ``AgentRosterService.create_backing_agent_for_app`` builds the agent + a v1 (empty) Agent Soul snapshot without committing; the user configures model/prompt/tools afterward in the Composer. * ``App.bound_agent_id`` resolves the backing roster Agent from the app id (so the console can open the Composer in roster-detail mode); surfaced on the app detail response. Returns None for non-agent apps (short-circuits, no DB hit). * ``CreateAppParams`` / ``AppListParams`` accept "agent"; list filter handles it. Scope: S1 only (foundation). Runtime/preview, web/service API, access & sharing, logs and feature flags land in S2–S7 per the design. Tests: roster backing-agent build/link/get + enum/template/params/bound_agent_id short-circuit. 46 passing in app + agent service suites; ruff + pyrefly clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Pyrefly Diffbase → PR--- /tmp/pyrefly_base.txt 2026-06-02 03:30:19.746748974 +0000
+++ /tmp/pyrefly_pr.txt 2026-06-02 03:30:05.100684329 +0000
@@ -1,5 +1,5 @@
-ERROR `str` is not assignable to `Literal['advanced-chat', 'agent-chat', 'all', 'channel', 'chat', 'completion', 'workflow']` [bad-assignment]
- --> controllers/console/app/app.py:69:104
+ERROR `str` is not assignable to `Literal['advanced-chat', 'agent', 'agent-chat', 'all', 'channel', 'chat', 'completion', 'workflow']` [bad-assignment]
+ --> controllers/console/app/app.py:69:113
ERROR Argument `str | None` is not assignable to parameter `language` with type `str` in function `services.account_service.AccountService.send_email_register_email` [bad-argument-type]
--> controllers/console/auth/email_register.py:86:108
ERROR Object of class `MissingRouter` has no attribute `get` [missing-attribute]
@@ -557,7 +557,7 @@
ERROR Object of class `Tenant` has no attribute `role` [missing-attribute]
--> services/account_service.py:1383:13
ERROR `+` is not supported between `str` and `dict[Unknown, Unknown]` [unsupported-operation]
- --> services/app_service.py:519:53
+ --> services/app_service.py:555:53
ERROR No matching overload found for function `flask.helpers.stream_with_context` called with arguments: (Generator[bytes]) [no-matching-overload]
--> services/audio_service.py:144:56
ERROR No matching overload found for function `flask.helpers.stream_with_context` called with arguments: (Generator[bytes]) [no-matching-overload]
@@ -996,7 +996,7 @@
--> tests/test_containers_integration_tests/services/test_app_service.py:84:38
ERROR Object of class `NoneType` has no attribute `id` [missing-attribute]
--> tests/test_containers_integration_tests/services/test_app_service.py:93:33
-ERROR Argument `str` is not assignable to parameter `mode` with type `Literal['advanced-chat', 'agent-chat', 'chat', 'completion', 'workflow']` in function `services.app_service.CreateAppParams.__init__` [bad-argument-type]
+ERROR Argument `str` is not assignable to parameter `mode` with type `Literal['advanced-chat', 'agent', 'agent-chat', 'chat', 'completion', 'workflow']` in function `services.app_service.CreateAppParams.__init__` [bad-argument-type]
--> tests/test_containers_integration_tests/services/test_app_service.py:136:22
ERROR Object of class `NoneType` has no attribute `id` [missing-attribute]
--> tests/test_containers_integration_tests/services/test_app_service.py:142:42
@@ -2249,7 +2249,7 @@
ERROR Argument value `Literal[101]` violates Pydantic `le` constraint `Literal[100]` for field `limit` [bad-argument-type]
--> tests/unit_tests/controllers/service_api/app/test_conversation.py:92:35
ERROR Missing argument `session` in function `services.conversation_service.ConversationService.pagination_by_last_id` [missing-argument]
- --> tests/unit_tests/controllers/service_api/app/test_conversation.py:456:59
+ --> tests/unit_tests/controllers/service_api/app/test_conversation.py:457:59
ERROR Argument `SimpleNamespace` is not assignable to parameter `application_generate_entity` with type `AdvancedChatAppGenerateEntity | WorkflowAppGenerateEntity` in function `core.app.apps.common.workflow_response_converter.WorkflowResponseConverter.__init__` [bad-argument-type]
--> tests/unit_tests/controllers/service_api/app/test_hitl_service_api.py:98:37
ERROR Argument `SimpleNamespace` is not assignable to parameter `workflow` with type `WorkflowSnapshot` in function `core.app.apps.advanced_chat.generate_task_pipeline.AdvancedChatAppGenerateTaskPipeline.__init__` [bad-argument-type]
@@ -2999,6 +2999,52 @@
--> tests/unit_tests/core/app/apps/advanced_chat/test_generate_task_pipeline_core.py:651:52
ERROR `(**kwargs: Unknown) -> Literal['failed']` is not assignable to attribute `workflow_node_finish_to_stream_response` with type `(self: WorkflowResponseConverter, *, event: QueueNodeExceptionEvent | QueueNodeFailedEvent | QueueNodeSucceededEvent, task_id: str) -> NodeFinishStreamResponse | None` [bad-assignment]
--> tests/unit_tests/core/app/apps/advanced_chat/test_generate_task_pipeline_core.py:673:89
+ERROR Object of class `NoneType` has no attribute `message` [missing-attribute]
+ --> tests/unit_tests/core/app/apps/agent_app/test_app_runner.py:133:12
+ERROR Object of class `NoneType` has no attribute `model` [missing-attribute]
+ --> tests/unit_tests/core/app/apps/agent_app/test_app_runner.py:134:12
+ERROR Object of class `NoneType` has no attribute `message` [missing-attribute]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:65:12
+ERROR Argument `SimpleNamespace` is not assignable to parameter `application_generate_entity` with type `AgentAppGenerateEntity` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:75:41
+ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:76:23
+ERROR Argument `SimpleNamespace` is not assignable to parameter `message` with type `Message` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:77:21
+ERROR Argument `_FakeQueueManager` is not assignable to parameter `queue_manager` with type `AppQueueManager` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:78:27
+ERROR Argument `SimpleNamespace` is not assignable to parameter `application_generate_entity` with type `AgentAppGenerateEntity` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:91:41
+ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:92:23
+ERROR Argument `SimpleNamespace` is not assignable to parameter `message` with type `Message` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:93:21
+ERROR Argument `_FakeQueueManager` is not assignable to parameter `queue_manager` with type `AppQueueManager` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:94:27
+ERROR Argument `SimpleNamespace` is not assignable to parameter `application_generate_entity` with type `AgentAppGenerateEntity` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:107:41
+ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:108:23
+ERROR Argument `SimpleNamespace` is not assignable to parameter `message` with type `Message` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:109:21
+ERROR Argument `_FakeQueueManager` is not assignable to parameter `queue_manager` with type `AppQueueManager` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:110:27
+ERROR Argument `SimpleNamespace` is not assignable to parameter `application_generate_entity` with type `AgentAppGenerateEntity` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:123:41
+ERROR Argument `SimpleNamespace` is not assignable to parameter `app_model` with type `App` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:124:23
+ERROR Argument `SimpleNamespace` is not assignable to parameter `message` with type `Message` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:125:21
+ERROR Argument `_FakeQueueManager` is not assignable to parameter `queue_manager` with type `AppQueueManager` in function `core.app.apps.agent_app.app_generator.AgentAppGenerator._run_input_guards` [bad-argument-type]
+ --> tests/unit_tests/core/app/apps/agent_app/test_input_guards.py:126:27
+ERROR Object of class `LayerConfig` has no attribute `plugin_id`
+ERROR Object of class `LayerConfig` has no attribute `model_provider`
+ERROR Object of class `LayerConfig` has no attribute `conversation_id`
+ERROR Object of class `LayerConfig` has no attribute `invoke_from`
+ERROR Object of class `FromClause` has no attribute `create` [missing-attribute]
+ --> tests/unit_tests/core/app/apps/agent_app/test_session_store.py:50:5
+ERROR Object of class `FromClause` has no attribute `drop` [missing-attribute]
+ --> tests/unit_tests/core/app/apps/agent_app/test_session_store.py:55:5
ERROR Argument `dict[str, dict[str, str]]` is not assignable to parameter `override_config_dict` with type `AppModelConfigDict | None` in function `core.app.apps.agent_chat.app_config_manager.AgentChatAppConfigManager.get_app_config` [bad-argument-type]
--> tests/unit_tests/core/app/apps/agent_chat/test_agent_chat_app_config_manager.py:41:34
ERROR Cannot index into `str` [bad-index]
|
Contributor
Pyrefly Type Coverage
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #36829 +/- ##
========================================
Coverage 85.82% 85.83%
========================================
Files 4565 4574 +9
Lines 223439 224053 +614
Branches 41135 41192 +57
========================================
+ Hits 191767 192313 +546
- Misses 27968 28019 +51
- Partials 3704 3721 +17
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Q2 decision: unify the workflow-only ``workflow_agent_runtime_sessions`` into an owner-agnostic ``agent_runtime_sessions`` table serving both owners. Feature is unreleased, so the old table is dropped (no data migration). * ``AgentRuntimeSession`` model (table ``agent_runtime_sessions``) with an ``owner_type`` discriminator (workflow_run | conversation): workflow columns (workflow_id/run_id/node_id/binding_id/agent_config_snapshot_id/ composition_layer_specs) and ``conversation_id`` are mutually-exclusive, enforced by two partial unique indexes. Back-compat aliases ``WorkflowAgentRuntimeSession`` / ``WorkflowAgentRuntimeSessionStatus`` keep the shipped lifecycle path (PR #36724) unchanged; the workflow store now sets ``owner_type=workflow_run``. * New ``AgentAppRuntimeSessionStore`` (conversation-keyed) for the Agent App side of the same table: one conversation = one Agent session for multi-turn. * Migration 121e7346074d (drop old + create unified) — applies and downgrade/upgrade round-trips clean on Postgres. Tests: 6 new conversation-store ORM round-trip tests; 154 existing workflow lifecycle + agent_backend tests still green against the unified table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ``AgentBackendRunRequestBuilder.build_for_agent_app`` + the ``AgentBackendAgentAppRunInput`` DTO: the app-shaped layer graph (agent soul system prompt → user message → execution context → history → llm → optional plugin tools → optional output), purpose=agent_app, on_exit=suspend. No workflow-node-job / previous-node prompt. * ``AgentAppRuntimeRequestBuilder`` maps an Agent Soul snapshot + conversation turn into a CreateRunRequest: plugin-daemon plugin_id/provider normalization, credential fetch + scalar normalization, Dify plugin tools (reuses WorkflowAgentPluginToolsBuilder), conversation-scoped execution_context with invoke_from=agent_app, and the prior session_snapshot for multi-turn. Tests: 5 builder/DTO cases + reuses the conversation session store; 40 passing across agent_backend + agent_app suites. ruff + pyrefly clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
``AgentAppRunner`` runs one conversation turn against the dify-agent backend (instead of the legacy in-process ReAct loop): load the conversation's prior session_snapshot, build the run request (S2b), create the run, consume the event stream, and republish the assistant answer as chat queue events (``QueueLLMChunkEvent`` + ``QueueMessageEndEvent``) so the existing EasyUI chat task pipeline persists the message and streams SSE. On success the conversation session_snapshot is saved for multi-turn continuity; failures raise AgentBackendError; the answer is normalized to text (plain string or structured JSON). MVP emits the final answer as one chunk + message-end; token-level streaming is a follow-up refinement. The generator/entity/converter wiring + live-stack verification land next. Tests: 4 runner cases via the deterministic fake backend client + fake queue (success→chunk+end+session-save, prior-snapshot threading, failure raises, answer extraction). 15 agent_app unit tests green; ruff + pyrefly clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Direction (your call): ride the existing chat (EasyUI) message + SSE pipeline, synthesizing the app config from the Agent Soul rather than inventing a new pipeline. ``AgentAppConfigManager`` shapes the Agent Soul (model + system prompt) plus any app-level feature flags stored on ``app_model_config`` (Q3) into an app_model_config-style dict, then reuses the same chat sub-managers (ModelConfigManager / PromptTemplateConfigManager / features) to build an EasyUI-shaped ``AgentAppConfig``. Model + prompt always come from the Soul (single source of truth); feature flags from app_model_config when present. Tests: 3 config-synthesis cases (soul model/prompt, feature-flag passthrough + soul override, missing-model). ruff clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Route AppMode.AGENT through AppGenerateService to a new AgentAppGenerator that resolves the bound roster Agent + published Soul snapshot, synthesizes an EasyUI-shaped app config from the Soul, and drives one conversation turn via AgentAppRunner against the dify-agent backend (streamed message + message_end over the existing chat SSE pipeline). - AgentAppGenerator + AgentAppGenerateResponseConverter - AgentAppGenerateEntity (agent_id + agent_config_snapshot_id) - console chat-messages: accept AppMode.AGENT; make model_config optional (Agent Apps derive model/prompt from the Soul, not an override config) - synthesize prompt_type=simple and NULL app_model_config_id so the conversation persists without a legacy app_model_config row Live-verified end-to-end against the real agent backend: single-turn, multi-turn resume (history preserved, session reused), and the runtime session row persists with owner_type=conversation and an intact snapshot (no "cleaned trap"). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…API chat Allow AppMode.AGENT through the web and service-API chat-message (and stop) mode gates so Agent Apps are reachable over /v1/chat-messages and the web app, routing to AgentAppGenerator like the console debug path. The web / service-API chat payloads carry no override model_config, so no payload change is needed there — model + prompt come from the bound Agent Soul. Live-verified: /v1/chat-messages with an app token streams a Soul-backed answer end-to-end through the real agent backend. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Allow AppMode.AGENT through the console conversation-log, message-log and average-session-interaction mode gates so an Agent App's conversations and messages show up in the app logs and statistics, same as chat / agent-chat. Token/message/conversation daily statistics already accept any mode. Live-verified: conversation list, message history (multi-turn memory intact) and conversation detail all return for an Agent App. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a shared get_app_parameters() resolver that maps any app type to its webapp parameters, and route the web, service-API and explore /parameters endpoints through it. Agent Apps have neither a workflow nor a legacy app_model_config, so their presentation features (opening statement, suggestions, file upload, ...) default to disabled with a free-form chat input until a dedicated config surface lands. This unblocks the public web app and service API for Agent Apps: /parameters now returns a valid config instead of 500-ing on the missing app_model_config. Live-verified end-to-end via the web app entry (passport -> parameters -> streamed chat) and /v1/parameters. Unit tests cover all four resolver branches (workflow, easy-UI config, agent defaults, unavailable). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…flags Per PRD (Misc Legacy / expert zone), an Agent App must support conversation opener, follow-up suggestions, citations, content moderation and annotation reply. These app-level presentation features live on app_model_config (design Q3), but create_app never made one for Agent Apps, so they were unconfigurable and /parameters returned hardcoded defaults. create_app now creates a model-less app_model_config row for AppMode.AGENT (model/prompt/tools stay in the Agent Soul; agent_mode left unset so App.is_agent stays False and the app mode is not mutated). The webapp /parameters endpoint and the chat pipeline already read feature flags from this row. Live-verified: a fresh Agent App gets the row; setting opening_statement + suggested_questions persists and surfaces through /v1/parameters, while chat continues to stream Soul-backed answers. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ons/...) Adds POST /console/api/apps/<id>/agent-features, a dedicated write surface for an Agent App's PRD "Misc Legacy" presentation features. The legacy /model-config endpoint also writes model / prompt / agent tools, which an Agent App owns through its Soul, so reusing it would be semantically wrong and let a caller override Soul-owned config. AgentAppFeatureConfigService validates only the allowed feature subset (opening_statement, suggested_questions, suggested_questions_after_answer, speech_to_text, text_to_speech, retriever_resource, sensitive_word_avoidance), fills disabled/empty defaults, and writes a new app_model_config version with model / prompt / agent_mode left NULL. Soul-owned keys (model, pre_prompt, agent_mode, tools, user_input_form) are dropped, so App.is_agent stays False and the app mode is never mutated. Live-verified: posting opener + follow-up + citations (with model/agent_mode smuggled in) persists a new config row with model=None/agent_mode=None, surfaces through /v1/parameters, keeps mode=agent, and streaming chat still works. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ation/follow-up) Now that the Agent App stores its presentation features, enforce them in the chat pipeline: - Input content moderation (sensitive_word_avoidance) and annotation reply run as pre-backend guards in the Agent App generate worker, mirroring the EasyUI chat / agent-chat runners. A blocked/preset moderation answer or a matched annotation short-circuits the turn and publishes a direct answer (one chunk + message-end) without calling the Agent backend; moderation "override" passes a sanitized query through. A shared publish_text_answer helper keeps backend and short-circuited answers on the same persistence + SSE path. - Follow-up suggestions: allow AppMode.AGENT on the console / web / service-api suggested-questions endpoints. Generation uses the tenant default model, so it works without an app-config model. Citations (retriever_resource) are intentionally deferred: they depend on the Agent backend surfacing retrieval sources. Live-verified: blocked keyword returns the preset response (no backend call) and a normal query still streams the Soul-backed answer; follow-up returns generated questions for an Agent App. _run_input_guards covered by unit tests (moderation pass/override/block, annotation hit). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ws list
Adds GET /console/api/apps/<id>/agent-referencing-workflows (AGENT-gated): the
PRD "Workflow access" surface listing which workflow apps reference this Agent
App's bound roster Agent, via WorkflowAgentNodeBinding (agent_id, roster type).
AgentRosterService.list_workflows_referencing_app_agent resolves the bound
Agent, queries its roster bindings, collapses per-version/per-node rows into one
entry per workflow app (deduped, sorted node ids), resolves app name/mode, and
skips orphaned bindings whose workflow app was deleted.
Site toggle, api-tokens, conversation logs and statistics already accept AGENT,
so this read-only list was the only missing access/logs surface.
Live-verified: returns {data:[]} for an Agent App with no references and 404 for
a non-agent app; conversation logs + daily/session/token statistics all return
200 with the app's real turns. Service covered by unit tests (grouping, no
agent, no bindings, orphaned binding).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
CreateAppParams (service) already accepts "agent", but the HTTP CreateAppPayload Literal omitted it, so an Agent App could not be created through the real console API — only via direct service calls. A full PRD-journey E2E surfaced this: the create step 400'd. Add "agent" to the payload mode Literal. Verified by an end-to-end run (real console + real dify-agent backend, no DB seeding): create Agent App → configure Soul via Composer → publish → preview chat + multi-turn recall → feature flags → access/sharing → web/service chat + moderation short-circuit + follow-up → logs → statistics → versioning, 27/27 assertions pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Resolved conflicts in two console controllers where main refactored the handlers to the @with_current_user decorator (injecting current_user/account as a parameter) while this branch had added AppMode.AGENT to the mode gates. Kept both: main's decorator + signature, plus AppMode.AGENT. - api/controllers/console/app/message.py (suggested-questions gate) - api/controllers/console/app/statistic.py (average-session-interactions gate) Also threaded "agent" through main's new controller-layer mode lists so Agent Apps remain creatable/listable post-merge: - app.py AppListQuery.mode (used by GET /apps?mode=agent) += "agent" - app.py ALLOW_CREATE_APP_MODES += "agent" Agent unit tests (57) + create-app payload + app_service tests green.
The merge brought main's stricter `make type-check-core` (pyrefly), surfacing type errors in the agent code plus a unit test that enumerates AppMode values. - AgentAppGenerateEntity now subclasses ChatAppGenerateEntity (it already rode the chat pipeline). This makes it assignable everywhere ChatAppGenerateEntity is accepted (_init_generate_records, _handle_response, the EasyUI task pipeline) without widening — and no isinstance branch keys off ChatAppGenerateEntity, so behavior is unchanged. - EasyUIBasedAppConfig.app_model_config_id -> str | None (an Agent App has no legacy config row; persistence stores NULL). Removes the inconsistent subclass override on AgentAppConfig. - AgentAppConfigManager: cast the synthesized dict to AppModelConfigDict for the EasyUI sub-managers; build `base` as a plain dict. - AppModelConfigService.validate_configuration: add AppMode.AGENT to the unsupported match arm so the function is exhaustive (Agent App features use AgentAppFeatureConfigService). - agent_v2 session_store: coerce nullable owner columns for the typed scope. - test_app_models: add "agent" to the expected AppMode value set. pyrefly: 0 errors on changed files. ruff clean. 123 unit tests pass.
Both branches refactored the webapp /parameters area concurrently: main into parameters_mapping.get_parameters_from_feature_dict(features_dict, user_input_form) with the workflow-vs-app_model_config branching inlined in each controller, and this branch into a get_app_parameters(app_model) wrapper. The merge kept our controllers but main's tests, which patch `<controller>.get_parameters_from_feature_dict` and broke. Our wrapper only existed to handle Agent Apps that lacked an app_model_config row; now that create_app seeds one, an Agent App flows through main's standard `else` branch (app_model_config.to_dict()) and produces the same parameters. So adopt main's versions of the three /parameters controllers and the parameters_mapping package (dropping the now-redundant get_app_parameters / AppParametersUnavailableError and their ours-only test). Agent App /parameters behavior is unchanged (seeded app_model_config carries opener/follow-up/etc.). console explore + web + service-api parameter tests + agent suites: 94 pass.
…atch codecov/patch was failing (80.6%) — the bulk of the uncovered diff was the Agent App generator (app_generator.py at 49%) and AgentAppFeatureConfigService .update_features. Add unit tests mirroring the agent_chat generator test pattern (module-level mocks, the generate-entity class patched to dodge pydantic, threading + flask context patched): - generate() guards + happy path + existing-conversation path - _generate_worker happy path, input-guard short-circuit, GenerateTaskStopped swallow, and unexpected-error publish - _resolve_agent / _resolve_agent_by_id with every not-found branch - AgentAppFeatureConfigService.update_features persistence Local coverage: app_generator 49% -> 98%, agent_app_feature_service -> 100%.
Merging main produced two alembic heads: main's credential-visibility migration (a1b2c3d4e5f6, #35468) and our unify_agent_runtime_sessions (121e7346074d) both chained off the lifecycle migration 7885bd53f9a9, so `flask db upgrade base:head` failed with "Multiple head revisions are present". The two migrations are unrelated (plugin credentials vs agent runtime sessions), so re-point ours' down_revision to a1b2c3d4e5f6, restoring a single linear head. Verified: `flask db heads` shows one head; offline upgrade base:head and downgrade head:base both succeed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
First slice (S1) of the new Agent App type — Agent as a standalone App that replaces chatbot / agent-legacy / completion, per PRD. Design doc (reviewed): Agent App Type — 后端设计.
S1 is the foundation: the app type enum, the create flow, and the App↔Agent binding. Runtime/preview, web+service API, access&sharing, logs and feature flags follow in S2–S7.
What changed
AppMode.AGENT = "agent"— new app type, distinct from legacyagent-chat(ReAct). Default template seeds nomodel_config(runtime model/prompt/tools come from the bound Agent Soul).Agent.app_id(design decision Q1), in the same transaction so app + backing agent persist atomically.AgentRosterService.create_backing_agent_for_appbuilds the agent + a v1 empty Agent Soul snapshot without committing (callerAppService.create_appowns the commit).App.bound_agent_id— resolves the backing roster Agent from the app id so the console can open the Composer in roster-detail mode; surfaced on the app detail response (AppDetailWithSite). ReturnsNonefor non-agent apps (short-circuits, no DB hit).CreateAppParams/AppListParamsaccept"agent"; list filter handles the new mode.Out of scope (later slices, per design §8)
S2 runtime+session · S3 multi-turn · S4 web/service API · S5 access&sharing · S6 feature flags · S7 logs/monitoring + E2E. Backend-blocked items (Babysit, CLI tools, Skills, Sandbox, ENV, Knowledge runtime) are 一期 but await dify-agent layers; Memory isolation / file-output-check are 二期 per PRD.
Test plan
pytest api/tests/unit_tests/services/test_app_service.py api/tests/unit_tests/services/agent/→ 46 passingbound_agent_idshort-circuitruff check+ruff format --checkcleanpyrefly— 0 new errors🤖 Generated with Claude Code