fix(agent): return empty build-draft state instead of 404 for new agents

Fixes #40733

A freshly-created agent has no DEBUG_BUILD row yet. The
`GET /console/api/agent/{id}/build-draft` endpoint calls
`AgentComposerService.load_agent_app_build_draft`, which raised
`AgentVersionNotFoundError("Agent config version not found")` when
the build draft was missing. The front-end toasted this as a version
error on the agent configure page (Flask-RESTX also appended
"did you mean ..."), preventing the normal draft view from loading.

Treat a missing build-draft as an empty state, not a 404. Return
a payload that preserves the existing schema contract
(`variant`, `draft`, `agent_soul`) with `draft` and `agent_soul`
set to None, so the UI can render a fresh draft screen without a
toast. Same pattern as `load_workflow_composer`, which returns
`_empty_workflow_state` when the binding is missing instead of
erroring.

The `apply_agent_app_build_draft` and `discard_agent_app_build_draft`
methods keep their existing behaviour (apply still 404s on a missing
build draft since you can't apply nothing; discard already no-ops
on a missing build draft). Only the load path needed the empty-state
treatment.

Added regression test asserting the fix returns the empty
state on a missing build draft and doesn't commit/delete anything.
This commit is contained in:
Taranum01 2026-08-14 02:59:30 +05:30 committed by Taranum Wasu
parent 6e974e1a1c
commit 8829bdc0c1
2 changed files with 45 additions and 1 deletions

View File

@ -813,7 +813,11 @@ class AgentComposerService:
account_id=account_id,
)
if build_draft is None:
raise AgentVersionNotFoundError()
# A freshly-created agent has no DEBUG_BUILD row yet. Return an
# empty build-draft state (keeps the existing schema
# contract: variant, draft, agent_soul) so the UI can stay on
# the normal draft screen instead of toasting a 404.
return cls._empty_build_draft_state()
return cls._serialize_build_draft_state(build_draft)
@classmethod
@ -2243,6 +2247,18 @@ class AgentComposerService:
"agent_soul": draft.config_snapshot_dict,
}
@classmethod
def _empty_build_draft_state(cls) -> dict[str, Any]:
# Empty payload for a not-yet-created DEBUG_BUILD draft. Keeps
# the AgentBuildDraftResponse schema (variant/draft/agent_soul) so
# the front-end can render a fresh draft screen without a 404
# toast. Regression for #40733.
return {
"variant": ComposerVariant.AGENT_APP.value,
"draft": None,
"agent_soul": None,
}
@classmethod
def _get_draft_workflow(cls, *, session: Session, tenant_id: str, app_id: str) -> Workflow:
workflow = session.scalar(

View File

@ -2170,8 +2170,36 @@ def test_agent_app_build_draft_apply_marks_unpublished_when_build_draft_differs(
)
def test_load_agent_app_build_draft_returns_empty_state_when_missing(
monkeypatch: pytest.MonkeyPatch, sqlite_session: Session
):
"""Regression for #40733: a freshly-created agent has no DEBUG_BUILD row.
GET /agent/<id>/build-draft used to return 404 "Agent config version
not found" and the front-end toasted it as a version error. The fix
returns an empty build-draft state so the UI stays on the normal
draft screen with no toast.
"""
session = sqlite_session
monkeypatch.setattr(AgentComposerService, "_get_agent_draft", lambda **kwargs: None)
result = AgentComposerService.load_agent_app_build_draft(
session=session,
tenant_id="tenant-1",
agent_id="agent-1",
account_id="account-1",
)
# Schema contract (variant/draft/agent_soul) preserved; draft and
# agent_soul are None so the UI can render an empty draft.
assert result["variant"] == "agent_app"
assert result["draft"] is None
assert result["agent_soul"] is None
def test_agent_app_composer_candidates_and_impact(monkeypatch: pytest.MonkeyPatch, sqlite_session: Session):
session = sqlite_session
bindings = [
WorkflowAgentNodeBinding(
tenant_id="tenant-1",