Skip to content

feat(api): Agent App type S1 — AppMode.AGENT + create flow + binding#36829

Merged
zyssyz123 merged 31 commits into
mainfrom
feat/agent-app-type-s1
Jun 2, 2026
Merged

feat(api): Agent App type S1 — AppMode.AGENT + create flow + binding#36829
zyssyz123 merged 31 commits into
mainfrom
feat/agent-app-type-s1

Conversation

@zyssyz123
Copy link
Copy Markdown
Contributor

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 legacy agent-chat (ReAct). Default template seeds no model_config (runtime model/prompt/tools come from the bound Agent Soul).
  • Create flow — creating an Agent App also creates a roster Agent bound 1:1 via Agent.app_id (design decision Q1), in 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 (caller AppService.create_app owns 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). Returns None for non-agent apps (short-circuits, no DB hit).
  • CreateAppParams / AppListParams accept "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 passing
  • New tests: backing-agent build/link/get (roster) + enum/template/params/bound_agent_id short-circuit
  • ruff check + ruff format --check clean
  • pyrefly — 0 new errors

🤖 Generated with Claude Code

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>
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label May 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Pyrefly Diff

base → 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]

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 2026

Pyrefly Type Coverage

Metric Base PR Delta
Type coverage 45.98% 46.02% +0.04%
Strict coverage 45.49% 45.52% +0.03%
Typed symbols 24,871 24,986 +115
Untyped symbols 29,534 29,631 +97
Modules 2770 2790 +20

@autofix-ci autofix-ci Bot requested a review from crazywoola as a code owner May 29, 2026 10:50
@github-actions github-actions Bot added the web This relates to changes on the web. label May 29, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented May 29, 2026

Codecov Report

❌ Patch coverage is 88.83861% with 74 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.83%. Comparing base (5c1cfe6) to head (a3abeee).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
api/core/app/apps/agent_app/app_runner.py 85.00% 8 Missing and 4 partials ⚠️
api/controllers/console/app/completion.py 33.33% 10 Missing ⚠️
...core/app/apps/agent_app/runtime_request_builder.py 86.48% 7 Missing and 3 partials ⚠️
api/clients/agent_backend/request_builder.py 85.29% 2 Missing and 3 partials ⚠️
api/controllers/console/app/agent_app_feature.py 88.09% 5 Missing ⚠️
api/controllers/service_api/app/completion.py 55.55% 3 Missing and 1 partial ⚠️
api/controllers/web/completion.py 60.00% 3 Missing and 1 partial ⚠️
api/models/model.py 50.00% 3 Missing and 1 partial ⚠️
api/controllers/console/app/agent_app_access.py 91.42% 3 Missing ⚠️
api/core/app/apps/agent_app/app_generator.py 97.54% 1 Missing and 2 partials ⚠️
... and 8 more
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     
Flag Coverage Δ
api 85.34% <88.83%> (+0.01%) ⬆️
dify-ui 95.57% <ø> (ø)
web 86.48% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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>
@zyssyz123 zyssyz123 requested a review from a team May 29, 2026 11:03
@dosubot dosubot Bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels May 29, 2026
zyssyz123 and others added 3 commits May 29, 2026 19:14
* ``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>
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:XL This PR changes 500-999 lines, ignoring generated files. labels May 29, 2026
zyssyz123 and others added 9 commits May 29, 2026 20:16
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>
autofix-ci Bot and others added 10 commits May 31, 2026 09:24
…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%.
@zyssyz123 zyssyz123 enabled auto-merge June 1, 2026 06:45
MRZHUH
MRZHUH previously approved these changes Jun 1, 2026
Copy link
Copy Markdown
Contributor

@MRZHUH MRZHUH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zyssyz123 zyssyz123 added this pull request to the merge queue Jun 1, 2026
@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jun 1, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 1, 2026
@zyssyz123 zyssyz123 enabled auto-merge June 1, 2026 09:00
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.
@zyssyz123 zyssyz123 requested a review from a team June 2, 2026 03:21
Copy link
Copy Markdown
Contributor

@MRZHUH MRZHUH left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@zyssyz123 zyssyz123 added this pull request to the merge queue Jun 2, 2026
Merged via the queue into main with commit e35d23c Jun 2, 2026
42 checks passed
@zyssyz123 zyssyz123 deleted the feat/agent-app-type-s1 branch June 2, 2026 04:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files. web This relates to changes on the web.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants