mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(sdk): _TERMINAL_TOOLS uses current gateway verb names
Smoke-7 evidence: every agent's first successful i_am_idle was followed
by a stop-hook error "you stopped without calling a terminal tool"
even though i_am_idle had just succeeded.
Root cause: agent_sdk._TERMINAL_TOOLS still held pre-gateway names
(roboco_agent_idle, roboco_task_submit_qa, ...). /terminal/tool_recorded
strips the `mcp__roboco-flow__` prefix and stores 'i_am_idle' — the
membership check against {'roboco_agent_idle', ...} never matched, so
had_terminal_recently() always returned False, and the stop hook nagged
every clean exit. Wasted ~2-3 turns per agent + burned stop_allowance.
Fix: rebuilt _TERMINAL_TOOLS with the current gateway verb names:
i_am_idle, i_am_done, i_am_blocked, i_documented, unclaim, pass, fail,
complete, submit_up, escalate_up, escalate_to_ceo.
7 new tests pin:
- every role's terminal verbs are recognized
- pre-gateway names are NOT in the set
- _SessionState.had_terminal_recently() returns True after i_am_idle
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
"""Smoke-7: SDK's _TERMINAL_TOOLS matches the current gateway verb names.
|
||||
|
||||
Original bug: the set still held pre-gateway names (roboco_agent_idle,
|
||||
roboco_task_submit_qa, ...). Tools recorded via /terminal/tool_recorded
|
||||
get their `mcp__roboco-flow__` prefix stripped, so `i_am_idle` lands in
|
||||
recent_tools — but the membership check against the old names always
|
||||
returned False. The stop hook then nagged every agent on every clean
|
||||
exit, wasting tokens and burning the stop-allowance counter.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from roboco.agent_sdk.server import _TERMINAL_TOOLS, _SessionState
|
||||
|
||||
|
||||
def test_idle_is_terminal() -> None:
|
||||
"""i_am_idle (the universal clean-exit verb) must be terminal."""
|
||||
assert "i_am_idle" in _TERMINAL_TOOLS
|
||||
|
||||
|
||||
def test_developer_terminal_verbs() -> None:
|
||||
"""A developer's clean-exit verbs must all be terminal."""
|
||||
for verb in ("i_am_done", "i_am_blocked", "unclaim"):
|
||||
assert verb in _TERMINAL_TOOLS, f"{verb} missing from _TERMINAL_TOOLS"
|
||||
|
||||
|
||||
def test_qa_terminal_verbs() -> None:
|
||||
"""QA's pass/fail verdicts and clean-exit verbs must all be terminal."""
|
||||
for verb in ("pass", "fail", "i_am_idle"):
|
||||
assert verb in _TERMINAL_TOOLS
|
||||
|
||||
|
||||
def test_pm_terminal_verbs() -> None:
|
||||
"""PM handoff verbs (complete, submit_up, escalate_*) must be terminal."""
|
||||
for verb in (
|
||||
"complete",
|
||||
"submit_up",
|
||||
"escalate_up",
|
||||
"escalate_to_ceo",
|
||||
):
|
||||
assert verb in _TERMINAL_TOOLS
|
||||
|
||||
|
||||
def test_documenter_terminal_verbs() -> None:
|
||||
"""Documenter's i_documented + i_am_idle must be terminal."""
|
||||
for verb in ("i_documented", "i_am_idle"):
|
||||
assert verb in _TERMINAL_TOOLS
|
||||
|
||||
|
||||
def test_pre_gateway_names_not_present() -> None:
|
||||
"""Pre-gateway names must NOT be in the set (they never match recent_tools)."""
|
||||
pre_gateway = {
|
||||
"roboco_agent_idle",
|
||||
"roboco_task_submit_qa",
|
||||
"roboco_task_qa_pass",
|
||||
"roboco_task_qa_fail",
|
||||
"roboco_task_complete",
|
||||
"roboco_task_docs_complete",
|
||||
"roboco_task_escalate",
|
||||
"roboco_task_escalate_to_ceo",
|
||||
}
|
||||
leaked = pre_gateway & _TERMINAL_TOOLS
|
||||
assert not leaked, (
|
||||
f"Pre-gateway names leaked into _TERMINAL_TOOLS: {sorted(leaked)}. "
|
||||
"These never match the suffix-stripped MCP verb names recorded by "
|
||||
"/terminal/tool_recorded."
|
||||
)
|
||||
|
||||
|
||||
def test_had_terminal_recently_after_i_am_idle() -> None:
|
||||
"""Recording i_am_idle marks the session as having terminal recently."""
|
||||
state = _SessionState()
|
||||
# Mirrors the SDK's suffix-strip on /terminal/tool_recorded.
|
||||
state.recent_tools.append("i_am_idle")
|
||||
state.last_tool = "i_am_idle"
|
||||
assert state.had_terminal_recently() is True, (
|
||||
"Stop hook reads had_terminal_recently to decide if a Stop is graceful. "
|
||||
"After i_am_idle the agent's exit IS graceful — must return True."
|
||||
)
|
||||
Reference in New Issue
Block a user