fix(mcp ux): even more error handling

who asked blud to conventional commits
This commit is contained in:
lda
2026-05-27 23:37:16 +07:00 Verified
parent 8a5525f87c
commit ffd352b660
7 changed files with 329 additions and 44 deletions
+52 -23
View File
@@ -9,6 +9,7 @@ from wf_artifacts import (
AvailableCapability,
AvailableSource,
DependencyDiagnostic,
DiagnosticSeverity,
DraftWorkspaceStore,
RequiredCapability,
RunStore,
@@ -201,13 +202,40 @@ class WorkflowSurfaceHandlers:
spec = self.service._get_qualified_spec(qualified_name)
handler = build_async_registry(spec)[spec.name]
result = await handler(payload, RuntimeContext(current_node_id=spec.name))
source_id = _source_id_for_capability(
self.service.capability_sources,
spec.name,
)
try:
result = await handler(payload, RuntimeContext(current_node_id=spec.name))
except Exception as exc:
return {
"qualified_name": spec.name,
"source_id": source_id,
"kind": "node_spec",
"deployment_id": None,
"outcome": "runtime_error",
"output": None,
"diagnostics": [
DependencyDiagnostic(
severity=DiagnosticSeverity.ERROR,
code="capability_call_failed",
logical_ref=spec.name,
bound_source=source_id,
message=(
f"Capability {spec.name!r} failed during test call: {exc}"
),
repair_hint=(
"Check the source runtime, then retry the capability "
"or inspect the deployment run if this happened inside "
"a workflow."
),
).model_dump(mode="json")
],
}
return {
"qualified_name": spec.name,
"source_id": _source_id_for_capability(
self.service.capability_sources,
spec.name,
),
"source_id": source_id,
"kind": "node_spec",
"deployment_id": None,
"outcome": result["outcome"],
@@ -710,19 +738,22 @@ class WorkflowSurfaceHandlers:
routes: dict[str, dict[str, str]] = {
DEFAULT_CALL_STEP_ID: {DEFAULT_OK_OUTCOME: "__end__"}
}
error_source = error_message_source or _first_state_path(draft_output)
if DEFAULT_ERROR_OUTCOME in outcomes and error_source is not None:
if DEFAULT_ERROR_OUTCOME in outcomes:
# The bootstrapper cannot infer provider-specific error envelopes.
# It only wires an error route when the caller gave, or output_map
# exposes, a concrete state path that can become a runtime message.
# Use a static default unless the caller explicitly supplies the
# state path containing a better provider error message.
error_input: dict[str, Any] = {
"target": {"root": "local", "parts": ["message"]},
"value": "Capability call failed",
}
if error_message_source is not None:
error_input = {
"target": {"root": "local", "parts": ["message"]},
"path": _graph_path_payload(error_message_source),
}
steps[DEFAULT_ERROR_STEP_ID] = {
"use": RUNTIME_ERROR_CAPABILITY,
"input": [
{
"target": {"root": "local", "parts": ["message"]},
"path": _graph_path_payload(error_source),
}
],
"input": [error_input],
"output": [],
}
routes[DEFAULT_CALL_STEP_ID][DEFAULT_ERROR_OUTCOME] = DEFAULT_ERROR_STEP_ID
@@ -954,6 +985,7 @@ class WorkflowSurfaceHandlers:
resume_readiness=record.resume_readiness.value,
interrupt=_interrupt_payload(run),
outcome=run.outcome,
error=run.error,
output=run.output,
trace_count=len(run.trace),
trace=(
@@ -1003,6 +1035,7 @@ class WorkflowSurfaceHandlers:
resume_readiness=blocked.resume_readiness.value,
interrupt=_interrupt_payload(stopped_run),
outcome=stopped_run.outcome,
error=stopped_run.error,
output=stopped_run.output,
diagnostics=diagnostics,
trace_count=len(stopped_run.trace),
@@ -1032,6 +1065,7 @@ class WorkflowSurfaceHandlers:
resume_readiness=next_record.resume_readiness.value,
interrupt=_interrupt_payload(run),
outcome=run.outcome,
error=run.error,
output=run.output,
trace_count=len(run.trace),
trace=(
@@ -1064,6 +1098,7 @@ class WorkflowSurfaceHandlers:
resume_readiness=record.resume_readiness.value,
interrupt=_interrupt_payload(run),
outcome=run.outcome,
error=run.error,
output=run.output,
diagnostics=record.diagnostics,
trace_count=len(run.trace),
@@ -1241,14 +1276,6 @@ def _schema_field_names(schema: dict[str, Any]) -> list[str]:
return sorted(str(name) for name in properties)
def _first_state_path(output_map: dict[str, str]) -> str | None:
"""Return the first mapped state path for minimal error-route bootstraps."""
for target in output_map.values():
if target.startswith("state."):
return target
return None
def _draft_input_maps(
*,
input: Sequence[InputBinding] | None,
@@ -1407,6 +1434,7 @@ def _run_payload(
resume_readiness: str | None = None,
interrupt: dict[str, Any] | None = None,
outcome: str | None = None,
error: str | None = None,
diagnostics: list[DependencyDiagnostic] | None = None,
output: dict[str, Any] | None = None,
trace_count: int = 0,
@@ -1424,6 +1452,7 @@ def _run_payload(
"resume_readiness": resume_readiness,
"interrupt": interrupt,
"outcome": outcome,
"error": error,
"output": output,
"diagnostics": [
diagnostic.model_dump(mode="json") for diagnostic in diagnostics or []
+4 -2
View File
@@ -280,7 +280,8 @@ class CreateMinimalDraftWorkspaceRequest(BaseModel):
default=None,
description=(
"Optional state path used as runtime_error.message when the capability "
"has an error outcome, for example state.error_message."
"has an error outcome, for example state.error_message. If omitted, "
"the generated error route uses a static default message."
),
)
title: str | None = Field(default=None, description="Optional workspace title.")
@@ -335,7 +336,8 @@ class CreateDraftWorkspaceFromCapabilityRequest(BaseModel):
default=None,
description=(
"Optional state path used as runtime_error.message when the capability "
"has an error outcome, for example state.error_message."
"has an error outcome, for example state.error_message. If omitted, "
"the generated error route uses a static default message."
),
)