Run workflow streaming inside the Builder worker to avoid queue deadlocks. Forward native events to shared editor callbacks and wait for graph refresh before retesting repairs.
Keep command cleanup stable, preserve batched canvas events, and allow failed or unfinished historical runs to refresh again.
The run id already reaches a client over SSE, which is enough to open the
run on the canvas while the page is up. It is not enough afterwards: SSE
frames do not survive a reload, and the persisted card's ``run_ids`` holds
BUILDER run ids -- uuid4s the engine mints, which resolve to nothing
outside it. So on reopening a session the card was the only record of the
result, and the canvas view was unreachable.
Put ``dify_run_id`` on TestResultCard, beside ``run_ids``, wired at all
nine construction sites across the Build, Edit and Fix flows. Now the
canvas is reachable from the durable conversation too, which is what makes
the card's ``output`` genuinely the client's choice to render or skip
rather than the only surviving copy.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A passing build test reported "All checks passed" and threw away the
run's actual node outputs. TestResultCard gains an `output: str` field
(pretty-printed JSON of the last node that produced anything, "" when
none); handle_test_and_repair's success card now populates it via a new
_terminal_output(per_node) helper.
_terminal_output is deliberately status-agnostic (no node.status filter):
per-node status spellings differ by source ("success" vs "succeeded"), and
a status filter that silently misses would defeat the point of the card.
FakeDifyPort (tests/unit_tests/core/dify_builder/fakes.py) gains a
settable run_outputs dict, seeded onto the success-path NodeOutput's
outputs, so tests can assert on what the card shows without hand-rolling
a Run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The resource-recommendation step made the user pick a "conflict
policy" that never decided anything. Remove it end to end: the
ConflictPolicyOption card option, ResourceSelectCard's
conflict_policy_options field, the third bind_resources() parameter
across the port/placeholder/LLM-agent implementations, and the dead
conflict_policy field on the (unused) ResourcesPayload contract type.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
api pods lose their logs on restart, so a failed build's real cause
("Workflow generator: structural validation failed: ...") could only be read
from a pod log that may already be gone. Session 01a08fd0 hit exactly that.
Carry the generator's own diagnostics back with the result instead:
BuildNodesResult and ErrorCard gain a ``diagnostics`` list, and build_nodes
records one entry per failed generation attempt -- initial AND the corrective
retry, so an error that changes between attempts stays visible -- plus the
all-rejected and exception paths.
Each entry holds a server-side UTC ``at`` (so the surviving pod-log window can
be searched around it), ``source``, ``attempt``, the generator's ``message``
(the same text the server logs), its ``codes``, and the full structured
``errors`` including ``node_id``. That is strictly more than the log line
carries: for an UNRESOLVED_REFERENCE the export now names the offending node.
The list rides in the ErrorCard's item payload, which is what the streamed
conversation item -- and therefore the exported debug log -- preserves. It is
not rendered in the UI. Retries that eventually succeed keep their breadcrumbs.
Deliberately not mirroring the "structural validation failed" prefix: build.py
cannot tell structural failures from the rest without duplicating the
generator's code taxonomy, which would go stale.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Render change sets directly in plan-style cards with affected node names,
IDs, and change details. Capture node metadata for proposed and applied
changes and persist it with conversation history.
Remove the view_changes action and expand/collapse state. Update generated
contracts, translations, and regression coverage.
Validation: 112 frontend tests and 742 backend unit tests passed.
Final-review fix wave for the testdata-gate + start-selector slice:
- I1 (Important): is_input_failure's signal list included the bare
"required" (and generic "variable not found"), which also fire on
config/runtime faults unrelated to test inputs -- misrouting those
failures to the *_AWAIT_TESTDATA gate, where the diagnosed auto-repair
is never offered and re-running just loops at the gate. Narrowed
_INPUT_FAILURE_SIGNALS to ("file variable", "not provided", "missing
input"), favoring precision per the spec's ambiguous-failure ->
config-repair safe default. The real file-input E2E error ("File
variable not found for selector: [...]") still matches via "file
variable".
- M2 (Minor, latent): handle_plan_approval's from-scratch delete-then-
create-filter path could drop a generator create_node/connect that
reuses a just-deleted placeholder start's id, since _already_present
compared against the PRE-delete existing_node_ids/existing_edges --
delete with no re-create, silently vanishing the node. Deleted ids are
now excluded from that comparison set; the loop-back path (no deletes)
is unaffected.
- Cleanups: state.py's Build/Edit state-count comments (14->15, 9->10,
verified against the actual enum members), FormCard's docstring now
lists the "testdata" variant, and handle_plan_approval binds
current_graph's nodes to one local instead of calling .get() 3x and
documents the delete-placeholder branch.
Tests: extended test_is_input_failure_matches_input_signals's sibling
with a config "required" case (red before the signal-list fix, green
after), and added a plan_approval test where the generator's create_node
reuses the deleted placeholder start's id (red before the id-exclusion
fix -- a dangling connect ValueError -- green after).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>