Commit Graph
154 Commits
Author SHA1 Message Date
twwu b0045ab1f1 fix(builder): keep native tests in sync through approval
Resume the original run after all approvals and restore terminal progress
and traversed edges on reload.

Show structured approval details and receipts, hide Stop while forms are
open, and recover when a running conversation is cleared.
2026-11-10 14:46:48 +08:00
twwu ffc2f267d0 fix(builder): unify native test streaming and controls
Observe Builder commands and native tests on one session stream. Reconnect to the retained Run without submitting another test.

Use Stop and Resume for task control during native tests and keep approval details available. Allow retained local sandbox bindings to be checked without reading restricted ancestor directories.

Refs: ENG-1448
2026-11-09 20:18:12 +08:00
Charles Yao e3de2d5b9f fix(builder): bind knowledge intent to user source 2026-11-06 10:46:41 -08:00
Charles Yao 2f2c3f6d6c fix(builder): preserve confirmed requirements in knowledge clarification 2026-11-01 08:07:26 -08:00
Charles Yao 94c7ee305d fix(builder): expose current lifecycle settlement without inferring cleanup 2026-10-31 23:07:10 -07:00
Charles Yao ae0e57163e feat(builder): preserve task continuity and confirm Fix switches 2026-10-30 23:13:09 -07:00
Charles Yao f2939c341d feat(builder): add workspace cognition policy and scoped planning materials 2026-10-29 19:41:42 -07:00
Charles Yao 01979fe850 fix(builder): serialize resource failure reasons in conversation responses 2026-10-29 18:35:40 -07:00
twwu a503b0af75 fix(builder): simplify canvas events and refresh lifecycle
Use committed graph notifications and passive edit-target updates so Builder
refreshes do not select nodes or open configuration sidebars.

Drive draft refresh and canvas locks from session events, remove redundant
state and reset paths, and preserve native node tracking and viewport behavior.

Refs: ENG-1325
2026-10-27 13:42:16 +08:00
Charles Yao 6f60a71756 fix(builder): complete approved insertions and bound unchanged recovery 2026-10-24 03:47:04 -07:00
Charles Yao 8c52a91ea0 feat(builder): fence task stop and synchronize safe resume 2026-10-23 16:40:43 -07:00
Charles Yao 186deb854d feat(builder): unify native test controls with session actions 2026-10-22 19:19:47 -07:00
Charles Yao 8e00366280 fix(builder): ground native generation and explicit acceptance evidence 2026-10-22 03:20:00 -07:00
Charles Yao 231e423431 Integrate validated Builder backend testing and CI repairs
Track backend integration, Ruff cleanup and migration reconciliation in ENG-1271. Retain required copy contracts without implementing frontend interactions.
2026-10-20 15:40:06 -07:00
twwu 5a442d510e Merge branch 'main' into feat/dify-builder
Adapt Builder execution to Graphon 0.8 and resolve merge conflicts.
Fix backend type errors, update affected tests, and propagate stream cleanup.
2026-10-20 15:53:02 +08:00
twwu 4c1c41c2ea fix(builder): support initial Chatflow builds (ENG-1242)
Recognize initial templates, generate graphs in the app mode, and replace default nodes atomically after approval.

Normalize node ordering in execution revisions to prevent false 409 conflicts, with frontend and backend regression coverage.
2026-10-19 16:44:09 +08:00
twwu dd08eb9f7a feat(builder): add workflow summaries to plan cards 2026-10-15 17:26:41 +08:00
twwu c899d8c2c0 fix(builder): align conflict checks across execution layers
Share revision exemptions between service admission and queued workers while
retaining fixture and session fences. Use executable graph normalization for
execution admission and native node bindings.

Keep recovery classification separate from handler execution, refresh cached
offers before continuing, and reject drift during rebasing. Add service-to-native
regression coverage and fix JSON execution context typing.
2026-10-15 16:56:57 +08:00
Charles Yao 13368bd5fa fix(builder): bind condition repairs to failed-run evidence
Use authorized historical workflow graphs and bounded sanitized condition and producer-type facts in Build, Edit and Fix diagnosis. Refuse repairs with missing or ambiguous runtime evidence, changed failed IF identities, or unsupported comparison semantics at proposal and write boundaries.

Preserve threshold, selector and graph identity for proven numeric equality-family repairs; withhold explicit credential values before projection. Keep restricted execution admission unchanged.

Validated in isolated Linux: 3067 normal-selection tests and 487 related tests pass, 15 Python files pass Ruff. Scoped independent source review passes; existing Pyrefly recursion warning remains. Live immutable Local/Dev E2E follows normal image qualification.
2026-10-14 15:44:20 -07:00
twwu 89504b7dce refactor(builder): localize fixed copy on the frontend
Move fixed copy into the difyBuilder i18n namespace and resolve scene IDs
using reply_language. Preserve dynamic streaming, historical receipts,
and submitted values.

Show fixed replies immediately with a short transition, detect language
before initial progress, and retire new notices and recovery messages.
Keep recovery and publication validation in place.

Update generated contracts and regression coverage, including progress
snapshot and execution context expectations and test type annotations.
2026-10-14 17:10:20 +08:00
twwu 405f2915dd fix(builder): resume edit analysis after conflict recovery 2026-10-14 16:33:36 +08:00
Charles Yao c6c7dc5eea fix(builder): localize structured decisions and interaction receipts 2026-10-13 13:11:22 -07:00
Charles Yao 6f75eeb244 fix(builder): present JSON safe-test coverage and review gates 2026-10-13 02:47:05 -07:00
Charles Yao a6b95a4c65 feat(builder): add bounded JSON Parse policy and tagged contracts 2026-10-13 01:31:22 -07:00
twwu 5f7202ac07 refactor(builder): remove unused canvas events 2026-10-13 13:10:56 +08:00
Charles Yao 33312a4d06 fix(builder): admit exact native citation display metadata 2026-10-12 17:10:56 -07:00
Charles Yao 4804bad018 feat(builder): safely interpret bounded native Template text 2026-10-12 16:11:10 -07:00
twwu b83ff42a43 fix(builder): stabilize panel refresh and simplify repair choices 2026-10-12 18:27:46 +08:00
Charles Yao c76d89f143 Fix Builder confirmed Tool consent recovery and SQL ownership 2026-10-11 23:22:35 -07:00
Charles YaoandClaude Opus 5.5 391d1a9c49 feat: launch confirmed Tool tests through native Builder Runs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 17:58:50 -07:00
Charles YaoandClaude Opus 5.5 7db172d821 feat: enforce one-shot consent at native Tool boundaries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:27:47 -07:00
Charles YaoandClaude Opus 5.5 cad55b260d fix: persist Builder consent reference, replay current execute state and clean consents with app
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:18:38 -07:00
Charles YaoandClaude Opus 5.5 9d034f2359 fix: bound full Gmail envelope path, add ConfirmedToolBinding, sanitize malformed selectors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:45:52 -07:00
Charles YaoandClaude Opus 5.5 1e90320ad1 fix: restore locked context discriminator and drop pure completion recorder
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:36:22 -07:00
Charles YaoandClaude Opus 5.5 ae8f512b99 feat: define scoped Gmail read consent policy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:27:22 -07:00
Charles Yao ae9fe752ce feat(builder): seal trusted native execution evidence 2026-10-10 02:55:36 -07:00
Charles Yao df9fc958c0 fix(builder): sanitize numeric defaults and align fixed headers 2026-10-10 02:05:11 -07:00
Charles Yao f8c1a17b74 feat(builder): enforce exact native restricted capabilities 2026-10-10 01:47:25 -07:00
Charles Yao 90af81b368 feat(builder): bind restricted execution through native admission 2026-10-10 01:04:03 -07:00
Charles Yao 995bda3548 fix(builder): harden snapshot admission and sidecar cleanup 2026-10-10 00:26:42 -07:00
Charles Yao 67b5f09bd4 feat(builder): bind safe execution requests and fixture ownership 2026-10-10 00:09:15 -07:00
Charles Yao 3e0e486d35 fix(dify-builder): synchronize publication metadata and requirement language
Refresh the native publication query after Builder publication and prevent
superseded workflow initialization from overwriting newer metadata. Keep
intentional query cancellation and aborted JSON parsing out of error logs
while preserving genuine failure reporting.

Pass the persisted reply language into requirement cognition and preserve
literal business values in generated forms.
2026-10-09 19:47:03 -07:00
Charles Yao 6bcc64d4ca fix(dify-builder): validate generation and gate publication on durable verification 2026-10-09 19:14:43 -07:00
Charles YaoandClaude Opus 5 07c43bc671 fix(dify-builder): the requirements form never asks for the goal back
A PM hit a requirements card whose first field was 需求描述 -- the requirement
they had already typed as their opening message -- shipped BLANK, so there was
nothing to write and the card read as test-data entry.

Two halves, because prose to the model alone is not enough.

The prompt now states the goal is already known and given below, bars a field
that restates it, and requires every field to ask for something the goal does
not already say. No test asserts that wording: a substring check on a prompt is
a change detector that passes for any badly-worded prompt containing the word
and fires on every intentional rewrite. An instruction to a model is verified by
the model's behaviour.

The deterministic backstop, _answer_goal_restatements, fills a goal-restating
field the model left blank with goal_text -- which is what _degraded_form has
always done, an instinct the LLM path had lost. It repairs and never overwrites,
matches names EXACTLY against a curated set (no bare "description"/"描述": a
report description is not the goal), and skips select/bool/number/json where
free text cannot render. Those limits are what keep the goal sentence out of the
API-key box that _scrub_invented_defaults blanks on purpose.

That blank credential box was the second finding: 4 of 4 live runs left a field
empty with no explanation. build_form_fields fed both the requirements and
edit-rules cards while carrying only key/label/type/options, dropping the hint
that testdata_form_fields 350 lines away already carries. It now carries hint,
and _explain_blank_fields attaches one to every field that ends up without a
value -- the model offering nothing and the scrubber blanking a credential look
identical to the user, so both get explained.

The hint is registered in strings.PLAIN because the Localizer is a CATALOG
matcher, not a translator: _source_to_translate returns None for an unregistered
string and it then ships in English on a Chinese form. The test asserts
`hint in strings.PLAIN` -- the contract -- rather than the wording.

Verified: 1491 tests green across services/dify_builder + core/dify_builder;
mypy at its recorded 19-error baseline, zero new; ruff clean on every touched
file. Mutation check 3/3 and 3/3 -- removing any single guard fails exactly one
test, including the unregistered-hint case. Live against a real model in the
local stack: the PM's exact reported form is repaired with the other four fields
byte-identical, and 4 fresh runs produced no restating field and a correctly
hinted blank credential.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-08 13:08:42 -07:00
Wu Tianwei b3e3540c43 refactor: improve Dify Builder session UX (#42760) 2026-09-24 15:18:32 +08:00
Charles YaoandClaude Opus 5 64127c17e2 fix(dify-builder): Fix explains the guard's verdict instead of implying it
Parity for the two semantic guards, which Task 8 committed to for exactly this
reason: a guard Edit honours and Fix merely implies is worse than no guard,
because the inconsistency is invisible to whoever reads only one of them.

The hole was not the one I reported at the end of round 2. Probed against the
literal S6 batch, `fix.propose_repair` already refuses to hand on a batch
carrying any rejection -- unlike Edit it never had a fallback to the first
attempt, so the condemned batch was never written. What it did have was a BLIND
surface: the engine-grounded reason was discarded and `_no_fix()` returned the
generic "No safe automatic fix found", which the handler then stored on
`fc.risk` and showed to nobody. The human at the gate was told a fix could not
be found and given nothing to act on -- while the guard had just described the
aggregator, the node and the selector that would have made a manual fix obvious.

So: `_no_fix()` takes an optional reason (one dead-end path, not a second), a
`would_run_wrong` verdict fills it in, and `handle_propose` renders it on an
ErrorCard beside the reply line -- the same division of labour Edit's refusal
uses. The verdict is read off the structured `would_run_wrong` list, never off
the rejection prose, and deliberately NOT off `rejections` at large: those
inline whole `intent.args` and belong in a re-prompt, not on a card, and a
structural refusal says nothing about the batch that remains. A purely
structural refusal therefore still ends at the generic sentence, as before.

The `would_run_wrong` check is written as its own line rather than left to the
broader rejection check it currently duplicates. That redundancy is a property
of the line below it, not of the guard: if Fix is ever loosened the way Edit
was, this is what still holds -- and it is what carries the reason either way.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 23:14:26 -07:00
Charles YaoandClaude Opus 5 4082cceb24 fix(dify-builder): never write a batch the agent has already judged wrong
The two semantic guards describe defects the ENGINE ACCEPTS: a new branch wired
into a variable-aggregator that was never told about it, an array re-sent with
an element quietly dropped. Both drafts start, both run green, and neither
produces a `DraftWouldNotStartError` for `apply_repair` to veto. So the guards
had one hole left, and it was in the agent's own fallback:
`build_edit_intents` spent its corrective re-prompt on the guard's reason and
then, if the retry yielded nothing usable, returned the FIRST attempt's
applicable intents -- the very batch the guard had condemned. The guard fired
and the silently-wrong draft was written anyway.

`vet_intents` now carries `would_run_wrong` alongside `rejections`: the subset
of refusals the engine does not make. The two kinds fail differently and a
caller has to tell them apart without reading the text -- a structural refusal
DROPPED its intent, so what survives is a batch nobody judged wrong and keeping
it is a deliberate choice; a semantic verdict drops nothing and condemns what
remains. Reading that off the prose would be the same mistake as keying control
flow on the model's prose.

`build_edit_intents` therefore raises `ProposalWouldRunWrongError` instead of
returning a batch still carrying one, whichever attempt it came from. Nothing is
written and nothing is attempted. `handle_plan_approval` catches it into the
same `_change_not_applied` recovery a refused write gets: the plan stays at its
gate, the reason goes on an error card and into `last_edit_rejection` for the
next attempt.

Surfacing is a real exit now and not the dead end it would have been before this
plan: Task 7 gave the gate a revert and a keep-adjusting action, and Task 6
carries the refusal into the next proposal, so the user can change the rules and
the retry is not blind.

`ProposalWouldRunWrongError` is deliberately neither a `DraftWouldNotStartError`
nor a `ValueError`: the draft WOULD start, and a broad `except ValueError`
around a port write must not swallow it as if the write had been attempted.

`_change_not_applied` takes the activity to mark failed, defaulting to the write
where the usual refusal happens. This exit gives up during "prepare", so failing
the write would have shown a LATER step failed while an earlier one was still
running, with the step between never resolving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 23:14:25 -07:00
Charles YaoandClaude Opus 5 10a6e0b9ef fix(dify-builder): a re-route is a change, and a comment is not a claim
Four small things the whole-branch review found, none of which changes what a
guard decides.

`_change_not_applied`'s docstring said the gate's other two exits "both clear
this store". Task 7's fix round moved the clear to `handle_impact_analysis` and
made it conditional on the rules actually changing, so neither
continue_adjusting nor revert clears it -- and two tests assert exactly that. A
maintainer trusting the comment would re-add the clear to the revert path,
which is the bug Task 7 removed. Reworded to name the sole, conditional clear
and the two tests that pin it.

`already_present_predicate` keyed a connect on `(from, to)` alone, so a repair
that re-routes an arm of a branch between the same pair of nodes matched the
edge already there and was dropped as a no-op -- in the dry run and at
`apply_repair`, which runs the same predicate. The handle is now part of the
key on both sides, defaulting to "source" the way graphon reads a missing one.

`_build_node`'s defaults merge was shallow, so `model: {provider, name}`
replaced the whole default block and the node was refused for `model.mode` --
spending the one corrective re-prompt on a field the default was carrying. It
now merges one level into a dict-valued default, with one exclusion that the
suite caught immediately: a block whose `type` the caller did not write is left
alone, because `type` reinterprets its siblings and completing a partial
`authorization` from the default produces a node that looks authorized, sends
no credential, and is written in silence.

`_withheld_args` redacted a `value` shaped like a whole node `data`, which is
not what `set_node_config` sends: `path="headers"` with a header line reached
the retry prompt unredacted. The value is now also redacted as what it is --
the value at that path -- by wrapping it back into its own nesting and running
the one redaction profile over it, so `credentials` stays the single definition
of what a credential is. Unreachable with a stored secret today.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 23:11:17 -07:00
Charles YaoandClaude Opus 5 43f22f6280 fix(dify-builder): a rejected edit can be reverted or adjusted, not only re-approved
edit.plan_approval offered exactly one action, approve_plan, while the card
its own refusal emitted said "Adjust it and approve again" -- an affordance
that did not exist. A chat message kept the PC at the gate and never touched
edit_rules, so an approval the engine refused had no exit at all.

The gate now also offers Continue adjusting and Discard this change plan,
admitted end to end: _ACTIONS_FOR renders them, _BACKEND_ACTIONS_FOR
[EDIT_PLAN_APPROVAL] admits re_fix and undo, and handle_plan_approval routes
both past the early return that used to swallow every non-approve kind. The
discard reuses perform_revert against the checkpoint handle_impact_analysis
already mints; Continue adjusting reuses handle_review's re_fix body, now
shared as _resume_adjusting, which returns to edit.impact_analysis with the
edit-rules form repopulated and unfrozen.

last_edit_rejection is cleared where its cause is: in handle_impact_analysis,
and only when the submitted payload actually changes fc.edit_rules. A stored
refusal goes stale precisely when the rules it refused stop being the rules,
and nowhere else -- so the actions that merely route the user to the form do
not clear it, and neither does edit.reverted's Retry. A revert taken at the
gate writes nothing, which makes it the one path reaching edit.reverted with
a refusal still true of the restored draft; clearing there would have made
Retry re-propose the identical plan blind.

Copy corrected in strings.py and handlers_edit.py to name the three buttons
that now exist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 21:11:06 -07:00
Charles YaoandClaude Opus 5 59cc7043a3 fix(dify-builder): a rejected edit tells the next attempt what the engine said
At edit.plan_approval the gate re-reads an UNCHANGED draft, so edit_rules and
graph are byte-identical on every re-approval, and _change_not_applied stored
nothing about WHY the port refused the write. The live F4 user approved the
same plan three times and got a byte-identical card each time.

Add DifyBuilderContext.last_edit_rejection: the raw text of the
DraftWouldNotStartError/ValueError the port raised, capped at 2000 chars,
stored by _change_not_applied and passed into build_edit_intents, which quotes
it into the user prompt as a "PREVIOUS ATTEMPT REJECTED BY THE ENGINE" block.
It is engine-sourced by construction -- a PreflightError's every message is
built by re-validating a credentials.redact_node_config copy of the node, and
graph_ops' ValueErrors carry only paths, ids and type names -- so no model
prose enters. Nothing branches on it. Cleared on a successful write, on a new
edit goal, and by the recovery reset.

Close the silent false success that left, rather than only recording it.
new_preflight_problems now takes the ids whose NODE DATA the batch wrote and
gives them no exemption at all: a node this batch wrote must be startable, not
merely no worse than it was. Keyed on locations alone, a repair that swaps one
bad value for another at the same field path -- or fixes one of three missing
fields -- produced nothing new, was written, and the card said "Applied the
changes" over a draft that dies at Graph.init. Since a repair's culprit is by
definition already invalid, that was Fix's main line. Every OTHER node keeps
the location-keyed exemption, so a defect the user left on the canvas still
cannot veto an unrelated change.

That set is deliberately narrower than a change set, on both sides (graph_ops
.DryRun gains the field filter_applicable already computed and discarded;
apply_repair keeps a separate written_nodes beside changed_nodes). connect is
excluded, because apply_connect reports both endpoints and an edge cannot
change either one's validate_node_config verdict -- counting them would let
merely wiring an already-broken node veto the batch. The heal is excluded too,
because heal_nodes_for_preflight scans the whole graph and a node it merely
normalized is not one this batch is answerable for.

Also, from the reviews of preflight.py: route an intent's config/value through
redact_node_config before a structural rejection line inlines it; key a
non-ValidationError crash on its truncated message as well as its class, and
keep that message out of the text the model is shown; and correct the
_CRASH_LOCATION comment -- verified against graphon, the rule is a config key
present AND type absent-or-invalid.

Finally, stop the docstrings overclaiming: redaction covers authorization,
headers and params, and a secret typed into a request body value is NOT
covered and reaches the stored text. Widening the profile is the wrong fix --
it shares the sentinel guard that refuses any write carrying REDACTED, so it
would make http-request bodies uneditable. Recorded in credentials.py as a
follow-up needing a separate persistence-only profile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 20:40:15 -07:00