refactor(dify-builder): drop the resource conflict policy

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>
This commit is contained in:
Charles YaoandClaude Opus 5 committed 2026-09-20 14:10:34 -07:00
1 parent 583f7fa55f
commit e8b14b50ff
13 files changed
+24 -48

No files matched your search

+1 -14
View File
@@ -394,15 +394,6 @@ class SessionView:
app_revision: AppRevision | None = None
@dataclass
class ConflictPolicyOption:
"""One selectable conflict-resolution policy in a ``resource_select`` card."""
id: str
label: str
recommended: bool = False
@dataclass
class PreflightIssue:
"""One checklist finding in a ``preflight_context`` card.
@@ -592,14 +583,11 @@ class ChallengeCard(_Card):
@dataclass
class ResourceSelectCard(_Card):
"""Resource discovery: multi-select ``recommended`` resources +
single-select ``conflict_policy_options``.
"""
"""Resource discovery: multi-select ``recommended`` resources."""
kind: ClassVar[CardKind] = CardKind.RESOURCE_SELECT
recommended: list[ResourceOption] = field(default_factory=list)
conflict_policy_options: list[ConflictPolicyOption] = field(default_factory=list)
@dataclass
@@ -737,7 +725,6 @@ class EditRulesPayload:
@dataclass
class ResourcesPayload:
conflict_policy: str
resource_ids: list[str] = field(default_factory=list)
+2 -11
View File
@@ -20,7 +20,6 @@ from core.dify_builder.contract import (
ChallengeCard,
ChangeSetCard,
CheckpointCard,
ConflictPolicyOption,
DecisionItem,
ErrorCard,
ExecutionProgress,
@@ -266,10 +265,6 @@ def handle_initial_plan(env: Env, turn: Turn, s: Session, fc: DifyBuilderContext
fc,
ResourceSelectCard(
recommended=options,
conflict_policy_options=[
ConflictPolicyOption(id="audited", label="Prefer audited", recommended=True),
ConflictPolicyOption(id="ask", label="Ask each time"),
],
),
)
if not options:
@@ -322,16 +317,12 @@ def handle_resource_recommendation(env: Env, turn: Turn, s: Session, fc: DifyBui
fc.checkpoint_seq = fc.next_seq
resource_ids: list[str] = []
conflict_policy = ""
if turn.action is not None and isinstance(turn.action.payload, dict):
raw_ids = turn.action.payload.get("resource_ids")
if isinstance(raw_ids, list):
resource_ids = [r for r in raw_ids if isinstance(r, str)]
cp = turn.action.payload.get("conflict_policy")
if isinstance(cp, str):
conflict_policy = cp
fc.resource_selection = {"resource_ids": resource_ids, "conflict_policy": conflict_policy}
fc.plan_items = env.agent.bind_resources(list(fc.plan_items), resource_ids, conflict_policy)
fc.resource_selection = {"resource_ids": resource_ids}
fc.plan_items = env.agent.bind_resources(list(fc.plan_items), resource_ids)
fc.plan_version_tag = "v2"
progress.activate("build-create-checkpoint")
+1 -1
View File
@@ -127,7 +127,7 @@ class PlaceholderAgent:
)
]
def bind_resources(self, plan_items: list[str], resource_ids: list[str], conflict_policy: str) -> list[str]:
def bind_resources(self, plan_items: list[str], resource_ids: list[str]) -> list[str]:
return [
"Ingest source documents",
"Retrieve from Company Knowledge Base",
+1 -1
View File
@@ -85,7 +85,7 @@ class DifyBuilderAgent(Protocol):
def discover_resources(self, plan_items: list[str]) -> list[ResourceOption]: ...
def bind_resources(self, plan_items: list[str], resource_ids: list[str], conflict_policy: str) -> list[str]: ...
def bind_resources(self, plan_items: list[str], resource_ids: list[str]) -> list[str]: ...
def build_nodes(self, plan_items: list[str], resource_ids: list[str] | None = None) -> BuildNodesResult: ...
+1 -3
View File
@@ -168,9 +168,7 @@ def discover_resources(
]
def bind_resources(
model, tenant_id: str, plan_items: list[str], resource_ids: list[str], conflict_policy: str
) -> list[str]:
def bind_resources(model, tenant_id: str, plan_items: list[str], resource_ids: list[str]) -> list[str]:
inv = resources.list_tenant_resources(tenant_id)
by_id = {r.id: r for r in (*inv.datasets, *inv.tools, *inv.models)}
labels = [by_id[rid].label for rid in resource_ids if rid in by_id]
+2 -2
View File
@@ -125,8 +125,8 @@ class LlmBuilderAgent:
self._reasoning_for("discover-resources"),
)
def bind_resources(self, plan_items, resource_ids, conflict_policy):
return build.bind_resources(self.model_or_none(), self._tenant_id, plan_items, resource_ids, conflict_policy)
def bind_resources(self, plan_items, resource_ids):
return build.bind_resources(self.model_or_none(), self._tenant_id, plan_items, resource_ids)
def build_nodes(self, plan_items, resource_ids=None):
return build.build_nodes(self._tenant_id, self._model_config, plan_items, resource_ids or ())
@@ -418,7 +418,7 @@ class StubAgent:
def discover_resources(self, _plan_items):
return []
def bind_resources(self, _plan_items, _resource_ids, _conflict_policy):
def bind_resources(self, _plan_items, _resource_ids):
return []
def build_nodes(self, _plan_items, _resource_ids=None):
@@ -175,7 +175,7 @@ def test_initial_plan_find_resources_advances_to_resource_recommendation():
assert res.next == PcState.BUILD_RESOURCE_RECOMMENDATION
rs = next(i for i in res.items if i.kind == "resource_select")
assert rs.payload["recommended"][0]["readiness"] == "ready"
assert len(rs.payload["conflict_policy_options"]) == 2
assert "conflict_policy_options" not in rs.payload
def test_resource_recommendation_confirm_creates_checkpoint_and_plan_v2():
@@ -189,7 +189,7 @@ def test_resource_recommendation_confirm_creates_checkpoint_and_plan_v2():
turn = Turn(
action=Action(
kind="confirm_resources",
payload={"resource_ids": ["kb-company"], "conflict_policy": "audited"},
payload={"resource_ids": ["kb-company"]},
base_version=1,
),
actor=_actor(),
@@ -198,7 +198,7 @@ def test_resource_recommendation_confirm_creates_checkpoint_and_plan_v2():
assert res.next == PcState.BUILD_PLAN_APPROVAL
assert res.context.plan_version_tag == "v2"
assert res.context.resource_selection == {"resource_ids": ["kb-company"], "conflict_policy": "audited"}
assert res.context.resource_selection == {"resource_ids": ["kb-company"]}
assert res.context.checkpoint_id
assert res.context.last_structure_fingerprint != ""
cp, _snap = repo.get_checkpoint(res.context.checkpoint_id)
@@ -1010,7 +1010,7 @@ def test_full_build_flow_goal_to_complete():
assert out.current_state == PcState.BUILD_RESOURCE_RECOMMENDATION
# 4) confirm_resources -> build.plan_approval
confirm_payload = {"resource_ids": ["kb-company"], "conflict_policy": "audited"}
confirm_payload = {"resource_ids": ["kb-company"]}
confirm_action = Action(kind="confirm_resources", payload=confirm_payload, base_version=out.version)
out = runner.advance(s.id, Turn(action=confirm_action, actor=_actor()))
assert out.current_state == PcState.BUILD_PLAN_APPROVAL
@@ -1082,7 +1082,7 @@ def test_full_build_flow_keep_draft_reaches_complete_without_publish():
reqs_action = Action(kind="submit_requirements", base_version=out.version)
out = runner.advance(s.id, Turn(action=reqs_action, actor=_actor()))
out = runner.advance(s.id, Turn(action=Action(kind="find_resources", base_version=out.version), actor=_actor()))
confirm_payload = {"resource_ids": ["kb-company"], "conflict_policy": "audited"}
confirm_payload = {"resource_ids": ["kb-company"]}
confirm_action = Action(kind="confirm_resources", payload=confirm_payload, base_version=out.version)
out = runner.advance(s.id, Turn(action=confirm_action, actor=_actor()))
out = runner.advance(s.id, Turn(action=Action(kind="approve_repair", base_version=out.version), actor=_actor()))
@@ -1123,7 +1123,7 @@ def test_review_continue_adjusting_then_reapprove_is_idempotent():
reqs_action = Action(kind="submit_requirements", base_version=out.version)
out = runner.advance(s.id, Turn(action=reqs_action, actor=_actor()))
out = runner.advance(s.id, Turn(action=Action(kind="find_resources", base_version=out.version), actor=_actor()))
confirm_payload = {"resource_ids": ["kb-company"], "conflict_policy": "audited"}
confirm_payload = {"resource_ids": ["kb-company"]}
confirm_action = Action(kind="confirm_resources", payload=confirm_payload, base_version=out.version)
out = runner.advance(s.id, Turn(action=confirm_action, actor=_actor()))
@@ -1263,7 +1263,7 @@ def test_execution_revert_then_retry_after_revert_reapprove_is_idempotent():
s.id, Turn(action=Action(kind="submit_requirements", base_version=out.version), actor=_actor())
)
out = runner.advance(s.id, Turn(action=Action(kind="find_resources", base_version=out.version), actor=_actor()))
confirm_payload = {"resource_ids": ["kb-company"], "conflict_policy": "audited"}
confirm_payload = {"resource_ids": ["kb-company"]}
out = runner.advance(
s.id,
Turn(
@@ -132,7 +132,7 @@ def test_propose_plan_v1_and_bind_resources_return_ordered_strings():
v1 = a.propose_plan_v1({"currency": "USD"})
assert v1
assert all(isinstance(x, str) for x in v1)
v2 = a.bind_resources(v1, ["kb-company"], "audited")
v2 = a.bind_resources(v1, ["kb-company"])
assert v2
assert all(isinstance(x, str) for x in v2)
@@ -125,7 +125,7 @@ class _StubAgent:
def discover_resources(self, _plan_items):
return []
def bind_resources(self, _plan_items, _resource_ids, _conflict_policy):
def bind_resources(self, _plan_items, _resource_ids):
return []
def build_nodes(self, _plan_items, _resource_ids=None):
@@ -143,7 +143,7 @@ def test_discover_resources_keeps_only_ids_the_model_actually_picked(monkeypatch
def test_bind_resources_names_bound_label(monkeypatch):
monkeypatch.setattr(build.resources, "list_tenant_resources", lambda t: resources.TenantResources( # noqa: ARG005
models=[], datasets=[resources.ResourceRef(id="kb-1", label="Company KB")], tools=[]))
out = build.bind_resources(None, "t1", ["Retrieve knowledge"], ["kb-1"], "audited")
out = build.bind_resources(None, "t1", ["Retrieve knowledge"], ["kb-1"])
assert any("Company KB" in item for item in out)
@@ -61,8 +61,8 @@ def test_remaining_build_methods_thread_model_and_tenant_args(monkeypatch):
seen["discover_resources"] = (model, tenant_id, plan_items)
return []
def mock_bind_resources(model, tenant_id, plan_items, resource_ids, conflict_policy):
seen["bind_resources"] = (model, tenant_id, plan_items, resource_ids, conflict_policy)
def mock_bind_resources(model, tenant_id, plan_items, resource_ids):
seen["bind_resources"] = (model, tenant_id, plan_items, resource_ids)
return []
def mock_build_nodes(tenant_id, model_config, plan_items, resource_ids=()):
@@ -89,8 +89,8 @@ def test_remaining_build_methods_thread_model_and_tenant_args(monkeypatch):
agent.discover_resources(["step"])
assert seen["discover_resources"] == ("MODEL", "t1", ["step"])
agent.bind_resources(["step"], ["rid"], "audited")
assert seen["bind_resources"] == ("MODEL", "t1", ["step"], ["rid"], "audited")
agent.bind_resources(["step"], ["rid"])
assert seen["bind_resources"] == ("MODEL", "t1", ["step"], ["rid"])
agent.build_nodes(["step"])
assert seen["build_nodes"] == ("t1", model_config, ["step"], ())
@@ -97,7 +97,7 @@ def test_context_round_trips_build_fields():
requirements={"currency": "USD", "prefer_audited": True},
plan_items=["Retrieve", "Summarize", "Emit"],
plan_version_tag="v2",
resource_selection={"resource_ids": ["kb-company"], "conflict_policy": "audited"},
resource_selection={"resource_ids": ["kb-company"]},
built_node_ids=["start", "llm", "end"],
)
out = context_from_dict(context_to_dict(fc))