mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(gateway): four PM-lifecycle smoke regressions
* choreographer.escalate_up: AttributeError when target lookup returned
None. Switched from role-based escalate_up_to_role (which mishandled
slug-shaped escalation_target like "main-pm" because AgentRole only
accepts underscore form) to slug-based task.escalate, with explicit
None-handling that returns invalid_state instead of 500.
* prompts/roles/cell_pm.md + main_pm.md: enumerate the new lifecycle
verbs (i_will_plan, delegate, submit_up, give_me_work, i_am_idle).
Without this, PM agents fell back to calling i_will_work_on (the dev
verb) and 404'd at /api/v2/flow/cell_pm/i_will_work_on. Workflow
walkthroughs included.
* messaging.get_channel_by_slug + get_or_create_channel_by_slug: strip
leading "#" so "#main-pm-board" resolves to the row stored as
"main-pm-board". Agents follow Slack convention; gateway must accept
it.
* agent_sdk session-end post-mortem hook: corrected payload shape from
{content, kind:"reflect"} to {type:"task_reflection", title, content}
so /api/journals/me/entries validates. Added pad-to-min-length so the
50-char content gate doesn't reject thin post-mortems.
* test_choreographer_pm: updated escalate_up test to assert task.escalate
is awaited, plus regression test for the None-target invalid_state path.
380 unit tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
4520293def
commit
27dccc7215
@@ -551,9 +551,9 @@ async def test_escalate_up_routes_by_escalation_target() -> None:
|
||||
task_svc.get.return_value = t
|
||||
task_svc.agent_for.return_value = MagicMock(
|
||||
role="cell_pm",
|
||||
escalation_target="main_pm",
|
||||
escalation_target="main-pm",
|
||||
)
|
||||
task_svc.escalate_up_to_role.return_value = after
|
||||
task_svc.escalate.return_value = after
|
||||
journal_svc = AsyncMock()
|
||||
journal_svc.has_decision_for_task.return_value = True
|
||||
deps = _make_deps(task=task_svc, journal=journal_svc)
|
||||
@@ -561,14 +561,37 @@ async def test_escalate_up_routes_by_escalation_target() -> None:
|
||||
|
||||
env = await c.escalate_up(pm_id, task_id, reason="cross-cell coordination needed")
|
||||
assert env.error is None
|
||||
task_svc.escalate_up_to_role.assert_awaited_once_with(
|
||||
task_svc.escalate.assert_awaited_once_with(
|
||||
pm_id,
|
||||
task_id,
|
||||
"main_pm",
|
||||
"cross-cell coordination needed",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_escalate_up_returns_invalid_state_when_target_lookup_fails() -> None:
|
||||
"""Regression: escalate_up_to_role returning None used to crash on t.status."""
|
||||
pm_id = uuid4()
|
||||
task_id = uuid4()
|
||||
t = MagicMock(id=task_id, status="blocked", assigned_to=pm_id, team="backend")
|
||||
task_svc = AsyncMock()
|
||||
task_svc.get.return_value = t
|
||||
task_svc.agent_for.return_value = MagicMock(
|
||||
role="cell_pm",
|
||||
escalation_target="main-pm",
|
||||
)
|
||||
task_svc.escalate.return_value = None # target slug not found in DB
|
||||
journal_svc = AsyncMock()
|
||||
journal_svc.has_decision_for_task.return_value = True
|
||||
deps = _make_deps(task=task_svc, journal=journal_svc)
|
||||
c = Choreographer(deps)
|
||||
|
||||
env = await c.escalate_up(pm_id, task_id, reason="x")
|
||||
body = env.as_dict()
|
||||
assert body["error"] == "invalid_state"
|
||||
assert "main-pm" in body["message"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_escalate_up_blocks_without_journal_decision() -> None:
|
||||
pm_id = uuid4()
|
||||
|
||||
Reference in New Issue
Block a user