diff --git a/api/core/dify_builder/placeholder_agent.py b/api/core/dify_builder/placeholder_agent.py index 54d59ce1adc..18607b322ca 100644 --- a/api/core/dify_builder/placeholder_agent.py +++ b/api/core/dify_builder/placeholder_agent.py @@ -175,6 +175,15 @@ class PlaceholderAgent: ) ] + def learn_from_build( + self, + goal_text: str, + requirements: dict[str, Any], + plan_items: list[str], + built_node_ids: list[str], + ) -> str: + return "Reusable skill: a Start->Knowledge->LLM->End summary workflow" + # -- Edit cognition (Slice 3; fixed, deterministic canned output) -- def analyze_impact(self, goal_text: str, graph: Graph) -> dict[str, Any]: diff --git a/api/core/dify_builder/ports.py b/api/core/dify_builder/ports.py index 81ce663b8c9..1d4513cbd9f 100644 --- a/api/core/dify_builder/ports.py +++ b/api/core/dify_builder/ports.py @@ -91,6 +91,14 @@ class DifyBuilderAgent(Protocol): def propose_build_repair(self, built_node_ids: list[str]) -> list[MutationIntent]: ... + def learn_from_build( + self, + goal_text: str, + requirements: dict[str, Any], + plan_items: list[str], + built_node_ids: list[str], + ) -> str: ... + # -- Edit cognition (Slice 3; canned in PlaceholderAgent) -- def analyze_impact(self, goal_text: str, graph: Graph) -> dict[str, Any]: ... diff --git a/api/tests/unit_tests/core/dify_builder/fakes.py b/api/tests/unit_tests/core/dify_builder/fakes.py index e417f96008c..94bac27fc1d 100644 --- a/api/tests/unit_tests/core/dify_builder/fakes.py +++ b/api/tests/unit_tests/core/dify_builder/fakes.py @@ -367,6 +367,10 @@ class StubAgent: def propose_build_repair(self, _built_node_ids): return [] + def learn_from_build(self, _goal_text, _requirements, _plan_items, _built_node_ids): + self.learn_calls = getattr(self, "learn_calls", 0) + 1 + return "stub-skill" + def analyze_impact(self, _goal_text, _graph): return {"edit_rules": {}, "target_node_ids": []} diff --git a/api/tests/unit_tests/core/dify_builder/test_placeholder_agent.py b/api/tests/unit_tests/core/dify_builder/test_placeholder_agent.py index be4952de014..91c3e47bffc 100644 --- a/api/tests/unit_tests/core/dify_builder/test_placeholder_agent.py +++ b/api/tests/unit_tests/core/dify_builder/test_placeholder_agent.py @@ -180,3 +180,10 @@ def test_propose_build_repair_targets_the_llm_node(): assert len(intents) == 1 assert intents[0].op == "set_node_config" assert intents[0].args["node_id"] == BUILD_LLM_ID + + +def test_placeholder_learn_from_build_returns_descriptor(): + from core.dify_builder.placeholder_agent import PlaceholderAgent + + out = PlaceholderAgent().learn_from_build("summarize a PDF", {}, ["step"], ["llm"]) + assert isinstance(out, str) and out != "" diff --git a/api/tests/unit_tests/core/dify_builder/test_ports.py b/api/tests/unit_tests/core/dify_builder/test_ports.py index a5e3216e52b..68492f9ad85 100644 --- a/api/tests/unit_tests/core/dify_builder/test_ports.py +++ b/api/tests/unit_tests/core/dify_builder/test_ports.py @@ -131,6 +131,9 @@ class _StubAgent: def propose_build_repair(self, _built_node_ids): return [] + def learn_from_build(self, _goal_text, _requirements, _plan_items, _built_node_ids): + return "stub" + def analyze_impact(self, _goal_text, _graph): return {"edit_rules": {}, "target_node_ids": []} @@ -267,3 +270,23 @@ def test_pcstate_is_the_state_module_type_used_by_compare_and_advance(): # compare_and_advance's `next` parameter is typed PcState; sanity-check # the import path ports.py takes matches state.py's canonical enum. assert PcState.FIX_DIAGNOSE == "fix.diagnose" + + +def test_agent_missing_learn_from_build_is_not_a_dify_builder_agent(): + from core.dify_builder.ports import DifyBuilderAgent + + class _Missing: # a former-complete agent lacking only learn_from_build + def diagnose(self, *a): ... + def diagnose_checklist(self, *a): ... + def propose_repair(self, *a): ... + def generate_mock_inputs(self, *a): ... + def analyze_goal(self, *a): ... + def propose_plan_v1(self, *a): ... + def discover_resources(self, *a): ... + def bind_resources(self, *a): ... + def build_nodes(self, *a): ... + def propose_build_repair(self, *a): ... + def analyze_impact(self, *a): ... + def propose_edit_plan(self, *a): ... + def build_edit_intents(self, *a): ... + assert not isinstance(_Missing(), DifyBuilderAgent)