refactor: extract NextActions into reusable workflow-surface models

Move wrapper-draft next_actions from handler-local dict helpers into
typed models in workflow_surface/next_actions.py. Adds NextActionTool
enum, NextActionPatchExample, and NextActions.from_wrapper_hints()
constructor. Replaces private _wrapper_draft_next_actions and
_wrapper_draft_patch_examples helpers. Adds can_continue as additive
advisory field. Keeps backward-compatible aliases in models.py.
This commit is contained in:
lda
2026-05-31 18:49:11 +07:00 Verified
parent 09cdae4f8e
commit 527f6023c6
7 changed files with 247 additions and 129 deletions
+5
View File
@@ -116,6 +116,11 @@ def test_server_exposes_upstream_admin_and_workflow_tools() -> None:
next_actions_schema = from_capability_output["properties"]["next_actions"]
assert "recommended_next_tool" in next_actions_schema["properties"]
assert "patch_examples" in next_actions_schema["properties"]
assert "can_continue" in next_actions_schema["properties"]
assert (
"Advisory"
in next_actions_schema["properties"]["can_continue"]["description"]
)
assert (
"Advisory"
in next_actions_schema["properties"]["can_save_now"]["description"]
@@ -541,6 +541,7 @@ def test_workflow_surface_creates_draft_workspace_from_capability_hints() -> Non
assert result["wrapper_hints"]["input_map"] == {"input.text": "text"}
assert result["wrapper_hints"]["output_map"] == {"echoed": "state.echoed"}
next_actions = result["next_actions"]
assert next_actions["can_continue"] is True
assert next_actions["can_save_now"] is True
assert (
next_actions["recommended_next_tool"] == "wf.workflow.validate_draft_workspace"
@@ -705,6 +706,7 @@ def test_workflow_surface_low_confidence_draft_returns_patch_guidance() -> None:
)
next_actions = result["next_actions"]
assert next_actions["can_continue"] is True
assert next_actions["can_save_now"] is False
assert next_actions["recommended_next_tool"] == "wf.workflow.patch_draft_workspace"
assert "missing wrapper decisions" in next_actions["reason"]
@@ -0,0 +1,62 @@
from __future__ import annotations
from wf_mcp.workflow_surface.next_actions import NextActionTool, NextActions
def test_next_actions_from_high_confidence_wrapper_hints_can_validate() -> None:
actions = NextActions.from_wrapper_hints(
workspace_id="echo_workspace",
revision=3,
hints={
"confidence": "high",
"missing_decisions": [],
"notes": [],
},
)
dumped = actions.model_dump(mode="json")
assert dumped["can_continue"] is True
assert dumped["can_save_now"] is True
assert dumped["recommended_next_tool"] == (
NextActionTool.VALIDATE_DRAFT_WORKSPACE.value
)
assert "high confidence" in dumped["reason"]
assert dumped["patch_examples"] == []
assert dumped["warnings"] == []
def test_next_actions_from_low_confidence_wrapper_hints_can_patch() -> None:
actions = NextActions.from_wrapper_hints(
workspace_id="echo_workspace",
revision=4,
hints={
"confidence": "low",
"missing_decisions": [
{
"kind": "review_nested_output",
"message": "Review nested output fields before mapping.",
},
{
"kind": "confirm_boolean_outcomes",
"message": "Boolean fields may be data, not outcomes.",
},
],
"notes": ["Raw MCP tool output is not workflow-shaped."],
},
)
dumped = actions.model_dump(mode="json")
assert dumped["can_continue"] is True
assert dumped["can_save_now"] is False
assert dumped["recommended_next_tool"] == NextActionTool.PATCH_DRAFT_WORKSPACE.value
assert "missing wrapper decisions" in dumped["reason"]
assert dumped["warnings"][0] == "Raw MCP tool output is not workflow-shaped."
assert dumped["patch_examples"][0]["tool"] == (
NextActionTool.PATCH_DRAFT_WORKSPACE.value
)
assert dumped["patch_examples"][0]["request"]["workspace_id"] == "echo_workspace"
assert dumped["patch_examples"][0]["request"]["revision"] == 4
assert dumped["patch_examples"][0]["request"]["patch"][0]["path"] == (
"/draft/steps/call/output"
)
assert dumped["patch_examples"][1]["request"]["patch"] == []