generated from coulomb/repo-seed
Record deferred reconciliation requests
This commit is contained in:
@@ -5,6 +5,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from api.database import get_session
|
||||
from api.models.agent_message import AgentMessage
|
||||
from api.models.managed_repo import ManagedRepo
|
||||
from api.models.task import Task
|
||||
from api.models.task import TaskStatus
|
||||
@@ -37,6 +38,39 @@ async def _workstream_tasks_terminal(session: AsyncSession, workstream_id: uuid.
|
||||
return bool(statuses) and all(status in {"done", "cancelled"} for status in statuses)
|
||||
|
||||
|
||||
def _deferred_message(
|
||||
*,
|
||||
body: StateChangeRequest,
|
||||
current_status: str,
|
||||
target_status: str,
|
||||
classification: ReconciliationClass,
|
||||
reason: str,
|
||||
follow_up: str,
|
||||
workplan_path: str | None,
|
||||
) -> AgentMessage:
|
||||
subject = f"Reconcile {body.target_type} state change: {current_status} -> {target_status}"
|
||||
lines = [
|
||||
"A UI-originated state change could not be written through immediately.",
|
||||
"",
|
||||
f"target_type: {body.target_type}",
|
||||
f"target_id: {body.target_id}",
|
||||
f"actor: {body.actor}",
|
||||
f"intent: {body.intent or ''}",
|
||||
f"current_status: {current_status}",
|
||||
f"target_status: {target_status}",
|
||||
f"reconciliation_class: {classification.value}",
|
||||
f"reason: {reason}",
|
||||
f"follow_up: {follow_up}",
|
||||
f"workplan_path: {workplan_path or ''}",
|
||||
]
|
||||
return AgentMessage(
|
||||
from_agent=body.actor,
|
||||
to_agent="state-hub",
|
||||
subject=subject,
|
||||
body="\n".join(lines),
|
||||
)
|
||||
|
||||
|
||||
@router.post("/state-change", response_model=StateChangeResponse)
|
||||
async def classify_state_change(
|
||||
body: StateChangeRequest,
|
||||
@@ -76,6 +110,7 @@ async def classify_state_change(
|
||||
tasks_terminal=tasks_terminal,
|
||||
)
|
||||
write_result = "not_attempted"
|
||||
reconciliation_record_id = None
|
||||
if body.apply:
|
||||
if classification.reconciliation_class == ReconciliationClass.WRITE_THROUGH and workplan_ref:
|
||||
patch_workplan_status(workplan_ref.path, target_status)
|
||||
@@ -83,6 +118,19 @@ async def classify_state_change(
|
||||
await session.commit()
|
||||
write_result = "applied"
|
||||
else:
|
||||
msg = _deferred_message(
|
||||
body=body,
|
||||
current_status=current_status,
|
||||
target_status=target_status,
|
||||
classification=classification.reconciliation_class,
|
||||
reason=classification.reason,
|
||||
follow_up=classification.follow_up,
|
||||
workplan_path=workplan_ref.relative_path if workplan_ref else None,
|
||||
)
|
||||
session.add(msg)
|
||||
await session.commit()
|
||||
await session.refresh(msg)
|
||||
reconciliation_record_id = msg.id
|
||||
write_result = "not_applicable"
|
||||
return StateChangeResponse(
|
||||
target_type=body.target_type,
|
||||
@@ -99,6 +147,7 @@ async def classify_state_change(
|
||||
follow_up=classification.follow_up,
|
||||
write_through_result=write_result,
|
||||
workplan_path=workplan_ref.relative_path if workplan_ref else None,
|
||||
reconciliation_record_id=reconciliation_record_id,
|
||||
)
|
||||
|
||||
task = await session.get(Task, body.target_id)
|
||||
@@ -137,6 +186,7 @@ async def classify_state_change(
|
||||
blocking_reason=body.blocking_reason,
|
||||
)
|
||||
write_result = "not_attempted"
|
||||
reconciliation_record_id = None
|
||||
if body.apply:
|
||||
if (
|
||||
classification.reconciliation_class == ReconciliationClass.WRITE_THROUGH
|
||||
@@ -150,6 +200,19 @@ async def classify_state_change(
|
||||
await session.commit()
|
||||
write_result = "applied"
|
||||
else:
|
||||
msg = _deferred_message(
|
||||
body=body,
|
||||
current_status=current_status,
|
||||
target_status=target_status,
|
||||
classification=classification.reconciliation_class,
|
||||
reason=classification.reason,
|
||||
follow_up=classification.follow_up,
|
||||
workplan_path=workplan_ref.relative_path if workplan_ref else None,
|
||||
)
|
||||
session.add(msg)
|
||||
await session.commit()
|
||||
await session.refresh(msg)
|
||||
reconciliation_record_id = msg.id
|
||||
write_result = "not_applicable"
|
||||
return StateChangeResponse(
|
||||
target_type=body.target_type,
|
||||
@@ -166,4 +229,5 @@ async def classify_state_change(
|
||||
follow_up=classification.follow_up,
|
||||
write_through_result=write_result,
|
||||
workplan_path=workplan_ref.relative_path if workplan_ref else None,
|
||||
reconciliation_record_id=reconciliation_record_id,
|
||||
)
|
||||
|
||||
@@ -39,3 +39,4 @@ class StateChangeResponse(BaseModel):
|
||||
follow_up: str
|
||||
write_through_result: Literal["not_attempted", "applied", "not_applicable"] = "not_attempted"
|
||||
workplan_path: str | None = None
|
||||
reconciliation_record_id: uuid.UUID | None = None
|
||||
|
||||
Reference in New Issue
Block a user