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>
WorkflowService.publish_workflow returns a single Workflow (-> Workflow), but
dify_port.publish still unpacked it as (workflow, retirement_candidates) -- a
stale signature. Every other caller was updated when the return type changed
(via the main merge); only the Dify Builder port was missed, so every publish
crashed with "TypeError: cannot unpack non-iterable Workflow object", surfaced
to the user as a generic "step failed" SSE frame.
Assign the single return value (retirement_candidates was unused anyway). The
existing test_dify_port publish test mocked publish_workflow as a 2-tuple, which
masked the bug -- corrected it to the real -> Workflow signature so it now guards
the regression.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A build for a workflow app whose goal reads like a chatbot produced an empty
canvas while the assistant reported "Workflow built on the canvas.": the plan
was chatbot-shaped, build_nodes fed it to the generator (mode="workflow"), the
LLM emitted a chatflow graph (answer node / no end node), the generator
rejected it as MISSING_TERMINAL -- a class it does NOT retry -- and build_nodes
swallowed the error into []. handle_plan_approval then treated [] as a
successful build: it deleted the placeholder start and advanced to
build.execution, leaving nothing on the canvas.
Two parts:
1. build_nodes now prepends an explicit workflow-topology directive (one
start + >=1 end node, no answer nodes) to the generator instruction, and
retries ONCE on an error/empty result with the specific error fed back as
a corrective nudge -- covering the terminal-node failure the shared
generator skips.
2. handle_plan_approval refuses to fake success: when build_nodes yields zero
create_node intents it surfaces an honest error and stays in
build.plan_approval (retryable) without deleting the placeholder, instead
of emptying the canvas and claiming a build. A loop-back re-approve still
returns the fixed create intents (only filtered as already-present), so
zero create intents unambiguously means a genuine generation failure.
Adds regression tests for both. Note: part 1 improves LLM reliability
(verifiable live); part 2 is the deterministic guarantee against the
false-success empty canvas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
It was stubbed to [] in LlmBuilderAgent, canned in PlaceholderAgent, and never
called by any handler — the Build failure path reuses Fix cognition (diagnose +
propose_repair). Removed from the DifyBuilderAgent Protocol, both implementations,
and all test doubles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final consolidated fix wave from whole-branch review of the Real Build +
Edit Cognition slice:
- _ground now overwrites the model dict on ANY create_node whose config
carries a model dict (llm, question-classifier, parameter-extractor),
not just "llm", so a drifted/hallucinated provider/name on those other
node types no longer flows through ungrounded.
- to_intents now carries a generator edge's sourceHandle/targetHandle
into the connect intent's args (source_handle/target_handle) so
branching nodes (if-else, question-classifier) wire onto the correct
port instead of collapsing onto the default handle; edges without
handles still produce bare {from_node, to_node} args.
- Cleared the 17 ruff errors (PT018/E302/E306/E501) across the slice's
test files by splitting composite asserts, adding missing blank
lines, and wrapping long lines -- no assertions weakened, no noqa.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
analyze_impact/propose_edit_plan/build_edit_intents now call
services.dify_builder.agent.edit.* via _model_or_none() instead of
delegating to the canned PlaceholderAgent. 9 of the Protocol's 14
Build/Edit/Fix methods now use real LLM cognition; only
generate_mock_inputs and propose_build_repair (returns []) remain
deferred pending the live-test step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
analyze_impact could raise on a non-list target_node_ids from the LLM (no
isinstance guard, unlike fields/values) -- violated the never-crash
contract. Guard it the same way. Also collapse the fake models' invoke_llm
signatures to **_kwargs (underscore-prefixed, no noqa) per the ARG lint
constraint, and pin two more malformed-shape degrade paths
(build_edit_intents non-list intents, propose_edit_plan non-list plan).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
handle_test_and_repair's AssistantTurnItem.cards was unconditional even
though error/change_set cards are now only appended when
propose_build_repair returns intents -- the manifest claimed cards that
were never attached in the neutral (empty-repair) path. Make cards
conditional to match what's actually appended, and assert it in both
branches.
Remove three noqa comments added in the prior commit that suppressed
ARG001/ARG005 on new test code (the global constraint disallows noqa
for non-enabled rules); switch the genuinely-unused monkeypatch
args/lambda params to underscore-prefixed names instead, and drop a
build_nodes monkeypatch that exercised nothing. Add
test_remaining_build_methods_thread_model_and_tenant_args to cover
propose_plan_v1/discover_resources/bind_resources/build_nodes/
learn_from_build's model/tenant_id/model_config threading, matching
the existing analyze_goal coverage.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
LlmBuilderAgent's 6 Build methods (analyze_goal, propose_plan_v1,
discover_resources, bind_resources, build_nodes, learn_from_build) now
delegate to services.dify_builder.agent.build instead of the canned
PlaceholderAgent. propose_build_repair returns [] -- there's no real
failure signal to diagnose until the live test step is de-canned.
handle_test_and_repair now conditions the error card + apply_repair on
a non-empty propose_build_repair result: placeholder mode keeps today's
"found and fixed" arc, while real cognition (which never reports a
failure yet) emits a neutral "no issues found" review summary instead
of a fake error/fix on a hardcoded node id that real build_nodes
wouldn't have generated anyway.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Grounding only overwrites provider+name on an llm node's model dict now,
preserving whatever mode/completion_params the generator produced instead
of synthesizing a temperature when none was present. knowledge-retrieval
grounding now assigns each node its own dataset_ids list copy instead of
aliasing one shared list across every KB node in the graph.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Fix S110: replace bare except-pass with explicit degrade behavior
- Fix E501: wrap long function signatures to <=120 chars
- Fix I001: sort imports in test file (resources to top)
- Fix PT018: split composite assertions into separate statements
- Add _BoomInstance for provider-error testing
- Add 5 degrade tests (analyze_goal, propose_plan_v1 x2, discover_resources, learn_from_build)
- Remove unused _KIND_BY_PREFIX dict
- Add noqa comments for test mock arguments
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Only map the first start-type node to canonical 'start'; additional starts
retain their original ids to prevent collisions and ambiguous intents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FINDING 1 (dataset visibility): DatasetService.get_datasets requires user
parameter and silently omits ONLY_ME/PARTIAL_TEAM datasets on RBAC tenants.
Query Dataset model directly by tenant_id for complete grounding inventory.
FINDING 2 (normalization guard): Move fetch+normalize inside _safe to prevent
malformed records from raising after _safe returns. Use lambda builders to wrap
complete resource transformation including record normalization.
FINDING 3 (test coverage): Add test_mixed_failure_partial_success (one source
fails, others succeed) and test_malformed_tool_record_degrades (missing
tool_name key gracefully degrades).
Minor: compute model id once and reuse for both id and label fields; ensure
E302 two blank lines between test functions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement resources.py with list_tenant_resources() to normalize tenant's
configured models, datasets, and tools to ResourceRef(id, label, meta,
readiness). Each source degrades to empty list on failure for graceful
degradation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
filter_applicable's per-intent try/except only caught (ValueError, TypeError),
so a hallucinated insert_between with a non-dict edge arg raised an uncaught
AttributeError and crashed the whole advance instead of being rejected by the
dry-run. Broaden to except Exception (ruff does not flag it, so no noqa is
added). Add a regression test for the string-edge case.
Also update llm_agent.py's stale module docstring (Fix cognition is now real,
not delegated; Protocol has 14 methods, not 13) and fix a stray closing paren
in a test_handlers_fix.py comment.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_touched_node_types looked up connect intents via a nonexistent node_id key,
always resolving to "" and missing the external-side-effect type of either
endpoint. Branch connect on from_node/to_node instead, and add a test proving
the external risk floor fires on a non-structural set_node_config against an
http-request node.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>