Commit Graph
29 Commits
Author SHA1 Message Date
twwu 9acf99060b fix(dify-builder): hide redundant send goal action 2026-09-21 17:25:50 +08:00
twwu d5ca5ea8ef fix(dify-builder): stream native test runs after canvas refresh
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.
2026-09-21 15:12:22 +08:00
Charles YaoandClaude Opus 5 a96ed2d387 Builder: fix the continue_adjusting dead end at build.initial_plan
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>
2026-09-20 14:50:45 -07:00
Charles YaoandClaude Opus 5 806cc4e517 Builder: collapse the decision-free find_resources gate at build.initial_plan
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>
2026-09-20 14:37:01 -07:00
Charles YaoandClaude Opus 5 d20286482f fix(dify-builder): let a testdata submit through a draft that moved while typing
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>
2026-09-17 20:09:18 -07:00
Charles YaoandClaude Opus 5 65a47018b4 feat(dify-builder): say the app's name once when the build lands
Spec N4 has the build-complete card headline read "<App name> is ready", with
the annotation 名字只在回复句里念一次;记录卡上不出现 -- said once, in that
sentence, and never repeated on the completion receipt. We said it nowhere: the
card was titled "Build plan" and the engine had no app name to say.

DifyBuilderContext now carries app_name, seeded at session creation through a
get_app_name_fn seam alongside the existing revision/authorize ones. Env.rename_app
returns the name the database actually took rather than None, so the refine
records what landed instead of what it proposed -- the service normalizes after
the engine hands off, and the two can differ.

The headline is interpolated, so it gets a strings.py template or it would
survive localization untranslated. That pattern matches almost anything ending
in "is ready", so it sits last and every more specific template gets first
refusal.

The receipt already omitted the name; there is now a test pinning that.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 18:09:23 -07:00
Charles YaoandClaude Opus 5 aa2b4fe4ab 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>
2026-09-17 17:55:38 -07:00
twwu cecf2a1b9a fix(dify-builder): handle unavailable models during session creation 2026-09-11 18:28:52 +08:00
Charles YaoandClaude Opus 4.8 0597a8b512 feat(dify-builder): localize the update_model "Model changed to X" notice
Follow-up to the reply-language feature. The update_model action commits its
"Model changed to <name>" notice directly via compare_and_advance, bypassing
Runner._commit and thus the Env.localize_items hook, so that one notice stayed
English. Localize it in the service (when the session has a detected
reply_language) with a "Model changed to {name}" catalog template that
translates the frame and re-inserts the model name verbatim.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-10 16:38:48 -07:00
twwu c51e277389 feat(dify-builder): show affected nodes in change cards
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.
2026-09-07 13:44:39 +08:00
Wu Tianwei 8b52c102a3 feat(dify-builder): add live progress and conversation streaming (#41778) 2026-09-04 18:30:44 +08:00
Charles YaoandClaude Opus 4.8 31a972e961 Merge PR #41778 (feat/add-progress-event) into feat/dify-builder
Integrates the FE team's live-progress + conversation-streaming rework
(durable progress events, repository layer, snapshot-consistent reads,
idempotent message retries, EDIT_PUBLISH->EDIT_COMPLETE) and reconciles its
interrupted-step recovery model with the reconciler already on
feat/dify-builder.

Combined recovery model:
- interrupted (crash mid working-step, lock released, nothing durably
  recorded) -> Retry (recovery_continue, re-runs the handler) + Start over
- FAILED (handler raised, recorded by runner.fail) -> Restart only
- a Retry that itself raises lands in FAILED -> Restart (no wedge)

Kept from the PR: EDIT_PUBLISH working / EDIT_COMPLETE terminal, snapshot
refresh in apply_recovery_action, paused reset. Kept from the reconciler:
Retry on interrupted, state-aware access tier (EDIT_PUBLISH added to the
publish tier), apply_repair idempotency filter.

680 dify_builder unit tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 02:14:40 -07:00
Charles YaoandClaude Opus 4.8 3fec0e9458 fix(dify_builder): gate interrupted-step Retry at the working op's access tier
A recovery_continue (Retry) re-runs the interrupted working handler, which
re-invokes the state's privileged op -- publish (RELEASE) at build.publish/
fix.publish, a draft test run (TEST_AND_RUN) at the test/verify states. Before
this, recovery_continue was classified as base EDIT, so a Retry re-ran the
privileged op below its own authorization tier without re-checking. Make
_app_access_for_action state-aware for recovery_continue so it enforces the
same tier the state's normal action requires. recovery_restart only resets +
re-enters the entry state, so it stays EDIT.

Also: an interrupted working step now always surfaces the Retry/Start-over
offer, overriding a stale drift recovery_class a user may have left set at an
earlier waiting gate (drift recovery only applies at a waiting gate).

Addresses the two findings from the final reconciliation review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 00:18:17 -07:00
Charles YaoandClaude Opus 4.8 f623dc3fe1 feat(dify_builder): recover an interrupted working step via Retry/Start over
When a session crashes mid working-step (e.g. build.publish; the async advance
raised and released the lock), it was a locked dead-end: working states surface
no UI actions and the composer locked with no way forward.

- Surface a RecoveryRef offer (can_continue=Retry, can_restart=Start over,
  recovery_class="") on an interrupted working-state view -- built directly so a
  real drift class still wins.
- Allow recovery_continue / recovery_restart at an interrupted working state in
  _internal_action_allowed. recovery_continue returns the working state
  (non-settled) so the runner re-runs the stalled handler; recovery_restart
  resets working fields and re-enters the entry state -- both reuse existing
  recovery machinery, no new action kinds / no runner change.

Adds unit tests (offer surfaced, allowance) + regression tests (recovery_continue
re-runs handle_publish and advances; recovery_restart resets to entry state;
repeat-failure re-surfaces the offer with no wedge).

This reconciler was reviewed as a standalone plan on the pre-merge base; here it
is re-applied onto feat/dify-builder after PR #41677 merged in the reworked
service.py + _internal_action_allowed gate (which makes the allowance necessary).
The apply_repair idempotency + delete-guard commits (safe fix.apply Retry) land
alongside it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 00:07:14 -07:00
twwu 4893c5e621 refactor(dify-builder): separate reasoning from execution progress 2026-09-04 13:43:16 +08:00
twwu eb25c2d8ee feat(dify-builder): add live progress and conversation streaming 2026-09-04 10:16:50 +08:00
Charles YaoandClaude Opus 4.8 de8a5745ea fix(dify_builder): always emit a terminal SSE frame so held version can't go stale
Two paths ended a streaming advance without a terminal `state`/`error`
frame, leaving the SSE client stalled to the deadline with a stale held
version -> its next action 409s:

1. advance_session published the terminal `state` frame outside the
   try/finally and unguarded. If the post-advance view projection
   (get_session_view -> read_graph) or the publish itself raised after a
   SUCCESSFUL advance, the exception escaped and no terminal frame was
   sent. Guard it and fall back to a terminal `error` frame.

2. submit_action_stream emitted a terminal `state` frame on settle only
   for `message`. `update_model` also settles synchronously while bumping
   the version, so it now emits one too -- otherwise the FE keeps the old
   version and 409s on its next action.

Adds regression tests for both (projection-failure -> error frame;
update_model -> state frame).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 08:48:23 +08:00
twwu 3eacff14a6 feat: add typed SSE interactions to Dify Builder 2026-09-03 08:48:23 +08:00
Charles YaoandClaude Opus 4.8 52d241d754 feat(dify-builder): streaming create_fix/build/edit_session with subscribe-before-dispatch
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 22:18:42 -07:00
Charles YaoandClaude Opus 4.8 6888282e95 feat(dify-builder): subscribe-before-dispatch streaming submit_action/message
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 22:18:42 -07:00
twwu 22be55af5f feat: redesign app builder fix flow 2026-08-31 18:18:30 +08:00
Charles YaoandClaude Opus 4.8 ef88f6532c feat(dify-builder): EDIT_AWAIT_TESTDATA gate + run_affected_tests routing
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 10:03:47 -07:00
Charles YaoandClaude Opus 4.8 4f6a5f07b7 feat(dify-builder): BUILD_AWAIT_TESTDATA gate + run_test routing
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-30 10:03:47 -07:00
Charles YaoandClaude Opus 4.8 ac5f48a765 feat(dify-builder): EDIT_AWAIT_REPAIR gate + handler
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 18:53:09 -07:00
Charles YaoandClaude Opus 4.8 26fdde78c2 feat(dify-builder): BUILD_AWAIT_REPAIR gate + handler
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-29 18:20:29 -07:00
Charles YaoandClaude Opus 4.8 4c0efd947d feat(dify-builder): echo chosen model in SessionView + regen contract
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 22:51:27 -07:00
Charles YaoandClaude Opus 4.8 f5815c8723 feat(dify-builder): accept + validate + stamp model_config on create
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-27 22:51:27 -07:00
Charles YaoandClaude Opus 4.8 45ab0d38e6 feat(dify-builder): stamp skill_learning_policy + surface await_learning actions (C-2)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-23 12:55:55 -07:00
Charles YaoandClaude Opus 4.8 7e83264330 refactor: rename Workflow Copilot → Dify Builder
Project rename across api/ + web/: modules, symbols, routes
(/console/api/dify-builder/*), feature flag (DIFY_BUILDER_ENABLED),
Celery queue/tasks, the 7 DB tables (via migration df1b0114de40),
and regenerated contract artifacts. Behavior unchanged; 430 tests green,
ruff clean. Branch renamed feat/copilot-py-backend → dify-builder.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-23 11:18:35 -07:00