Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions katana_mcp_server/src/katana_mcp/tools/_modification.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,25 +554,40 @@ def to_tool_result(
) -> ToolResult:
"""Build a :class:`ToolResult` with a Prefab UI from a ModificationResponse.

Preview branch: emits ``build_modification_preview_ui`` with the
direct-apply rail (Confirm fires ``tools/call`` directly + iframe
pushes the structured result back via ``ui/update-model-context``).
Non-preview branch: emits ``build_modification_result_ui`` summarizing
each action's terminal status.

``confirm_request`` is the original Pydantic request (its ``preview``
field flips to ``False`` when the iframe re-issues for apply);
``confirm_tool`` is the registered MCP tool name to re-call. The
preview branch wires both into the Confirm-button CallTool; the
result branch uses ``confirm_tool`` to derive the title verb so a
delete reads "Product Delete" instead of "Product Modification".
Dispatches by ``response.entity_type`` to a per-entity builder that
handles BOTH preview and applied states (one entrypoint per entity,
matching the create-card pattern in #728 and the modify-card design
in #721). Each per-entity builder shares its entity-view renderer
with its create-card sibling (e.g. ``_render_po_entity_view``) —
create cards call it with no diff overlay; modify cards pass the
per-field diff lookup built from ``response.actions[*].changes``.

Entities not yet migrated fall back to ``build_modification_preview_ui``
/ ``build_modification_result_ui`` — the legacy single-entrypoint pair
today's modify/delete/correct tools render through. Removed once
every #721 child PR has shipped.
"""
from katana_mcp.tools.prefab_ui import (
build_modification_preview_ui,
build_modification_result_ui,
build_po_modify_ui,
)

response_dict = response.model_dump()

# Per-entity dispatch — PO migrated in #722; remaining entities
# (SO, MO, stock_transfer, item) fall through to the legacy
# builders until their respective child PRs land.
if response.entity_type == "purchase_order":
ui = build_po_modify_ui(
response_dict,
confirm_request=confirm_request,
confirm_tool=confirm_tool,
)
Comment thread
dougborg marked this conversation as resolved.
return make_tool_result(response, ui=ui)

# Legacy path — preserves today's behavior for not-yet-migrated
# entity types so #722 can land without cross-entity test churn.
if response.is_preview:
ui = build_modification_preview_ui(
response_dict,
Expand Down
12 changes: 11 additions & 1 deletion katana_mcp_server/src/katana_mcp/tools/_modification_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -762,12 +762,23 @@ async def run_modify_plan(
)
raise ValueError(msg)

# ``prior_state`` populated on BOTH branches: apply path uses it for
# the revert reference; preview path uses it for renderer-side entity
# view (the modify-card design in #721 wants the unchanged header /
# reference fields to render as context around the diff-decorated
# changing fields — without prior_state, the card would show only the
# changed fields and a mostly-empty header). ``existing`` may be None
# if the diff fetch failed; ``serialize_for_prior_state`` tolerates
# that and returns ``None`` so the renderer sees no snapshot.
prior_state = serialize_for_prior_state(existing)

if request.preview:
return ModificationResponse(
entity_type=entity_type,
entity_id=request.id,
is_preview=True,
actions=plan_to_preview_results(plan),
prior_state=prior_state,
warnings=warnings,
next_actions=[
f"Review {len(plan)} planned action(s)",
Expand All @@ -777,7 +788,6 @@ async def run_modify_plan(
message=f"Preview: {len(plan)} action(s) planned for {entity_label}",
)

prior_state = serialize_for_prior_state(existing)
actions = await execute_plan(plan)
message, next_actions = summarize_modify_outcome(
actions, len(plan), entity_label=entity_label, tool_name=tool_name
Expand Down
35 changes: 23 additions & 12 deletions katana_mcp_server/src/katana_mcp/tools/foundation/corrections.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,13 +578,22 @@ async def _correct_manufacturing_order_impl(
close_phase = _build_close_mo_actions(request.id, snapshot, services)
phases = [revert_phase, edit_phase, recreate_phase, close_phase]

# ``prior_state`` populated on BOTH branches: apply path uses it for
# the revert reference; preview path uses it for renderer-side entity
# view (#721 modify-card design — without prior_state, the rendered
# card has only the changed-field diffs and an almost-empty header).
prior_state = _augment_prior_state_with_snapshot(
serialize_for_prior_state(existing_mo), snapshot
)

if request.preview:
full_plan = [action for phase in phases for action in phase]
return ModificationResponse(
entity_type="manufacturing_order",
entity_id=request.id,
is_preview=True,
actions=plan_to_preview_results(full_plan),
prior_state=prior_state,
warnings=_close_state_warnings_mo(snapshot),
next_actions=[
f"Review {len(full_plan)} planned action(s) for MO {request.id}",
Expand All @@ -600,10 +609,6 @@ async def _correct_manufacturing_order_impl(
f"({len(full_plan)} action(s))"
),
)

prior_state = _augment_prior_state_with_snapshot(
serialize_for_prior_state(existing_mo), snapshot
)
aggregated, failed = await _run_phases_until_failure(phases)
if failed:
return _build_failure_response(
Expand Down Expand Up @@ -1097,13 +1102,21 @@ async def _correct_sales_order_impl(
close_phase = [_build_close_so_action(request.id, services)]
phases = [delete_phase, revert_phase, edit_phase, recreate_phase, close_phase]

# See #722 note on the MO correction above — prior_state populated
# on both branches so the per-entity modify card can render the
# unchanged-field context around the diff overlay on preview too.
prior_state = _augment_prior_state_with_snapshot(
serialize_for_prior_state(existing_so), snapshot
)

if request.preview:
full_plan = [action for phase in phases for action in phase]
return ModificationResponse(
entity_type="sales_order",
entity_id=request.id,
is_preview=True,
actions=plan_to_preview_results(full_plan),
prior_state=prior_state,
warnings=_close_state_warnings_so(snapshot),
next_actions=[
f"Review {len(full_plan)} planned action(s) for SO {request.id}",
Expand All @@ -1118,10 +1131,6 @@ async def _correct_sales_order_impl(
f"{request.id} ({len(full_plan)} action(s))"
),
)

prior_state = _augment_prior_state_with_snapshot(
serialize_for_prior_state(existing_so), snapshot
)
aggregated, failed = await _run_phases_until_failure(phases)
if failed:
return _build_failure_response(
Expand Down Expand Up @@ -1537,13 +1546,19 @@ async def _correct_purchase_order_impl(
]
phases = [revert_phase, edit_phase, receive_phase]

# See #722 note on the MO / SO correction above.
prior_state = _augment_prior_state_with_snapshot(
serialize_for_prior_state(existing_po), snapshot
)

if request.preview:
full_plan = [action for phase in phases for action in phase]
return ModificationResponse(
entity_type="purchase_order",
entity_id=request.id,
is_preview=True,
actions=plan_to_preview_results(full_plan),
prior_state=prior_state,
warnings=_close_state_warnings_po(snapshot),
next_actions=[
f"Review {len(full_plan)} planned action(s) for PO {request.id}",
Expand All @@ -1557,10 +1572,6 @@ async def _correct_purchase_order_impl(
f"{request.id} ({len(full_plan)} action(s))"
),
)

prior_state = _augment_prior_state_with_snapshot(
serialize_for_prior_state(existing_po), snapshot
)
aggregated, failed = await _run_phases_until_failure(phases)
if failed:
return _build_failure_response(
Expand Down
Loading
Loading