mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(mcp): register propose_feature_spotlight in do_server (v0.18.0 B follow-up)
The v0.18.0 feature-spotlight verb was wired at the role-config, content-actions, and route layers but never added to do_server's _TOOLS registry, so _register_tools() (which registers only granted ∩ _TOOLS) silently dropped it — a spawned Head of Marketing could NOT call propose_feature_spotlight, making the feature non-functional as shipped. Added the do_server wrapper + _TOOLS entry (mirroring propose_roadmap) and a regression test asserting every role-granted do-tool is registered, so this class of gap can't recur.
This commit is contained in:
@@ -547,6 +547,31 @@ def propose_roadmap(cycle_goal: str, items: list[dict[str, Any]]) -> dict[str, A
|
||||
)
|
||||
|
||||
|
||||
def propose_feature_spotlight(
|
||||
feature_slug: str, feature_title: str, body: str
|
||||
) -> dict[str, Any]:
|
||||
"""Head of Marketing: draft ONE feature-spotlight marketing post.
|
||||
|
||||
Call this exactly ONCE per exploration cycle, after investigating the
|
||||
CHANGELOG, feature-flags ledger, docs/map, charter, and KB to pick a real,
|
||||
under-publicized capability. The draft is held in the X post queue for the
|
||||
CEO to edit/approve — nothing auto-posts.
|
||||
|
||||
Args:
|
||||
feature_slug: Stable slug identifying the feature (the dedup key).
|
||||
feature_title: Short human title of the feature.
|
||||
body: The tweet text (plain, <=280 chars, no invented facts).
|
||||
"""
|
||||
return _post(
|
||||
"/api/v1/do/propose_feature_spotlight",
|
||||
{
|
||||
"feature_slug": feature_slug,
|
||||
"feature_title": feature_title,
|
||||
"body": body,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def dm(
|
||||
recipient: str,
|
||||
text: str,
|
||||
@@ -787,6 +812,7 @@ _TOOLS: dict[str, Any] = {
|
||||
"note": note,
|
||||
"pitch": pitch,
|
||||
"propose_roadmap": propose_roadmap,
|
||||
"propose_feature_spotlight": propose_feature_spotlight,
|
||||
"dm": dm,
|
||||
"notify": notify,
|
||||
"evidence": evidence,
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
"""Every do-verb granted to a role must be registered in ``do_server._TOOLS``.
|
||||
|
||||
Regression guard for the class of bug where a new content verb is wired at the
|
||||
role-config + content-actions + route layers but never added to do_server's
|
||||
``_TOOLS`` registry — ``_register_tools()`` then registers only the intersection
|
||||
of granted verbs and ``_TOOLS``, silently dropping the verb, so the agent can
|
||||
never call it. This is exactly what happened to ``propose_feature_spotlight`` in
|
||||
the v0.18.0 feature-spotlight work.
|
||||
"""
|
||||
|
||||
import importlib
|
||||
|
||||
import pytest
|
||||
from roboco.services.gateway.role_config import ROLE_CONFIGS
|
||||
|
||||
|
||||
def test_every_granted_do_tool_is_registered(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
# do_server reads agent/orchestrator env + a manifest at import; the
|
||||
# ROBOCO_ALLOW_FULL_TOOLSET escape hatch lets it register the full _TOOLS
|
||||
# set without a manifest file.
|
||||
monkeypatch.setenv("ROBOCO_AGENT_ID", "00000000-0000-0000-0000-000000000099")
|
||||
monkeypatch.setenv("ROBOCO_AGENT_ROLE", "developer")
|
||||
monkeypatch.setenv("ROBOCO_ORCHESTRATOR_URL", "http://test-orchestrator:8000")
|
||||
monkeypatch.setenv("ROBOCO_SDK_URL", "http://test-sdk:9000")
|
||||
monkeypatch.setenv("ROBOCO_ALLOW_FULL_TOOLSET", "1")
|
||||
|
||||
from roboco.mcp import do_server
|
||||
|
||||
importlib.reload(do_server)
|
||||
|
||||
registered = set(do_server._TOOLS)
|
||||
missing = sorted(
|
||||
(cfg.role, verb)
|
||||
for cfg in ROLE_CONFIGS.values()
|
||||
for verb in cfg.do_tools
|
||||
if verb not in registered
|
||||
)
|
||||
assert not missing, (
|
||||
"do-tools granted in role_config but absent from do_server._TOOLS "
|
||||
f"(the MCP server cannot expose them): {missing}"
|
||||
)
|
||||
Reference in New Issue
Block a user