feat(api): Human Input Node (backend part) (#31646)

The backend part of the human in the loop (HITL) feature and relevant architecture / workflow engine changes.

Signed-off-by: yihong0618 <zouzou0208@gmail.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: -LAN- <laipz8200@outlook.com>
Co-authored-by: 盐粒 Yanli <yanli@dify.ai>
Co-authored-by: CrabSAMA <40541269+CrabSAMA@users.noreply.github.com>
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: yihong <zouzou0208@gmail.com>
Co-authored-by: Joel <iamjoel007@gmail.com>
This commit is contained in:
QuantumGhost
2026-01-30 10:18:49 +08:00
committed by GitHub
parent fedd097f63
commit 03e3acfc71
207 changed files with 19006 additions and 373 deletions

View File

@ -1296,6 +1296,7 @@ class TestConversationStatusCount:
assert result["success"] == 1 # One SUCCEEDED
assert result["failed"] == 1 # One FAILED
assert result["partial_success"] == 1 # One PARTIAL_SUCCEEDED
assert result["paused"] == 0
def test_status_count_app_id_filtering(self):
"""Test that status_count filters workflow runs by app_id for security."""
@ -1350,6 +1351,7 @@ class TestConversationStatusCount:
assert result["success"] == 0
assert result["failed"] == 0
assert result["partial_success"] == 0
assert result["paused"] == 0
def test_status_count_handles_invalid_workflow_status(self):
"""Test that status_count gracefully handles invalid workflow status values."""
@ -1404,3 +1406,57 @@ class TestConversationStatusCount:
assert result["success"] == 0
assert result["failed"] == 0
assert result["partial_success"] == 0
assert result["paused"] == 0
def test_status_count_paused(self):
"""Test status_count includes paused workflow runs."""
# Arrange
from core.workflow.enums import WorkflowExecutionStatus
app_id = str(uuid4())
conversation_id = str(uuid4())
workflow_run_id = str(uuid4())
conversation = Conversation(
app_id=app_id,
mode=AppMode.CHAT,
name="Test Conversation",
status="normal",
from_source="api",
)
conversation.id = conversation_id
mock_messages = [
MagicMock(
conversation_id=conversation_id,
workflow_run_id=workflow_run_id,
),
]
mock_workflow_runs = [
MagicMock(
id=workflow_run_id,
status=WorkflowExecutionStatus.PAUSED.value,
app_id=app_id,
),
]
with patch("models.model.db.session.scalars") as mock_scalars:
def mock_scalars_side_effect(query):
mock_result = MagicMock()
if "messages" in str(query):
mock_result.all.return_value = mock_messages
elif "workflow_runs" in str(query):
mock_result.all.return_value = mock_workflow_runs
else:
mock_result.all.return_value = []
return mock_result
mock_scalars.side_effect = mock_scalars_side_effect
# Act
result = conversation.status_count
# Assert
assert result["paused"] == 1