feat(dify-builder): name apps from their prompt, and turn gate actions into card options

Two App Builder spec changes that share the Builder session contract.

Naming (spec N1-N3): an app created from the homepage prompt is no longer named
by the user. core/dify_builder/naming.py cuts the prompt's opening clause at
create time -- instant and infallible, since it runs inside POST /apps -- and
CreateAppPayload.name becomes optional alongside a new prompt field. The Builder
then refines that name once during goal analysis, where a model and the detected
reply language are already in hand, so the judgement calls that are
English-shaped in pure Python (dropping an imperative lead-in, spotting a
contentless prompt) work in any language. Plan approval freezes the name.

Card options: SessionView gains a Decision carrying the gate's choice as options
inside the card plus a fixed confirm/cancel pair, replacing a button row whose
shape changed at every state. Option ids are the old action ids untouched, so
resolve_action_kind and the per-state legality check are unchanged, and
SessionView.actions keeps being populated -- the deployed web client goes on
working until it reads decision instead.

The regenerated console OpenAPI doc also picks up ErrorCard.diagnostics, missed
when cc9ca8b1dd landed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Charles YaoandClaude Opus 5 committed 2026-09-17 17:55:38 -07:00
1 parent cc9ca8b1dd
commit aa2b4fe4ab
30 files changed
+1163 -18

No files matched your search

@@ -16,6 +16,7 @@ from core.dify_builder.contract import (
ChangeSetCard,
CheckpointCard,
CheckpointRef,
Decision,
DecisionItem,
ErrorCard,
ErrorEventData,
@@ -52,6 +53,10 @@ class DifyBuilderCreateBuildSessionPayload(DifyBuilderPayload):
app_id: NonEmptyString
scenario: Literal["build"]
goal_text: NonEmptyString
# True when the app was created from this same prompt and still carries the
# name cut from it, so Builder may rewrite it once it understands the goal.
# False is create-from-blank: the user named the app, Builder inherits it.
derive_app_name: bool = False
model_config_data: SessionModel | None = Field(default=None, alias="model_config")
@@ -252,6 +257,7 @@ class DifyBuilderSessionViewResponse(ResponseModel):
entry_mode: EntryMode = EntryMode.FIX
phase: Phase = Phase.UNDERSTAND
actions: list[Action] = Field(default_factory=list)
decision: Decision | None = None
active_interaction: DifyBuilderActiveInteractionResponse | None = None
checkpoint: CheckpointRef | None = None
recovery: RecoveryRef | None = None