handle_execution's run_test branch used to stop and ask a human for test
data any time none was prepared yet, even when every declared start
variable was something the agent could invent. Add
needs_upload_inputs(schema) -> bool (true only for a declared file /
file-list variable) and gate BUILD_AWAIT_TESTDATA on it: everything else
is now mocked inline via env.agent.generate_mock_inputs and the flow goes
straight to build.test_and_repair. A schema mixing text and file
variables still stops at the gate, since the file can't be invented.
Updates three full-build-flow tests whose generated graph has no file
start variable, so run_test now lands directly at build.review instead of
pausing at the testdata gate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code review finding: handle_review's re_fix branch and handle_reverted both
emitted PlanCard("Build plan", "v1") before falling through to
build.initial_plan's unconditional resource discovery, which falls through
again to confirm_resources -- which emits PlanCard("Build plan", "v1") a
SECOND time, now with resources bound. Before this task those two cards were
tagged v1 then v2, so they were visibly different plans; now both say v1,
so the loop-back shows the same plan card twice in one pass. Task 2 already
removed this exact duplicate from the straight-through path
(handle_goal_analysis); it should not survive on the loop-back and not on
the straight-through path.
Fix: handle_review's re_fix branch and handle_reverted still draft
fc.plan_items/fc.plan_version_tag (needed by discovery and the eventual
confirm_resources card) but no longer emit a PlanCard or claim cards=["plan"]
on their AssistantTurnItem (now cards=[], matching the existing
handlers_fix.py precedent for a turn with no card). There is now exactly ONE
plan card per pass, tagged v1, on every path: straight-through,
continue_adjusting, and retry_after_revert.
Updated the two direct-handler unit tests that asserted the now-removed
card, and extended test_review_continue_adjusting_then_reapprove_is_
idempotent (an end-to-end Runner-driven test) to assert re_fix adds zero new
plan cards and the subsequent confirm_resources adds exactly one, tagged v1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code review finding: handle_goal_analysis built its own ProgressReporter
(build-review-requirements/build-draft-plan) and finish()ed it, then
tail-called _discover_and_offer_resources, which built a SECOND reporter
under the SAME env.operation_id. ProgressReporter's revision counter starts
at 0 per instance, so the second reporter's activate/finish calls restarted
revision at 1 -- two progress events with revision 1 (and 2, and 3) under
one operation_id. contract.py's ProgressEventData docstring states revision
is monotonic within an operation_id; every other handler across the three
handler files uses exactly one reporter per step -- this was the sole
exception, on the most-travelled build path.
Fix: _discover_and_offer_resources now takes an optional `progress`
parameter. handle_goal_analysis passes its live (still-open) reporter
through instead of finishing it early; the helper adds the discovery steps
onto that SAME reporter via add_steps() and is the one that finish()es it.
handle_initial_plan (the continue_adjusting/retry_after_revert loop-back,
which has no reporter of its own -- a separate CAS commit/operation) still
gets progress=None and the helper builds a fresh one, unchanged.
Added test_submitting_requirements_progress_is_one_operation_with_monotonic_
revisions: drives the real Runner with submit_requirements, captures every
emitted ProgressEventData, and asserts a single operation_id with strictly
increasing, non-repeating revisions across all four progress steps.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code review finding: the plan card's version_tag was renumbered v2 -> v1
in an earlier commit, but handle_resource_recommendation's own reply_text
still read "Plan v2 ready for approval." -- a card that says v1 next to
prose that says v2 is worse than leaving both at v2, since it's exactly
the user-visible inconsistency the renumber exists to eliminate.
Updated the reply_text to "Plan v1 ready for approval." and its matching
entry in the strings.py literal catalog, so test_catalog_covers_handler_
literals keeps passing for the right reason (a registered v1 string, not
a stale v2 one). Grepped the whole of core/dify_builder/ for any other
quoted v2/v2.1 string this renumber might have missed -- this was the
only one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Code review finding: build.initial_plan was left in _WAITING with zero
_ACTIONS_FOR entries but a handler still gated on find_resources. Since a
waiting state settles the runner's advance loop (is_waiting(next_state) ->
settled), continue_adjusting from build.review landed a session there with
no button the FE could render and no action the handler would accept --
the loop-back could never advance.
Reclassify build.initial_plan as working (state.py _WORKING, not _WAITING):
it is now a genuine pass-through step, matching build.test_and_repair /
build.publish / build.governance_feedback. handle_initial_plan drops its
find_resources gate and runs resource discovery unconditionally on entry;
the runner drives it with the action already consumed and falls straight
through to build.resource_recommendation within the same advance() call.
Removed the now-stale BUILD_INITIAL_PLAN entry from service.py's
_BACKEND_ACTIONS_FOR (no action to gate on any more) and the compensating
exemption it required in test_every_backend_waiting_action_has_a_ui_path
(the invariant now holds with no special case).
Added a state-classification test (test_build_initial_plan_is_working_not_
waiting) and an end-to-end regression driving the Runner from build.review
through continue_adjusting with no find_resources action ever sent
(test_continue_adjusting_reaches_resources_without_find_resources_action),
which is the scenario this fix exists to unwedge. Fixed the two existing
idempotency flow tests that asserted the old (broken) two-step loop-back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
handle_capability_check now advances a session that already carries a goal
(fc.goal_text set at creation) on ANY turn, not only send_goal -- the
composer already carried the goal in, so requiring a further send_goal
click was a second decision-free gate. A goal-less (create-from-blank)
session still waits for send_goal to supply one; that action id stays in
_ACTIONS_FOR for exactly that case.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With build.initial_plan's plan card gone (previous commit), the plan card
emitted by handle_resource_recommendation on confirm_resources is now the
FIRST and only plan card a build shows -- so it becomes v1 instead of v2.
The build-complete plan card in handle_plan_approval follows suit,
v2.1 -> v1.1. A build no longer displays "the same plan twice under two
version numbers".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
submit_requirements now tail-calls a shared _discover_and_offer_resources
helper straight into build.resource_recommendation, instead of stopping at
build.initial_plan to show a plan card the user must click through with no
real decision to make. The resource-discovery body (progress reporting,
discover_resources, ResourceSelectCard, empty-options notice, assistant
turn) is extracted verbatim into that helper so the straight-through path
and the continue_adjusting/retry_after_revert loop-back (which still
re-enters at build.initial_plan and still requires find_resources) share
one implementation instead of drifting apart.
build.initial_plan survives as the loop-back target but now offers no UI
action (_ACTIONS_FOR entry removed); its handler still accepts
find_resources at the backend for that loop-back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
api/openapi/markdown/console-openapi.md still documented the removed
ConflictPolicyOption schema and ResourceSelectCard.conflict_policy_options
field as live API contract after e8b14b50ff. Regenerated via
api/dev/generate_swagger_markdown_docs.py and kept only the two hunks
that remove the conflict-policy schema; discarded unrelated churn the
regeneration also picked up (the already-merged /workflow-generate/improve
endpoint's docs, never regenerated by that earlier commit).
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>
The App Builder composer's "Add missing details" link needs a backend. It
fires in the pre-creation composer, so there is no app, no draft workflow and
no Builder session yet -- it is tenant-scoped only, which puts it beside
/workflow-generate/suggestions rather than under console/dify_builder.
POST /console/api/workflow-generate/improve expands a short build instruction
into one that states what a builder would otherwise have to ask about: the
trigger and inputs, the steps, the rules that decide between outcomes, the
output and its destination, and the failure path.
Soft-fail like suggestions: no default model, an LLM error, an echo of the
input, or a rewrite shorter than the input all return "" from the generator,
and the route answers 200 with the caller's own text and changed=false. The
action can never strand what the user typed, so there is deliberately no
402/429 mapping.
Deliberately not grounded in the tenant's tools or knowledge bases. Naming
tools here would prejudice the workflow generator's own tool router, which
selects from the full installed catalogue against the instruction it is
actually given -- and it keeps the endpoint free of DB and plugin-daemon
calls.
The boundary guard is split out rather than shared: _workflow_instruction_guard
names ideal_output in its wording, which this payload does not carry. The
existing endpoints' error strings stay byte-identical.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The main merge added Azerbaijani (#42291) as a console locale. The naming
fallback keys its "New app" stem by console locale, so a new locale without a
stem silently falls back to English; the catalog-completeness test caught it on
rebase, which is what that test exists for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
discover_resources initialised its selection to the entire tenant inventory and
only replaced it when the model returned a non-empty pick, so three paths -- no
model, a model that picked nothing, a failed call -- recommended everything. On
a workspace with 57 resources that is not merely noisy: the client pre-checks
every recommendation and submits them all by default, and bind_resources then
appends all 57 labels to the last plan item, which is the text fed to the graph
generator.
Recommending nothing is the honest answer when there is no signal. The card is
still emitted so the confirm gate keeps its active interaction and a later pass
cannot inherit a stale one, and a notice says why rather than leaving a blank
card. The conflict-policy control no longer renders over an empty list.
The test that should have caught this used a one-resource catalog, where "all"
and "none" are indistinguishable; it now uses 57.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The testdata gate is the one place a human types for tens of seconds, and it
opens right after Builder has written the canvas -- long enough for the editor's
own autosave to move the app revision underneath them. Both revision gates then
refused the submit, and because a conflict leaves the session with only
check_recovery as an action, active_interaction drops to None and the client
freezes every input: the form stayed on screen but could no longer be typed into.
A testdata submit carries run inputs, not graph edits, so neither gate applies
to it. Exempting only the draft-changed check would not have been enough --
the stale-app-revision check above it fires first, on the base_app_revision the
client has held since the form was issued.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>